diff --git a/src/NuGet.Config b/src/NuGet.Config index 5816f1bb3..95143bd09 100644 --- a/src/NuGet.Config +++ b/src/NuGet.Config @@ -3,8 +3,6 @@ - - diff --git a/src/Runner.Worker/ActionRunner.cs b/src/Runner.Worker/ActionRunner.cs index d88e1910c..c604679bc 100644 --- a/src/Runner.Worker/ActionRunner.cs +++ b/src/Runner.Worker/ActionRunner.cs @@ -179,17 +179,15 @@ namespace GitHub.Runner.Worker ExecutionContext.Debug("Loading env"); var environment = new Dictionary(VarUtil.EnvironmentVariableKeyComparer); - // Apply environment set using ##[set-env] first since these are job level env - foreach (var env in ExecutionContext.EnvironmentVariables) +#if OS_WINDOWS + var envContext = ExecutionContext.ExpressionValues["env"] as DictionaryContextData; +#else + var envContext = ExecutionContext.ExpressionValues["env"] as CaseSensitiveDictionaryContextData; +#endif + // Apply environment from env context, env context contains job level env and action's evn block + foreach (var env in envContext) { - environment[env.Key] = env.Value ?? string.Empty; - } - - // Apply action's env block later. - var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(Action.Environment, ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer); - foreach (var env in actionEnvironment) - { - environment[env.Key] = env.Value ?? string.Empty; + environment[env.Key] = env.Value.ToString(); } // Apply action's intra-action state at last diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index c87bb0569..a0de59458 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -76,10 +76,33 @@ namespace GitHub.Runner.Worker // Start step.ExecutionContext.Start(); - // Set GITHUB_ACTION + // Populate env context for each step + Trace.Info("Initialize Env context for step"); +#if OS_WINDOWS + var envContext = new DictionaryContextData(); +#else + var envContext = new CaseSensitiveDictionaryContextData(); +#endif + step.ExecutionContext.ExpressionValues["env"] = envContext; + foreach (var pair in step.ExecutionContext.EnvironmentVariables) + { + envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); + } + if (step is IActionRunner actionStep) { + // Set GITHUB_ACTION step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name); + + // Evaluate and merge action's env block to env context + var templateTrace = step.ExecutionContext.ToTemplateTraceWriter(); + var schema = new PipelineTemplateSchemaFactory().CreateSchema(); + var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema); + var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer); + foreach (var env in actionEnvironment) + { + envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); + } } // Initialize scope diff --git a/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs b/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs index 164d57cb6..575e3e8ba 100644 --- a/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs +++ b/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs @@ -35,6 +35,19 @@ namespace GitHub.DistributedTask.Pipelines.ContextData throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(DictionaryContextData)}' was expected."); } + [EditorBrowsable(EditorBrowsableState.Never)] + public static CaseSensitiveDictionaryContextData AssertCaseSensitiveDictionary( + this PipelineContextData value, + String objectDescription) + { + if (value is CaseSensitiveDictionaryContextData dictionary) + { + return dictionary; + } + + throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(CaseSensitiveDictionaryContextData)}' was expected."); + } + [EditorBrowsable(EditorBrowsableState.Never)] public static StringContextData AssertString( this PipelineContextData value, diff --git a/src/Test/L0/Worker/ActionRunnerL0.cs b/src/Test/L0/Worker/ActionRunnerL0.cs index caa593f65..b0d0c0ff4 100644 --- a/src/Test/L0/Worker/ActionRunnerL0.cs +++ b/src/Test/L0/Worker/ActionRunnerL0.cs @@ -314,6 +314,12 @@ namespace GitHub.Runner.Common.Tests.Worker githubContext.Add("event", JToken.Parse("{\"foo\":\"bar\"}").ToPipelineContextData()); _context.Add("github", githubContext); +#if OS_WINDOWS + _context["env"] = new DictionaryContextData(); +#else + _context["env"] = new CaseSensitiveDictionaryContextData(); +#endif + _ec = new Mock(); _ec.Setup(x => x.ExpressionValues).Returns(_context); _ec.Setup(x => x.IntraActionState).Returns(new Dictionary()); diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs index d9721305e..3ed1e7804 100644 --- a/src/Test/L0/Worker/StepsRunnerL0.cs +++ b/src/Test/L0/Worker/StepsRunnerL0.cs @@ -19,6 +19,7 @@ namespace GitHub.Runner.Common.Tests.Worker private Mock _ec; private StepsRunner _stepsRunner; private Variables _variables; + private Dictionary _env; private DictionaryContextData _contexts; private JobContext _jobContext; private StepsContext _stepContext; @@ -32,6 +33,11 @@ namespace GitHub.Runner.Common.Tests.Worker _variables = new Variables( hostContext: hc, copy: variablesToCopy); + _env = new Dictionary() + { + {"env1", "1"}, + {"test", "github_actions"} + }; _ec = new Mock(); _ec.SetupAllProperties(); _ec.Setup(x => x.Variables).Returns(_variables); @@ -399,18 +405,98 @@ namespace GitHub.Runner.Common.Tests.Worker } } - private Mock CreateStep(TaskResult result, string condition, Boolean continueOnError = false) + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task StepEnvOverrideJobEnvContext() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var env1 = new MappingToken(null, null, null); + env1.Add(new StringToken(null, null, null, "env1"), new StringToken(null, null, null, "100")); + env1.Add(new StringToken(null, null, null, "env2"), new BasicExpressionToken(null, null, null, "env.test")); + var step1 = CreateStep(TaskResult.Succeeded, "success()", env: env1); + + _ec.Object.Result = null; + + _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object })); + + // Act. + await _stepsRunner.RunAsync(jobContext: _ec.Object); + + // Assert. + Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded); + +#if OS_WINDOWS + Assert.Equal("100", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("100")); + Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env2"].AssertString("github_actions")); +#else + Assert.Equal("100", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("100")); + Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env2"].AssertString("github_actions")); +#endif + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task PopulateEnvContextForEachStep() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var env1 = new MappingToken(null, null, null); + env1.Add(new StringToken(null, null, null, "env1"), new StringToken(null, null, null, "100")); + env1.Add(new StringToken(null, null, null, "env2"), new BasicExpressionToken(null, null, null, "env.test")); + var step1 = CreateStep(TaskResult.Succeeded, "success()", env: env1); + + var env2 = new MappingToken(null, null, null); + env2.Add(new StringToken(null, null, null, "env1"), new StringToken(null, null, null, "1000")); + env2.Add(new StringToken(null, null, null, "env3"), new BasicExpressionToken(null, null, null, "env.test")); + var step2 = CreateStep(TaskResult.Succeeded, "success()", env: env2); + + _ec.Object.Result = null; + + _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object, step2.Object })); + + // Act. + await _stepsRunner.RunAsync(jobContext: _ec.Object); + + // Assert. + Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded); +#if OS_WINDOWS + Assert.Equal("1000", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("1000")); + Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env3"].AssertString("github_actions")); + Assert.False(_ec.Object.ExpressionValues["env"].AssertDictionary("env").ContainsKey("env2")); +#else + Assert.Equal("1000", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("1000")); + Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env3"].AssertString("github_actions")); + Assert.False(_ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env").ContainsKey("env2")); +#endif + } + } + + private Mock CreateStep(TaskResult result, string condition, Boolean continueOnError = false, MappingToken env = null) { // Setup the step. - var step = new Mock(); + var step = new Mock(); step.Setup(x => x.Condition).Returns(condition); step.Setup(x => x.ContinueOnError).Returns(new BooleanToken(null, null, null, continueOnError)); step.Setup(x => x.RunAsync()).Returns(Task.CompletedTask); + step.Setup(x => x.Action) + .Returns(new DistributedTask.Pipelines.ActionStep() + { + Name = "Test", + Id = Guid.NewGuid(), + Environment = env + }); // Setup the step execution context. var stepContext = new Mock(); stepContext.SetupAllProperties(); stepContext.Setup(x => x.Variables).Returns(_variables); + stepContext.Setup(x => x.EnvironmentVariables).Returns(_env); stepContext.Setup(x => x.ExpressionValues).Returns(_contexts); stepContext.Setup(x => x.JobContext).Returns(_jobContext); stepContext.Setup(x => x.StepsContext).Returns(_stepContext); @@ -428,7 +514,7 @@ namespace GitHub.Runner.Common.Tests.Worker return step; } - private string FormatSteps(IEnumerable> steps) + private string FormatSteps(IEnumerable> steps) { return String.Join( " ; ",