diff --git a/src/Runner.Worker/Handlers/ContainerActionHandler.cs b/src/Runner.Worker/Handlers/ContainerActionHandler.cs index 1f67c9dd3..099495cb4 100644 --- a/src/Runner.Worker/Handlers/ContainerActionHandler.cs +++ b/src/Runner.Worker/Handlers/ContainerActionHandler.cs @@ -239,6 +239,11 @@ namespace GitHub.Runner.Worker.Handlers Environment["ACTIONS_RESULTS_URL"] = resultsUrl; } + if (ExecutionContext.Global.Variables.TryGetValue("actions_cache_mode", out var cacheMode) && !string.IsNullOrEmpty(cacheMode)) + { + Environment["ACTIONS_CACHE_MODE"] = cacheMode; + } + if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.SetOrchestrationIdEnvForActions) ?? false) { if (ExecutionContext.Global.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out var orchestrationId) && !string.IsNullOrEmpty(orchestrationId)) diff --git a/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs b/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs index 29def039f..85ff32777 100644 --- a/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs +++ b/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs @@ -78,6 +78,11 @@ namespace GitHub.Runner.Worker.Handlers Environment["ACTIONS_CACHE_SERVICE_V2"] = bool.TrueString; } + if (ExecutionContext.Global.Variables.TryGetValue("actions_cache_mode", out var cacheMode) && !string.IsNullOrEmpty(cacheMode)) + { + Environment["ACTIONS_CACHE_MODE"] = cacheMode; + } + if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.SetOrchestrationIdEnvForActions) ?? false) { if (ExecutionContext.Global.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out var orchestrationId) && !string.IsNullOrEmpty(orchestrationId)) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 33f93825a..22bfccc82 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -171,6 +171,12 @@ namespace GitHub.Runner.Worker context.Output($"Secret source: {secretSource}"); } + var cacheMode = jobContext.Global.Variables.Get("actions_cache_mode"); + if (!string.IsNullOrEmpty(cacheMode)) + { + context.Output($"Actions cache-mode: {cacheMode}"); + } + var repoFullName = context.GetGitHubContext("repository"); ArgUtil.NotNull(repoFullName, nameof(repoFullName)); context.Debug($"Primary repository: {repoFullName}"); diff --git a/src/Test/L0/Worker/HandlerL0.cs b/src/Test/L0/Worker/HandlerL0.cs index f9dbd67c7..111bfc524 100644 --- a/src/Test/L0/Worker/HandlerL0.cs +++ b/src/Test/L0/Worker/HandlerL0.cs @@ -1,7 +1,12 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using GitHub.Actions.RunService.WebApi; using GitHub.DistributedTask.Pipelines; +using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Sdk; using GitHub.Runner.Worker; @@ -85,5 +90,122 @@ namespace GitHub.Runner.Common.Tests.Worker Assert.Equal("ubuntu:20.04", _stepTelemetry.Action); } } + + [Theory] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + [InlineData("read")] + [InlineData("none")] + [InlineData("write")] + [InlineData("write-only")] + public async Task RunAsync_ExportsCacheModeEnv_WhenVariableSet(string mode) + { + using (TestHostContext hc = CreateTestContext()) + { + var environment = await RunNodeScriptActionHandlerAsync(hc, new Dictionary + { + { "actions_cache_mode", mode } + }); + + Assert.True(environment.TryGetValue("ACTIONS_CACHE_MODE", out var value)); + Assert.Equal(mode, value); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task RunAsync_DoesNotExportCacheModeEnv_WhenVariableAbsent() + { + using (TestHostContext hc = CreateTestContext()) + { + var environment = await RunNodeScriptActionHandlerAsync(hc, new Dictionary()); + + Assert.False(environment.ContainsKey("ACTIONS_CACHE_MODE")); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task RunAsync_DoesNotExportCacheModeEnv_WhenVariableEmpty() + { + using (TestHostContext hc = CreateTestContext()) + { + var environment = await RunNodeScriptActionHandlerAsync(hc, new Dictionary + { + { "actions_cache_mode", "" } + }); + + Assert.False(environment.ContainsKey("ACTIONS_CACHE_MODE")); + } + } + + private async Task> RunNodeScriptActionHandlerAsync(TestHostContext hc, IDictionary variables) + { + var actionDirectory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), Guid.NewGuid().ToString()); + Directory.CreateDirectory(actionDirectory); + var scriptFile = "main.js"; + File.WriteAllText(Path.Combine(actionDirectory, scriptFile), "// noop"); + + var serverVariables = new Variables(hc, variables); + var endpoints = new List + { + new ServiceEndpoint() + { + Name = WellKnownServiceEndpointNames.SystemVssConnection, + Url = new Uri("https://pipelines.actions.githubusercontent.com"), + Authorization = new EndpointAuthorization() + { + Scheme = "Test", + Parameters = { { "AccessToken", "token" } } + } + } + }; + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + Endpoints = endpoints, + PrependPath = new List(), + EnvironmentVariables = new Dictionary() + }); + _ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData()); + _ec.Setup(x => x.GetGitHubContext("workspace")).Returns(actionDirectory); + _ec.Setup(x => x.GetMatchers()).Returns(new List()); + _ec.Setup(x => x.ForceCompleted).Returns(new TaskCompletionSource().Task); + _ec.Setup(x => x.CancellationToken).Returns(CancellationToken.None); + + var stepHost = new Mock(); + stepHost.Setup(x => x.DetermineNodeRuntimeVersion(It.IsAny(), It.IsAny())).ReturnsAsync("node20"); + stepHost.Setup(x => x.ResolvePathForStepHost(It.IsAny(), It.IsAny())).Returns((IExecutionContext ec, string path) => path); + stepHost.Setup(x => x.ExecuteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(0); + + var handler = new NodeScriptActionHandler(); + handler.Initialize(hc); + handler.ExecutionContext = _ec.Object; + handler.StepHost = stepHost.Object; + handler.Environment = new Dictionary(); + handler.Inputs = new Dictionary(); + handler.RuntimeVariables = serverVariables; + handler.ActionDirectory = actionDirectory; + handler.Action = new RepositoryPathReference() { Name = "actions/checkout", Ref = "v2" }; + handler.Data = new NodeJSActionExecutionData() { Script = scriptFile, NodeVersion = "node20" }; + + await handler.RunAsync(ActionRunStage.Main); + + return handler.Environment; + } } }