From 038e5e2c2e99d9e9e5074f1f8f84b323c49308e6 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 18 Jun 2020 09:48:18 -0400 Subject: [PATCH 01/31] Composite Action Run Steps --- src/Runner.Worker/ActionManager.cs | 18 + src/Runner.Worker/ActionManifestManager.cs | 31 +- src/Runner.Worker/ExecutionContext.cs | 65 +++- .../Handlers/CompositeActionHandler.cs | 105 ++++++ src/Runner.Worker/Handlers/HandlerFactory.cs | 8 + src/Runner.Worker/StepsRunner.cs | 17 +- src/Runner.Worker/action_yaml.json | 29 +- .../PipelineTemplateConstants.cs | 2 + .../PipelineTemplateConverter.cs | 347 +++++++++++++++++- .../PipelineTemplateEvaluator.cs | 48 ++- .../Pipelines/PipelineConstants.cs | 7 + 11 files changed, 661 insertions(+), 16 deletions(-) create mode 100644 src/Runner.Worker/Handlers/CompositeActionHandler.cs diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 347eb3d0d03..5b2fa3fda1e 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -395,6 +395,10 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}."); } } + else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + // Don't do anything for now + } else { throw new NotSupportedException(definition.Data.Execution.ExecutionType.ToString()); @@ -1101,6 +1105,11 @@ private ActionContainer PrepareRepositoryActionAsync(IExecutionContext execution Trace.Info($"Action plugin: {(actionDefinitionData.Execution as PluginActionExecutionData).Plugin}, no more preparation."); return null; } + else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + Trace.Info($"Action composite: {(actionDefinitionData.Execution as CompositeActionExecutionData).Steps}, no more preparation."); + return null; + } else { throw new NotSupportedException(actionDefinitionData.Execution.ExecutionType.ToString()); @@ -1211,6 +1220,7 @@ public enum ActionExecutionType NodeJS, Plugin, Script, + Composite } public sealed class ContainerActionExecutionData : ActionExecutionData @@ -1267,6 +1277,14 @@ public sealed class ScriptActionExecutionData : ActionExecutionData public override bool HasPost => false; } + public sealed class CompositeActionExecutionData : ActionExecutionData + { + public override ActionExecutionType ExecutionType => ActionExecutionType.Composite; + public override bool HasPre => false; + public override bool HasPost => false; + public List Steps { get; set; } + } + public abstract class ActionExecutionData { private string _initCondition = $"{Constants.Expressions.Always}()"; diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 4e9149d26b6..b210d79383d 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -14,6 +14,7 @@ using YamlDotNet.Core.Events; using System.Globalization; using System.Linq; +using Pipelines = GitHub.DistributedTask.Pipelines; namespace GitHub.Runner.Worker { @@ -92,7 +93,7 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani break; case "runs": - actionDefinition.Execution = ConvertRuns(context, actionPair.Value); + actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value); break; default: Trace.Info($"Ignore action property {propertyName}."); @@ -284,7 +285,7 @@ private TemplateContext CreateContext( // Add the file table if (_fileTable?.Count > 0) { - for (var i = 0 ; i < _fileTable.Count ; i++) + for (var i = 0; i < _fileTable.Count; i++) { result.GetFileId(_fileTable[i]); } @@ -294,6 +295,7 @@ private TemplateContext CreateContext( } private ActionExecutionData ConvertRuns( + IExecutionContext executionContext, TemplateContext context, TemplateToken inputsToken) { @@ -311,6 +313,8 @@ private ActionExecutionData ConvertRuns( var postToken = default(StringToken); var postEntrypointToken = default(StringToken); var postIfToken = default(StringToken); + var stepsLoaded = default(List); + foreach (var run in runsMapping) { var runsKey = run.Key.AssertString("runs key").Value; @@ -355,6 +359,15 @@ private ActionExecutionData ConvertRuns( case "pre-if": preIfToken = run.Value.AssertString("pre-if"); break; + case "steps": + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + var steps = run.Value.AssertSequence("steps"); + var evaluator = executionContext.ToPipelineTemplateEvaluator(); + stepsLoaded = evaluator.LoadCompositeSteps(steps); + break; + } + throw new Exception("You aren't supposed to be using Composite Actions yet!"); default: Trace.Info($"Ignore run property {runsKey}."); break; @@ -402,6 +415,20 @@ private ActionExecutionData ConvertRuns( }; } } + else if (string.Equals(usingToken.Value, "composite", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + if (stepsLoaded == null) + { + throw new ArgumentNullException($"No steps provided."); + } + else + { + return new CompositeActionExecutionData() + { + Steps = stepsLoaded, + }; + } + } else { throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead."); diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 0318974b01d..a76ca3cd019 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -105,6 +105,8 @@ public interface IExecutionContext : IRunnerService // others void ForceTaskComplete(); void RegisterPostJobStep(IStep step); + IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData); + void EnqueueAllCompositeSteps(Queue steps); } public sealed class ExecutionContext : RunnerService, IExecutionContext @@ -169,7 +171,6 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext public bool EchoOnActionCommand { get; set; } - public TaskResult? Result { get @@ -266,6 +267,68 @@ public void RegisterPostJobStep(IStep step) Root.PostJobSteps.Push(step); } + /* + RegisterCompositeStep is a helper function used in CompositeActionHandler::RunAsync to + add a child node, aka a step, to the current job to the front of the queue for processing. + */ + public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData) + { + // ~Brute Force Method~ + // There is no way to put this current job in front of the queue in < O(n) time where n = # of elements in JobSteps + // Everytime we add a new step, you could requeue every item to put those steps from that stack in JobSteps which + // would result in O(n) for each time we add a composite action step where n = number of jobSteps which would compound + // to O(n*m) where m = number of composite steps + // var temp = Root.JobSteps.ToArray(); + // Root.JobSteps.Clear(); + // Root.JobSteps.Enqueue(step); + // foreach(var s in temp) + // Root.JobSteps.Enqueue(s); + + // ~Optimized Method~ + // Alterative solution: We add to another temp Queue + // After we add all the transformed composite steps to this temp queue, we requeue the whole JobSteps accordingly in EnqueueAllCompositeSteps() + // where the queued composite steps are at the front of the JobSteps Queue and the rest of the jobs maintain its order and are + // placed after the queued composite steps + // This will take only O(n+m) time which would be just as efficient if we refactored the code of JobSteps to a PriorityQueue + // This temp Queue is created in the CompositeActionHandler. + + Trace.Info("Adding Composite Action Step"); + var newGuid = Guid.NewGuid(); + step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); + step.ExecutionContext.ExpressionValues["inputs"] = inputsData; + return step; + } + + // Add Composite Steps first and then requeue the rest of the job steps. + public void EnqueueAllCompositeSteps(Queue steps) + { + // TODO: For UI purposes, look at figuring out how to condense steps in one node + // maybe use "this" instead of "Root"? + if (Root.JobSteps != null) + { + var temp = Root.JobSteps.ToArray(); + Root.JobSteps.Clear(); + foreach (var cs in steps) + { + Trace.Info($"EnqueueAllCompositeSteps : Adding Composite action step {cs}"); + Root.JobSteps.Enqueue(cs); + } + foreach (var s in temp) + { + Root.JobSteps.Enqueue(s); + } + } + else + { + Root.JobSteps = new Queue(); + foreach (var cs in steps) + { + Trace.Info($"EnqueueAllCompositeSteps : Adding Composite action step {cs}"); + Root.JobSteps.Enqueue(cs); + } + } + } + public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) { Trace.Entering(); diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs new file mode 100644 index 00000000000..34f12270b73 --- /dev/null +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -0,0 +1,105 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; +using GitHub.Runner.Common; +using GitHub.Runner.Sdk; +using GitHub.DistributedTask.WebApi; +using Pipelines = GitHub.DistributedTask.Pipelines; +using System; +using System.Linq; +using GitHub.DistributedTask.ObjectTemplating.Tokens; +using System.Collections.Generic; +using GitHub.DistributedTask.Pipelines.ContextData; + +namespace GitHub.Runner.Worker.Handlers +{ + [ServiceLocator(Default = typeof(CompositeActionHandler))] + public interface ICompositeActionHandler : IHandler + { + CompositeActionExecutionData Data { get; set; } + } + public sealed class CompositeActionHandler : Handler, ICompositeActionHandler + { + public CompositeActionExecutionData Data { get; set; } + + public Task RunAsync(ActionRunStage stage) + { + // Validate args. + Trace.Entering(); + ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); + ArgUtil.NotNull(Inputs, nameof(Inputs)); + + var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext; + ArgUtil.NotNull(githubContext, nameof(githubContext)); + + var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); + + // Resolve action steps + var actionSteps = Data.Steps; + if (actionSteps == null) + { + Trace.Error("Data.Steps in CompositeActionHandler is null"); + } + else + { + Trace.Info($"Data Steps Value for Composite Actions is: {actionSteps}."); + } + + // Create Context Data to reuse for each composite action step + var inputsData = new DictionaryContextData(); + foreach (var i in Inputs) + { + inputsData[i.Key] = new StringContextData(i.Value); + } + + // Add each composite action step to the front of the queue + var compositeActionSteps = new Queue(); + foreach (Pipelines.ActionStep aStep in actionSteps) + { + // Ex: + // runs: + // using: "composite" + // steps: + // - uses: example/test-composite@v2 (a) + // - run echo hello world (b) + // - run echo hello world 2 (c) + // + // ethanchewy/test-composite/action.yaml + // runs: + // using: "composite" + // steps: + // - run echo hello world 3 (d) + // - run echo hello world 4 (e) + // + // Stack (LIFO) [Bottom => Middle => Top]: + // | a | + // | a | => | d | + // (Run step d) + // | a | + // | a | => | e | + // (Run step e) + // | a | + // (Run step a) + // | b | + // (Run step b) + // | c | + // (Run step c) + // Done. + + var actionRunner = HostContext.CreateService(); + actionRunner.Action = aStep; + actionRunner.Stage = stage; + actionRunner.Condition = aStep.Condition; + actionRunner.DisplayName = aStep.DisplayName; + // TODO: Do we need to add any context data from the job message? + // (See JobExtension.cs ~line 236) + + compositeActionSteps.Enqueue(ExecutionContext.RegisterCompositeStep(actionRunner, inputsData)); + } + ExecutionContext.EnqueueAllCompositeSteps(compositeActionSteps); + + return Task.CompletedTask; + } + + } +} diff --git a/src/Runner.Worker/Handlers/HandlerFactory.cs b/src/Runner.Worker/Handlers/HandlerFactory.cs index 0f2413ef5b7..99311afca5f 100644 --- a/src/Runner.Worker/Handlers/HandlerFactory.cs +++ b/src/Runner.Worker/Handlers/HandlerFactory.cs @@ -66,6 +66,14 @@ public IHandler Create( handler = HostContext.CreateService(); (handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData; } + else if (data.ExecutionType == ActionExecutionType.Composite) + { + // TODO + // Runner plugin + handler = HostContext.CreateService(); + // handler = CompositeHandler; + (handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData; + } else { // This should never happen. diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index 485a4cdf980..6a0e2b6946f 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -67,6 +67,11 @@ public async Task RunAsync(IExecutionContext jobContext) var step = jobContext.JobSteps.Dequeue(); var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null; + // TODO: Fix this temporary workaround for Composite Actions + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + nextStep = null; + } Trace.Info($"Processing step: DisplayName='{step.DisplayName}'"); ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext)); @@ -409,7 +414,11 @@ private bool InitializeScope(IStep step, Dictionary scope = scopesToInitialize.Pop(); executionContext.Debug($"Initializing scope '{scope.Name}'"); executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName); - executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null; + // TODO: Fix this temporary workaround for Composite Actions + if (!executionContext.ExpressionValues.ContainsKey("inputs") && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null; + } var templateEvaluator = executionContext.ToPipelineTemplateEvaluator(); var inputs = default(DictionaryContextData); try @@ -432,7 +441,11 @@ private bool InitializeScope(IStep step, Dictionary // Setup expression values var scopeName = executionContext.ScopeName; executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scopeName); - executionContext.ExpressionValues["inputs"] = string.IsNullOrEmpty(scopeName) ? null : scopeInputs[scopeName]; + // TODO: Fix this temporary workaround for Composite Actions + if (!executionContext.ExpressionValues.ContainsKey("inputs") && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + executionContext.ExpressionValues["inputs"] = string.IsNullOrEmpty(scopeName) ? null : scopeInputs[scopeName]; + } return true; } diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index 7a8b847d31f..da8fb79a962 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -32,7 +32,8 @@ "one-of": [ "container-runs", "node12-runs", - "plugin-runs" + "plugin-runs", + "composite-runs" ] }, "container-runs": { @@ -83,6 +84,32 @@ } } }, + "composite-runs": { + "mapping": { + "properties": { + "using": "non-empty-string", + "steps": "composite-steps" + } + } + }, + "composite-steps": { + "context": [ + "github", + "needs", + "strategy", + "matrix", + "secrets", + "steps", + "inputs", + "job", + "runner", + "env", + "hashFiles(1,255)" + ], + "sequence": { + "item-type": "any" + } + }, "container-runs-context": { "context": [ "inputs" diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs index d1c886dd891..bf7d8d2e971 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs @@ -65,6 +65,8 @@ public sealed class PipelineTemplateConstants public const String StepEnv = "step-env"; public const String StepIfResult = "step-if-result"; public const String Steps = "steps"; + + public const String StepsInTemplate = "steps-in-template"; public const String StepsScopeInputs = "steps-scope-inputs"; public const String StepsScopeOutputs = "steps-scope-outputs"; public const String StepsTemplateRoot = "steps-template-root"; diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs index 43be43d3375..a952f58fbb9 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs @@ -29,7 +29,6 @@ internal static Boolean ConvertToIfResult( var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult); return evaluationResult.IsTruthy; } - internal static Boolean? ConvertToStepContinueOnError( TemplateContext context, TemplateToken token, @@ -264,5 +263,351 @@ internal static List> ConvertToJobServiceCont return result; } + + //Note: originally was List but we need to change to List to use the "Inputs" attribute + internal static List ConvertToSteps( + TemplateContext context, + TemplateToken steps) + { + var stepsSequence = steps.AssertSequence($"job {PipelineTemplateConstants.Steps}"); + + var result = new List(); + foreach (var stepsItem in stepsSequence) + { + var step = ConvertToStep(context, stepsItem); + if (step != null) // step = null means we are hitting error during step conversion, there should be an error in context.errors + { + if (step.Enabled) + { + result.Add(step); + } + } + } + + return result; + } + + private static ActionStep ConvertToStep( + TemplateContext context, + TemplateToken stepsItem) + { + var step = stepsItem.AssertMapping($"{PipelineTemplateConstants.Steps} item"); + var continueOnError = default(ScalarToken); + var env = default(TemplateToken); + var id = default(StringToken); + var ifCondition = default(String); + var ifToken = default(ScalarToken); + var name = default(ScalarToken); + var run = default(ScalarToken); + var scope = default(StringToken); + var timeoutMinutes = default(ScalarToken); + var uses = default(StringToken); + var with = default(TemplateToken); + var workingDir = default(ScalarToken); + var path = default(ScalarToken); + var clean = default(ScalarToken); + var fetchDepth = default(ScalarToken); + var lfs = default(ScalarToken); + var submodules = default(ScalarToken); + var shell = default(ScalarToken); + + foreach (var stepProperty in step) + { + var propertyName = stepProperty.Key.AssertString($"{PipelineTemplateConstants.Steps} item key"); + + switch (propertyName.Value) + { + case PipelineTemplateConstants.Clean: + clean = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Clean}"); + break; + + case PipelineTemplateConstants.ContinueOnError: + ConvertToStepContinueOnError(context, stepProperty.Value, allowExpressions: true); // Validate early if possible + continueOnError = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} {PipelineTemplateConstants.ContinueOnError}"); + break; + + case PipelineTemplateConstants.Env: + ConvertToStepEnvironment(context, stepProperty.Value, StringComparer.Ordinal, allowExpressions: true); // Validate early if possible + env = stepProperty.Value; + break; + + case PipelineTemplateConstants.FetchDepth: + fetchDepth = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.FetchDepth}"); + break; + + case PipelineTemplateConstants.Id: + id = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Id}"); + if (!NameValidation.IsValid(id.Value, true)) + { + context.Error(id, $"Step id {id.Value} is invalid. Ids must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'"); + } + break; + + case PipelineTemplateConstants.If: + ifToken = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.If}"); + break; + + case PipelineTemplateConstants.Lfs: + lfs = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Lfs}"); + break; + + case PipelineTemplateConstants.Name: + name = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Name}"); + break; + + case PipelineTemplateConstants.Path: + path = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Path}"); + break; + + case PipelineTemplateConstants.Run: + run = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Run}"); + break; + + case PipelineTemplateConstants.Shell: + shell = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Shell}"); + break; + + case PipelineTemplateConstants.Scope: + scope = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Scope}"); + break; + + case PipelineTemplateConstants.Submodules: + submodules = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Submodules}"); + break; + + case PipelineTemplateConstants.TimeoutMinutes: + ConvertToStepTimeout(context, stepProperty.Value, allowExpressions: true); // Validate early if possible + timeoutMinutes = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.TimeoutMinutes}"); + break; + + case PipelineTemplateConstants.Uses: + uses = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Uses}"); + break; + + case PipelineTemplateConstants.With: + ConvertToStepInputs(context, stepProperty.Value, allowExpressions: true); // Validate early if possible + with = stepProperty.Value; + break; + + case PipelineTemplateConstants.WorkingDirectory: + workingDir = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.WorkingDirectory}"); + break; + + default: + propertyName.AssertUnexpectedValue($"{PipelineTemplateConstants.Steps} item key"); // throws + break; + } + } + + // Fixup the if-condition + var isDefaultScope = String.IsNullOrEmpty(scope?.Value); + ifCondition = ConvertToIfCondition(context, ifToken, false, isDefaultScope); + + if (run != null) + { + var result = new ActionStep + { + ScopeName = scope?.Value, + ContextName = id?.Value, + ContinueOnError = continueOnError, + DisplayNameToken = name, + Condition = ifCondition, + TimeoutInMinutes = timeoutMinutes, + Environment = env, + Reference = new ScriptReference(), + }; + + var inputs = new MappingToken(null, null, null); + inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.Script), run); + + if (workingDir != null) + { + inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.WorkingDirectory), workingDir); + } + + if (shell != null) + { + inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.Shell), shell); + } + + result.Inputs = inputs; + + return result; + } + else + { + uses.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Uses}"); + var result = new ActionStep + { + ScopeName = scope?.Value, + ContextName = id?.Value, + ContinueOnError = continueOnError, + DisplayNameToken = name, + Condition = ifCondition, + TimeoutInMinutes = timeoutMinutes, + Inputs = with, + Environment = env, + }; + + if (uses.Value.StartsWith("docker://", StringComparison.Ordinal)) + { + var image = uses.Value.Substring("docker://".Length); + result.Reference = new ContainerRegistryReference { Image = image }; + } + else if (uses.Value.StartsWith("./") || uses.Value.StartsWith(".\\")) + { + result.Reference = new RepositoryPathReference + { + RepositoryType = PipelineConstants.SelfAlias, + Path = uses.Value + }; + } + else + { + var usesSegments = uses.Value.Split('@'); + var pathSegments = usesSegments[0].Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + var gitRef = usesSegments.Length == 2 ? usesSegments[1] : String.Empty; + + if (usesSegments.Length != 2 || + pathSegments.Length < 2 || + String.IsNullOrEmpty(pathSegments[0]) || + String.IsNullOrEmpty(pathSegments[1]) || + String.IsNullOrEmpty(gitRef)) + { + // todo: loc + context.Error(uses, $"Expected format {{org}}/{{repo}}[/path]@ref. Actual '{uses.Value}'"); + } + else + { + var repositoryName = $"{pathSegments[0]}/{pathSegments[1]}"; + var directoryPath = pathSegments.Length > 2 ? String.Join("/", pathSegments.Skip(2)) : String.Empty; + + result.Reference = new RepositoryPathReference + { + RepositoryType = RepositoryTypes.GitHub, + Name = repositoryName, + Ref = gitRef, + Path = directoryPath, + }; + } + } + + return result; + } + } + + /// + /// When empty, default to "success()". + /// When a status function is not referenced, format as "success() && <CONDITION>". + /// + private static String ConvertToIfCondition( + TemplateContext context, + TemplateToken token, + Boolean isJob, + Boolean isDefaultScope) + { + String condition; + if (token is null) + { + condition = null; + } + else if (token is BasicExpressionToken expressionToken) + { + condition = expressionToken.Expression; + } + else + { + var stringToken = token.AssertString($"{(isJob ? "job" : "step")} {PipelineTemplateConstants.If}"); + condition = stringToken.Value; + } + + if (String.IsNullOrWhiteSpace(condition)) + { + return $"{PipelineTemplateConstants.Success}()"; + } + + var expressionParser = new ExpressionParser(); + var functions = default(IFunctionInfo[]); + var namedValues = default(INamedValueInfo[]); + if (isJob) + { + namedValues = s_jobIfNamedValues; + // TODO: refactor into seperate functions + // functions = PhaseCondition.FunctionInfo; + } + else + { + namedValues = isDefaultScope ? s_stepNamedValues : s_stepInTemplateNamedValues; + functions = s_stepConditionFunctions; + } + + var node = default(ExpressionNode); + try + { + node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode; + } + catch (Exception ex) + { + context.Error(token, ex); + return null; + } + + if (node == null) + { + return $"{PipelineTemplateConstants.Success}()"; + } + + var hasStatusFunction = node.Traverse().Any(x => + { + if (x is Function function) + { + return String.Equals(function.Name, PipelineTemplateConstants.Always, StringComparison.OrdinalIgnoreCase) || + String.Equals(function.Name, PipelineTemplateConstants.Cancelled, StringComparison.OrdinalIgnoreCase) || + String.Equals(function.Name, PipelineTemplateConstants.Failure, StringComparison.OrdinalIgnoreCase) || + String.Equals(function.Name, PipelineTemplateConstants.Success, StringComparison.OrdinalIgnoreCase); + } + + return false; + }); + + return hasStatusFunction ? condition : $"{PipelineTemplateConstants.Success}() && ({condition})"; + } + + private static readonly INamedValueInfo[] s_jobIfNamedValues = new INamedValueInfo[] + { + new NamedValueInfo(PipelineTemplateConstants.GitHub), + new NamedValueInfo(PipelineTemplateConstants.Needs), + }; + private static readonly INamedValueInfo[] s_stepNamedValues = new INamedValueInfo[] + { + new NamedValueInfo(PipelineTemplateConstants.Strategy), + new NamedValueInfo(PipelineTemplateConstants.Matrix), + new NamedValueInfo(PipelineTemplateConstants.Steps), + new NamedValueInfo(PipelineTemplateConstants.GitHub), + new NamedValueInfo(PipelineTemplateConstants.Job), + new NamedValueInfo(PipelineTemplateConstants.Runner), + new NamedValueInfo(PipelineTemplateConstants.Env), + new NamedValueInfo(PipelineTemplateConstants.Needs), + }; + private static readonly INamedValueInfo[] s_stepInTemplateNamedValues = new INamedValueInfo[] + { + new NamedValueInfo(PipelineTemplateConstants.Strategy), + new NamedValueInfo(PipelineTemplateConstants.Matrix), + new NamedValueInfo(PipelineTemplateConstants.Steps), + new NamedValueInfo(PipelineTemplateConstants.Inputs), + new NamedValueInfo(PipelineTemplateConstants.GitHub), + new NamedValueInfo(PipelineTemplateConstants.Job), + new NamedValueInfo(PipelineTemplateConstants.Runner), + new NamedValueInfo(PipelineTemplateConstants.Env), + new NamedValueInfo(PipelineTemplateConstants.Needs), + }; + private static readonly IFunctionInfo[] s_stepConditionFunctions = new IFunctionInfo[] + { + new FunctionInfo(PipelineTemplateConstants.Always, 0, 0), + new FunctionInfo(PipelineTemplateConstants.Cancelled, 0, 0), + new FunctionInfo(PipelineTemplateConstants.Failure, 0, 0), + new FunctionInfo(PipelineTemplateConstants.Success, 0, 0), + new FunctionInfo(PipelineTemplateConstants.HashFiles, 1, Byte.MaxValue), + }; } } diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs index a36f5b7e3aa..55076e670e5 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs @@ -159,6 +159,32 @@ public String EvaluateStepDisplayName( return result; } + public List LoadCompositeSteps( + TemplateToken token + ) + { + var result = default(List); + if (token != null && token.Type != TokenType.Null) + { + var context = CreateContext(null, null, setMissingContext: false); + // TODO: we might want to to have a bool to prevent it from filling in with missing context w/ dummy variables + try + { + token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsInTemplate, token, 0, null, omitHeader: true); + context.Errors.Check(); + result = PipelineTemplateConverter.ConvertToSteps(context, token); + } + catch (Exception ex) when (!(ex is TemplateValidationException)) + { + context.Errors.Add(ex); + } + + context.Errors.Check(); + } + return result; + } + + public Dictionary EvaluateStepEnvironment( TemplateToken token, DictionaryContextData contextData, @@ -400,7 +426,8 @@ public IList> EvaluateJobServiceContainers( private TemplateContext CreateContext( DictionaryContextData contextData, IList expressionFunctions, - IEnumerable> expressionState = null) + IEnumerable> expressionState = null, + bool setMissingContext = true) { var result = new TemplateContext { @@ -449,18 +476,21 @@ private TemplateContext CreateContext( // - Evaluating early when all referenced contexts are available, even though all allowed // contexts may not yet be available. For example, evaluating step display name can often // be performed early. - foreach (var name in s_expressionValueNames) + if (setMissingContext) { - if (!result.ExpressionValues.ContainsKey(name)) + foreach (var name in s_expressionValueNames) { - result.ExpressionValues[name] = null; + if (!result.ExpressionValues.ContainsKey(name)) + { + result.ExpressionValues[name] = null; + } } - } - foreach (var name in s_expressionFunctionNames) - { - if (!functionNames.Contains(name)) + foreach (var name in s_expressionFunctionNames) { - result.ExpressionFunctions.Add(new FunctionInfo(name, 0, Int32.MaxValue)); + if (!functionNames.Contains(name)) + { + result.ExpressionFunctions.Add(new FunctionInfo(name, 0, Int32.MaxValue)); + } } } diff --git a/src/Sdk/DTPipelines/Pipelines/PipelineConstants.cs b/src/Sdk/DTPipelines/Pipelines/PipelineConstants.cs index 2d599dd9c55..2e03671fbb2 100644 --- a/src/Sdk/DTPipelines/Pipelines/PipelineConstants.cs +++ b/src/Sdk/DTPipelines/Pipelines/PipelineConstants.cs @@ -94,5 +94,12 @@ public static class WorkspaceCleanOptions public static readonly String Resources = "resources"; public static readonly String All = "all"; } + + public static class ScriptStepInputs + { + public static readonly String Script = "script"; + public static readonly String WorkingDirectory = "workingDirectory"; + public static readonly String Shell = "shell"; + } } } From e56b2439b90f51309f71c02ef273b67c662044e6 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 18 Jun 2020 15:34:48 -0400 Subject: [PATCH 02/31] Env Flow => Able to get env variables and overwrite current env variables => but it doesn't 'stick' --- src/Runner.Worker/ActionManager.cs | 1 + src/Runner.Worker/ActionManifestManager.cs | 61 +++++++++++++++++++ src/Runner.Worker/ExecutionContext.cs | 14 ++++- .../Handlers/CompositeActionHandler.cs | 28 ++++++++- src/Runner.Worker/Handlers/ScriptHandler.cs | 8 +++ src/Runner.Worker/StepsRunner.cs | 1 + src/Runner.Worker/action_yaml.json | 1 + 7 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 5b2fa3fda1e..b34082c8697 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -1283,6 +1283,7 @@ public sealed class CompositeActionExecutionData : ActionExecutionData public override bool HasPre => false; public override bool HasPost => false; public List Steps { get; set; } + public MappingToken Environment { get; set; } } public abstract class ActionExecutionData diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index b210d79383d..eac9e1ea145 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -27,6 +27,7 @@ public interface IActionManifestManager : IRunnerService Dictionary EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary extraExpressionValues); + public Dictionary EvaluateCompositeActionEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary extraExpressionValues); string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token); } @@ -214,6 +215,65 @@ public Dictionary EvaluateContainerEnvironment( return result; } + // TODO: Add Evaluate Composite Action Env Function Here + // Steps will be evaluated already in StepsRunner + public Dictionary EvaluateCompositeActionEnvironment( + IExecutionContext executionContext, + MappingToken token, + IDictionary extraExpressionValues) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (token != null) + { + var context = CreateContext(executionContext, extraExpressionValues); + try + { + var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); + context.Errors.Check(); + if (evaluateResult != null) { + Trace.Info($"EvaluateCompositeActionEnvironment evaluateResult {evaluateResult}"); + } else { + Trace.Info($"EvaluateCompositeActionEnvironment evaluateResult is null"); + } + + + Trace.Info($"Composite Action Environments evaluate result: {StringUtil.ConvertToJson(evaluateResult)}"); + + // Mapping + var mapping = evaluateResult.AssertMapping("composite env"); + if (mapping != null) { + Trace.Info($"EvaluateCompositeActionEnvironment Mapping {mapping}"); + } else { + Trace.Info($"EvaluateCompositeActionEnvironment Mapping is null"); + } + + foreach (var pair in mapping) + { + // Literal key + var key = pair.Key.AssertString("composite env key"); + Trace.Info($"EvaluateCompositeActionEnvironment Key{key.Value}"); + + // Literal value + var value = pair.Value.AssertString("composite env value"); + Trace.Info($"EvaluateCompositeActionEnvironment Value{value.Value}"); + result[key.Value] = value.Value; + + Trace.Info($"Add env {key} = {value}"); + } + } + catch (Exception ex) when (!(ex is TemplateValidationException)) + { + Trace.Error(ex); + context.Errors.Add(ex); + } + + context.Errors.Check(); + } + + return result; + + } public string EvaluateDefaultInput( IExecutionContext executionContext, string inputName, @@ -426,6 +486,7 @@ private ActionExecutionData ConvertRuns( return new CompositeActionExecutionData() { Steps = stepsLoaded, + Environment = envToken }; } } diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index a76ca3cd019..9cd7e6dbb8f 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -105,8 +105,9 @@ public interface IExecutionContext : IRunnerService // others void ForceTaskComplete(); void RegisterPostJobStep(IStep step); - IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData); + IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, PipelineContextData envData); void EnqueueAllCompositeSteps(Queue steps); + ExecutionContext getParentExecutionContext(); } public sealed class ExecutionContext : RunnerService, IExecutionContext @@ -271,7 +272,7 @@ public void RegisterPostJobStep(IStep step) RegisterCompositeStep is a helper function used in CompositeActionHandler::RunAsync to add a child node, aka a step, to the current job to the front of the queue for processing. */ - public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData) + public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, PipelineContextData envData) { // ~Brute Force Method~ // There is no way to put this current job in front of the queue in < O(n) time where n = # of elements in JobSteps @@ -296,6 +297,10 @@ public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData) var newGuid = Guid.NewGuid(); step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; + step.ExecutionContext.ExpressionValues["env"] = envData; + + // TODO add environment variables for individual composite action steps + return step; } @@ -329,6 +334,11 @@ public void EnqueueAllCompositeSteps(Queue steps) } } + public ExecutionContext getParentExecutionContext() + { + return _parentExecutionContext; + } + public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) { Trace.Entering(); diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 34f12270b73..35ae41bb4d9 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -52,6 +52,32 @@ public Task RunAsync(ActionRunStage stage) inputsData[i.Key] = new StringContextData(i.Value); } + // Set up parent's environment data and then add on composite action environment data +#if OS_WINDOWS + var envData = ExecutionContext.ExpressionValues["env"].Clone() as DictionaryContextData; +#else + var envData = ExecutionContext.ExpressionValues["env"].Clone() as CaseSensitiveDictionaryContextData; +#endif + // Composite action will have already inherited the root env attributes. + // We evaluated the env simimilar to how ContainerActionHandler does it. + if (Data.Environment == null) { + Trace.Info($"Composite Env Mapping Token is null"); + } else { + Trace.Info($"Composite Env Mapping Token {Data.Environment}"); + } + var extraExpressionValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + extraExpressionValues["inputs"] = inputsData; + var manifestManager = HostContext.GetService(); + var evaluatedEnv = manifestManager.EvaluateCompositeActionEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); + foreach (var e in evaluatedEnv) + { + // How to add to EnvironmentContextData + // We need to use IEnvironmentContextData because ScriptHandler uses this type for environment variables + Trace.Info($"Composite Action Env Key: {e.Key}"); + Trace.Info($"Composite Action Env Value: {e.Value}"); + envData[e.Key] = new StringContextData(e.Value); + } + // Add each composite action step to the front of the queue var compositeActionSteps = new Queue(); foreach (Pipelines.ActionStep aStep in actionSteps) @@ -94,7 +120,7 @@ public Task RunAsync(ActionRunStage stage) // TODO: Do we need to add any context data from the job message? // (See JobExtension.cs ~line 236) - compositeActionSteps.Enqueue(ExecutionContext.RegisterCompositeStep(actionRunner, inputsData)); + compositeActionSteps.Enqueue(ExecutionContext.RegisterCompositeStep(actionRunner, inputsData, envData)); } ExecutionContext.EnqueueAllCompositeSteps(compositeActionSteps); diff --git a/src/Runner.Worker/Handlers/ScriptHandler.cs b/src/Runner.Worker/Handlers/ScriptHandler.cs index 051cd5fc78e..b93c40de8da 100644 --- a/src/Runner.Worker/Handlers/ScriptHandler.cs +++ b/src/Runner.Worker/Handlers/ScriptHandler.cs @@ -255,6 +255,14 @@ public async Task RunAsync(ActionRunStage stage) Environment[env.Key] = env.Value; } } + + // if (context.Key == "env" && context.Value != null && !String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + // { + // foreach (var env in context.Value as DictionaryContextData) + // { + // Environment[env.Key] = env.Value as StringContextData; + // } + // } } // dump out the command diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index 6a0e2b6946f..a1f0bbaa805 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -90,6 +90,7 @@ public async Task RunAsync(IExecutionContext jobContext) // Initialize scope if (InitializeScope(step, scopeInputs)) { + // TODO Check if this adds env context to each composite action step // Populate env context for each step Trace.Info("Initialize Env context for step"); #if OS_WINDOWS diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index da8fb79a962..f789edaf8ed 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -88,6 +88,7 @@ "mapping": { "properties": { "using": "non-empty-string", + "env": "container-runs-env", "steps": "composite-steps" } } From 65522633690ca61ea3460d119d9d316379cd32a5 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 18 Jun 2020 15:57:23 -0400 Subject: [PATCH 03/31] clean up --- src/Runner.Worker/ActionManifestManager.cs | 21 ++---------- src/Runner.Worker/ExecutionContext.cs | 21 ++++++------ .../Handlers/CompositeActionHandler.cs | 33 ++----------------- src/Runner.Worker/Handlers/ScriptHandler.cs | 8 ----- src/Runner.Worker/action_yaml.json | 6 ++-- 5 files changed, 18 insertions(+), 71 deletions(-) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index eac9e1ea145..9abd058447a 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -183,7 +183,7 @@ public Dictionary EvaluateContainerEnvironment( var context = CreateContext(executionContext, extraExpressionValues); try { - var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); + var evaluateResult = TemplateEvaluator.Evaluate(context, "runs-env", token, 0, null, omitHeader: true); context.Errors.Check(); Trace.Info($"Environments evaluate result: {StringUtil.ConvertToJson(evaluateResult)}"); @@ -215,8 +215,6 @@ public Dictionary EvaluateContainerEnvironment( return result; } - // TODO: Add Evaluate Composite Action Env Function Here - // Steps will be evaluated already in StepsRunner public Dictionary EvaluateCompositeActionEnvironment( IExecutionContext executionContext, MappingToken token, @@ -229,34 +227,19 @@ public Dictionary EvaluateCompositeActionEnvironment( var context = CreateContext(executionContext, extraExpressionValues); try { - var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); + var evaluateResult = TemplateEvaluator.Evaluate(context, "runs-env", token, 0, null, omitHeader: true); context.Errors.Check(); - if (evaluateResult != null) { - Trace.Info($"EvaluateCompositeActionEnvironment evaluateResult {evaluateResult}"); - } else { - Trace.Info($"EvaluateCompositeActionEnvironment evaluateResult is null"); - } - - - Trace.Info($"Composite Action Environments evaluate result: {StringUtil.ConvertToJson(evaluateResult)}"); // Mapping var mapping = evaluateResult.AssertMapping("composite env"); - if (mapping != null) { - Trace.Info($"EvaluateCompositeActionEnvironment Mapping {mapping}"); - } else { - Trace.Info($"EvaluateCompositeActionEnvironment Mapping is null"); - } foreach (var pair in mapping) { // Literal key var key = pair.Key.AssertString("composite env key"); - Trace.Info($"EvaluateCompositeActionEnvironment Key{key.Value}"); // Literal value var value = pair.Value.AssertString("composite env value"); - Trace.Info($"EvaluateCompositeActionEnvironment Value{value.Value}"); result[key.Value] = value.Value; Trace.Info($"Add env {key} = {value}"); diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 9cd7e6dbb8f..a8ee9c9b0b4 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -105,9 +105,8 @@ public interface IExecutionContext : IRunnerService // others void ForceTaskComplete(); void RegisterPostJobStep(IStep step); - IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, PipelineContextData envData); + IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, Dictionary envData); void EnqueueAllCompositeSteps(Queue steps); - ExecutionContext getParentExecutionContext(); } public sealed class ExecutionContext : RunnerService, IExecutionContext @@ -272,7 +271,7 @@ public void RegisterPostJobStep(IStep step) RegisterCompositeStep is a helper function used in CompositeActionHandler::RunAsync to add a child node, aka a step, to the current job to the front of the queue for processing. */ - public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, PipelineContextData envData) + public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, Dictionary envData) { // ~Brute Force Method~ // There is no way to put this current job in front of the queue in < O(n) time where n = # of elements in JobSteps @@ -297,10 +296,15 @@ public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, var newGuid = Guid.NewGuid(); step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; - step.ExecutionContext.ExpressionValues["env"] = envData; - // TODO add environment variables for individual composite action steps - + // Add the composite action environment variables to each step. + // If the key already exists, we override it since the composite action env variables will have higher precedence + // Note that for each composite action step, it's environment variables will be set in the StepRunner automatically + foreach (var e in envData) + { + step.ExecutionContext.EnvironmentVariables[e.Key] = e.Value; + } + return step; } @@ -334,11 +338,6 @@ public void EnqueueAllCompositeSteps(Queue steps) } } - public ExecutionContext getParentExecutionContext() - { - return _parentExecutionContext; - } - public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) { Trace.Entering(); diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 35ae41bb4d9..0902fafae44 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -36,14 +36,6 @@ public Task RunAsync(ActionRunStage stage) // Resolve action steps var actionSteps = Data.Steps; - if (actionSteps == null) - { - Trace.Error("Data.Steps in CompositeActionHandler is null"); - } - else - { - Trace.Info($"Data Steps Value for Composite Actions is: {actionSteps}."); - } // Create Context Data to reuse for each composite action step var inputsData = new DictionaryContextData(); @@ -52,31 +44,12 @@ public Task RunAsync(ActionRunStage stage) inputsData[i.Key] = new StringContextData(i.Value); } - // Set up parent's environment data and then add on composite action environment data -#if OS_WINDOWS - var envData = ExecutionContext.ExpressionValues["env"].Clone() as DictionaryContextData; -#else - var envData = ExecutionContext.ExpressionValues["env"].Clone() as CaseSensitiveDictionaryContextData; -#endif - // Composite action will have already inherited the root env attributes. - // We evaluated the env simimilar to how ContainerActionHandler does it. - if (Data.Environment == null) { - Trace.Info($"Composite Env Mapping Token is null"); - } else { - Trace.Info($"Composite Env Mapping Token {Data.Environment}"); - } + + // Get Environment Data for Composite Action var extraExpressionValues = new Dictionary(StringComparer.OrdinalIgnoreCase); extraExpressionValues["inputs"] = inputsData; var manifestManager = HostContext.GetService(); - var evaluatedEnv = manifestManager.EvaluateCompositeActionEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); - foreach (var e in evaluatedEnv) - { - // How to add to EnvironmentContextData - // We need to use IEnvironmentContextData because ScriptHandler uses this type for environment variables - Trace.Info($"Composite Action Env Key: {e.Key}"); - Trace.Info($"Composite Action Env Value: {e.Value}"); - envData[e.Key] = new StringContextData(e.Value); - } + var envData = manifestManager.EvaluateCompositeActionEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); // Add each composite action step to the front of the queue var compositeActionSteps = new Queue(); diff --git a/src/Runner.Worker/Handlers/ScriptHandler.cs b/src/Runner.Worker/Handlers/ScriptHandler.cs index b93c40de8da..051cd5fc78e 100644 --- a/src/Runner.Worker/Handlers/ScriptHandler.cs +++ b/src/Runner.Worker/Handlers/ScriptHandler.cs @@ -255,14 +255,6 @@ public async Task RunAsync(ActionRunStage stage) Environment[env.Key] = env.Value; } } - - // if (context.Key == "env" && context.Value != null && !String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) - // { - // foreach (var env in context.Value as DictionaryContextData) - // { - // Environment[env.Key] = env.Value as StringContextData; - // } - // } } // dump out the command diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index f789edaf8ed..da937f2a919 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -43,7 +43,7 @@ "image": "non-empty-string", "entrypoint": "non-empty-string", "args": "container-runs-args", - "env": "container-runs-env", + "env": "runs-env", "pre-entrypoint": "non-empty-string", "pre-if": "non-empty-string", "post-entrypoint": "non-empty-string", @@ -56,7 +56,7 @@ "item-type": "container-runs-context" } }, - "container-runs-env": { + "runs-env": { "context": [ "inputs" ], @@ -88,7 +88,7 @@ "mapping": { "properties": { "using": "non-empty-string", - "env": "container-runs-env", + "env": "runs-env", "steps": "composite-steps" } } From 180a687f30861bfe347684c5e43f7ccd969ff7e4 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 18 Jun 2020 16:13:56 -0400 Subject: [PATCH 04/31] Clean up trace messages + add Trace debug in ActionManager --- src/Runner.Worker/ActionManager.cs | 3 ++- src/Runner.Worker/ExecutionContext.cs | 4 ---- src/Runner.Worker/Handlers/CompositeActionHandler.cs | 8 -------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 5b2fa3fda1e..d582a26e941 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -397,7 +397,8 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio } else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { - // Don't do anything for now + var compositeAction = definition.Data.Execution as CompositeActionExecutionData; + Trace.Info($"Action steps: {compositeAction.Steps}."); } else { diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index a76ca3cd019..b99ff99cdfb 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -291,8 +291,6 @@ public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData) // placed after the queued composite steps // This will take only O(n+m) time which would be just as efficient if we refactored the code of JobSteps to a PriorityQueue // This temp Queue is created in the CompositeActionHandler. - - Trace.Info("Adding Composite Action Step"); var newGuid = Guid.NewGuid(); step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; @@ -310,7 +308,6 @@ public void EnqueueAllCompositeSteps(Queue steps) Root.JobSteps.Clear(); foreach (var cs in steps) { - Trace.Info($"EnqueueAllCompositeSteps : Adding Composite action step {cs}"); Root.JobSteps.Enqueue(cs); } foreach (var s in temp) @@ -323,7 +320,6 @@ public void EnqueueAllCompositeSteps(Queue steps) Root.JobSteps = new Queue(); foreach (var cs in steps) { - Trace.Info($"EnqueueAllCompositeSteps : Adding Composite action step {cs}"); Root.JobSteps.Enqueue(cs); } } diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 34f12270b73..c29e6c10f82 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -36,14 +36,6 @@ public Task RunAsync(ActionRunStage stage) // Resolve action steps var actionSteps = Data.Steps; - if (actionSteps == null) - { - Trace.Error("Data.Steps in CompositeActionHandler is null"); - } - else - { - Trace.Info($"Data Steps Value for Composite Actions is: {actionSteps}."); - } // Create Context Data to reuse for each composite action step var inputsData = new DictionaryContextData(); From 96e003706fbd99ee04d56476bbefa163127bcfac Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 18 Jun 2020 16:16:55 -0400 Subject: [PATCH 05/31] Add debugging message --- src/Runner.Worker/ActionManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index d157f8fd395..6ecc09f0166 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -399,6 +399,7 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio { var compositeAction = definition.Data.Execution as CompositeActionExecutionData; Trace.Info($"Action steps: {compositeAction.Steps}."); + Trace.Info($"Action environment: {compositeAction.Environment}."); } else { From 496064f72db93ea8596c87cfd7c4d2ed44f3519f Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 18 Jun 2020 17:34:17 -0400 Subject: [PATCH 06/31] Optimize runtime of code --- src/Runner.Worker/ExecutionContext.cs | 12 ++++++----- .../Handlers/CompositeActionHandler.cs | 20 +++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 11116e96ef2..526f9af560e 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -107,6 +107,7 @@ public interface IExecutionContext : IRunnerService void RegisterPostJobStep(IStep step); IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, Dictionary envData); void EnqueueAllCompositeSteps(Queue steps); + public void SetEnvironmentVariables(Dictionary dict); } public sealed class ExecutionContext : RunnerService, IExecutionContext @@ -298,14 +299,16 @@ public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData, // Add the composite action environment variables to each step. // If the key already exists, we override it since the composite action env variables will have higher precedence // Note that for each composite action step, it's environment variables will be set in the StepRunner automatically - foreach (var e in envData) - { - step.ExecutionContext.EnvironmentVariables[e.Key] = e.Value; - } + step.ExecutionContext.SetEnvironmentVariables(envData); return step; } + public void SetEnvironmentVariables(Dictionary dict) + { + this.EnvironmentVariables = dict; + } + // Add Composite Steps first and then requeue the rest of the job steps. public void EnqueueAllCompositeSteps(Queue steps) { @@ -333,7 +336,6 @@ public void EnqueueAllCompositeSteps(Queue steps) } } } - public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) { Trace.Entering(); diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 0902fafae44..db3ff2cdd16 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -44,12 +44,28 @@ public Task RunAsync(ActionRunStage stage) inputsData[i.Key] = new StringContextData(i.Value); } - + // Get Environment Data for Composite Action var extraExpressionValues = new Dictionary(StringComparer.OrdinalIgnoreCase); extraExpressionValues["inputs"] = inputsData; var manifestManager = HostContext.GetService(); - var envData = manifestManager.EvaluateCompositeActionEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); + + // Add the composite action environment variables to each step. + // If the key already exists, we override it since the composite action env variables will have higher precedence + // Note that for each composite action step, it's environment variables will be set in the StepRunner automatically + var compositeEnvData = manifestManager.EvaluateCompositeActionEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); + var envData = new Dictionary(); + + // Copy over parent environment + foreach (var env in ExecutionContext.EnvironmentVariables) + { + envData[env.Key] = env.Value; + } + // Overwrite with current env + foreach (var env in compositeEnvData) + { + envData[env.Key] = env.Value; + } // Add each composite action step to the front of the queue var compositeActionSteps = new Queue(); From 5843362bd45562a1bb6951fee02ce3f379bdb718 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 19 Jun 2020 11:22:49 -0400 Subject: [PATCH 07/31] Change String to string --- src/Runner.Worker/ActionManager.cs | 4 ++-- src/Runner.Worker/ActionManifestManager.cs | 4 ++-- src/Runner.Worker/StepsRunner.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index d582a26e941..52440a01998 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -395,7 +395,7 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}."); } } - else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { var compositeAction = definition.Data.Execution as CompositeActionExecutionData; Trace.Info($"Action steps: {compositeAction.Steps}."); @@ -1106,7 +1106,7 @@ private ActionContainer PrepareRepositoryActionAsync(IExecutionContext execution Trace.Info($"Action plugin: {(actionDefinitionData.Execution as PluginActionExecutionData).Plugin}, no more preparation."); return null; } - else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { Trace.Info($"Action composite: {(actionDefinitionData.Execution as CompositeActionExecutionData).Steps}, no more preparation."); return null; diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index b210d79383d..1dbf35ace45 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -360,7 +360,7 @@ private ActionExecutionData ConvertRuns( preIfToken = run.Value.AssertString("pre-if"); break; case "steps": - if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { var steps = run.Value.AssertSequence("steps"); var evaluator = executionContext.ToPipelineTemplateEvaluator(); @@ -415,7 +415,7 @@ private ActionExecutionData ConvertRuns( }; } } - else if (string.Equals(usingToken.Value, "composite", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + else if (string.Equals(usingToken.Value, "composite", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { if (stepsLoaded == null) { diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index 6a0e2b6946f..3c7b38d4a14 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -68,7 +68,7 @@ public async Task RunAsync(IExecutionContext jobContext) var step = jobContext.JobSteps.Dequeue(); var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null; // TODO: Fix this temporary workaround for Composite Actions - if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { nextStep = null; } @@ -415,7 +415,7 @@ private bool InitializeScope(IStep step, Dictionary executionContext.Debug($"Initializing scope '{scope.Name}'"); executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName); // TODO: Fix this temporary workaround for Composite Actions - if (!executionContext.ExpressionValues.ContainsKey("inputs") && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + if (!executionContext.ExpressionValues.ContainsKey("inputs") && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null; } @@ -442,7 +442,7 @@ private bool InitializeScope(IStep step, Dictionary var scopeName = executionContext.ScopeName; executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scopeName); // TODO: Fix this temporary workaround for Composite Actions - if (!executionContext.ExpressionValues.ContainsKey("inputs") && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + if (!executionContext.ExpressionValues.ContainsKey("inputs") && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { executionContext.ExpressionValues["inputs"] = string.IsNullOrEmpty(scopeName) ? null : scopeInputs[scopeName]; } From 941a24ee3747f65b46c62e061873feb6d262933a Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 19 Jun 2020 11:27:19 -0400 Subject: [PATCH 08/31] Add comma to Composite --- src/Runner.Worker/ActionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 52440a01998..55e3a2b973a 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -1221,7 +1221,7 @@ public enum ActionExecutionType NodeJS, Plugin, Script, - Composite + Composite, } public sealed class ContainerActionExecutionData : ActionExecutionData From 9939cf527eaadece243d0c5c75e78de478f1c556 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 19 Jun 2020 14:29:48 -0400 Subject: [PATCH 09/31] Change JobSteps to a List, Change Register Step function name --- src/Runner.Worker/ExecutionContext.cs | 68 +++---------------- .../Handlers/CompositeActionHandler.cs | 8 +-- src/Runner.Worker/JobRunner.cs | 2 +- src/Runner.Worker/StepsRunner.cs | 7 +- 4 files changed, 20 insertions(+), 65 deletions(-) diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index b99ff99cdfb..228074e6c1b 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -63,7 +63,7 @@ public interface IExecutionContext : IRunnerService JobContext JobContext { get; } // Only job level ExecutionContext has JobSteps - Queue JobSteps { get; } + List JobSteps { get; } // Only job level ExecutionContext has PostJobSteps Stack PostJobSteps { get; } @@ -105,8 +105,7 @@ public interface IExecutionContext : IRunnerService // others void ForceTaskComplete(); void RegisterPostJobStep(IStep step); - IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData); - void EnqueueAllCompositeSteps(Queue steps); + void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location); } public sealed class ExecutionContext : RunnerService, IExecutionContext @@ -161,7 +160,7 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext public List ServiceContainers { get; private set; } // Only job level ExecutionContext has JobSteps - public Queue JobSteps { get; private set; } + public List JobSteps { get; private set; } // Only job level ExecutionContext has PostJobSteps public Stack PostJobSteps { get; private set; } @@ -267,62 +266,17 @@ public void RegisterPostJobStep(IStep step) Root.PostJobSteps.Push(step); } - /* - RegisterCompositeStep is a helper function used in CompositeActionHandler::RunAsync to - add a child node, aka a step, to the current job to the front of the queue for processing. - */ - public IStep RegisterCompositeStep(IStep step, DictionaryContextData inputsData) + /// + /// Helper function used in CompositeActionHandler::RunAsync to + /// add a child node, aka a step, to the current job to the Root.JobSteps based on the location. + /// + public void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location) { - // ~Brute Force Method~ - // There is no way to put this current job in front of the queue in < O(n) time where n = # of elements in JobSteps - // Everytime we add a new step, you could requeue every item to put those steps from that stack in JobSteps which - // would result in O(n) for each time we add a composite action step where n = number of jobSteps which would compound - // to O(n*m) where m = number of composite steps - // var temp = Root.JobSteps.ToArray(); - // Root.JobSteps.Clear(); - // Root.JobSteps.Enqueue(step); - // foreach(var s in temp) - // Root.JobSteps.Enqueue(s); - - // ~Optimized Method~ - // Alterative solution: We add to another temp Queue - // After we add all the transformed composite steps to this temp queue, we requeue the whole JobSteps accordingly in EnqueueAllCompositeSteps() - // where the queued composite steps are at the front of the JobSteps Queue and the rest of the jobs maintain its order and are - // placed after the queued composite steps - // This will take only O(n+m) time which would be just as efficient if we refactored the code of JobSteps to a PriorityQueue - // This temp Queue is created in the CompositeActionHandler. + // TODO: For UI purposes, look at figuring out how to condense steps in one node => maybe use the same previous GUID var newGuid = Guid.NewGuid(); step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; - return step; - } - - // Add Composite Steps first and then requeue the rest of the job steps. - public void EnqueueAllCompositeSteps(Queue steps) - { - // TODO: For UI purposes, look at figuring out how to condense steps in one node - // maybe use "this" instead of "Root"? - if (Root.JobSteps != null) - { - var temp = Root.JobSteps.ToArray(); - Root.JobSteps.Clear(); - foreach (var cs in steps) - { - Root.JobSteps.Enqueue(cs); - } - foreach (var s in temp) - { - Root.JobSteps.Enqueue(s); - } - } - else - { - Root.JobSteps = new Queue(); - foreach (var cs in steps) - { - Root.JobSteps.Enqueue(cs); - } - } + Root.JobSteps.Insert(0, step); } public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) @@ -719,7 +673,7 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation PrependPath = new List(); // JobSteps for job ExecutionContext - JobSteps = new Queue(); + JobSteps = new List(); // PostJobSteps for job ExecutionContext PostJobSteps = new Stack(); diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index c29e6c10f82..74c3d4c7e92 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -45,7 +45,7 @@ public Task RunAsync(ActionRunStage stage) } // Add each composite action step to the front of the queue - var compositeActionSteps = new Queue(); + int location = 0; foreach (Pipelines.ActionStep aStep in actionSteps) { // Ex: @@ -63,7 +63,7 @@ public Task RunAsync(ActionRunStage stage) // - run echo hello world 3 (d) // - run echo hello world 4 (e) // - // Stack (LIFO) [Bottom => Middle => Top]: + // Steps processed as follow: // | a | // | a | => | d | // (Run step d) @@ -86,9 +86,9 @@ public Task RunAsync(ActionRunStage stage) // TODO: Do we need to add any context data from the job message? // (See JobExtension.cs ~line 236) - compositeActionSteps.Enqueue(ExecutionContext.RegisterCompositeStep(actionRunner, inputsData)); + ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location); + location++; } - ExecutionContext.EnqueueAllCompositeSteps(compositeActionSteps); return Task.CompletedTask; } diff --git a/src/Runner.Worker/JobRunner.cs b/src/Runner.Worker/JobRunner.cs index 31dfb17145b..33b291adb6d 100644 --- a/src/Runner.Worker/JobRunner.cs +++ b/src/Runner.Worker/JobRunner.cs @@ -152,7 +152,7 @@ public async Task RunAsync(Pipelines.AgentJobRequestMessage message, { foreach (var step in jobSteps) { - jobContext.JobSteps.Enqueue(step); + jobContext.JobSteps.Add(step); } await stepsRunner.RunAsync(jobContext); diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index 3c7b38d4a14..9fc323c8692 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -59,14 +59,15 @@ public async Task RunAsync(IExecutionContext jobContext) checkPostJobActions = true; while (jobContext.PostJobSteps.TryPop(out var postStep)) { - jobContext.JobSteps.Enqueue(postStep); + jobContext.JobSteps.Add(postStep); } continue; } - var step = jobContext.JobSteps.Dequeue(); - var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null; + var step = jobContext.JobSteps[0]; + jobContext.JobSteps.RemoveAt(0); + var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps[0] : null; // TODO: Fix this temporary workaround for Composite Actions if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { From 5988076fcf9913d98d745549089e85d11a46be8f Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 19 Jun 2020 14:38:37 -0400 Subject: [PATCH 10/31] Add TODO, remove unn. content --- src/Runner.Worker/ExecutionContext.cs | 1 + src/Runner.Worker/Handlers/HandlerFactory.cs | 3 --- .../Pipelines/ObjectTemplating/PipelineTemplateConstants.cs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 228074e6c1b..67ef66cb78b 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -276,6 +276,7 @@ public void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int var newGuid = Guid.NewGuid(); step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; + // TODO: confirm whether not copying message contexts is safe Root.JobSteps.Insert(0, step); } diff --git a/src/Runner.Worker/Handlers/HandlerFactory.cs b/src/Runner.Worker/Handlers/HandlerFactory.cs index 99311afca5f..db4d6559c88 100644 --- a/src/Runner.Worker/Handlers/HandlerFactory.cs +++ b/src/Runner.Worker/Handlers/HandlerFactory.cs @@ -68,10 +68,7 @@ public IHandler Create( } else if (data.ExecutionType == ActionExecutionType.Composite) { - // TODO - // Runner plugin handler = HostContext.CreateService(); - // handler = CompositeHandler; (handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData; } else diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs index bf7d8d2e971..f2609462b23 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs @@ -65,7 +65,6 @@ public sealed class PipelineTemplateConstants public const String StepEnv = "step-env"; public const String StepIfResult = "step-if-result"; public const String Steps = "steps"; - public const String StepsInTemplate = "steps-in-template"; public const String StepsScopeInputs = "steps-scope-inputs"; public const String StepsScopeOutputs = "steps-scope-outputs"; From 00a736d9cc5e3fe6e2c9b9776d8915c9edc18b51 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 19 Jun 2020 14:43:31 -0400 Subject: [PATCH 11/31] Remove unnecessary code --- src/Runner.Worker/StepsRunner.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index 9fc323c8692..e75d2e106f8 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -68,11 +68,6 @@ public async Task RunAsync(IExecutionContext jobContext) var step = jobContext.JobSteps[0]; jobContext.JobSteps.RemoveAt(0); var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps[0] : null; - // TODO: Fix this temporary workaround for Composite Actions - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) - { - nextStep = null; - } Trace.Info($"Processing step: DisplayName='{step.DisplayName}'"); ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext)); From e4dfd0e8fd39bfa5592ea5e517790b0f33fe39bd Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 19 Jun 2020 14:51:16 -0400 Subject: [PATCH 12/31] Fix unit tests --- src/Test/L0/Worker/StepsRunnerL0.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs index 2d7cb9fb0c4..1dfee2252ad 100644 --- a/src/Test/L0/Worker/StepsRunnerL0.cs +++ b/src/Test/L0/Worker/StepsRunnerL0.cs @@ -80,7 +80,7 @@ public async Task RunNormalStepsAllStepPass() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -115,7 +115,7 @@ public async Task RunNormalStepsContinueOnError() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -154,7 +154,7 @@ public async Task RunsAfterFailureBasedOnCondition() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Steps.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Steps.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -208,7 +208,7 @@ public async Task RunsAlwaysSteps() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Steps.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Steps.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -287,7 +287,7 @@ public async Task SetsJobResultCorrectly() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Steps.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Steps.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -330,7 +330,7 @@ public async Task SkipsAfterFailureOnlyBaseOnCondition() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Step.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Step.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -361,7 +361,7 @@ public async Task AlwaysMeansAlways() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -391,7 +391,7 @@ public async Task TreatsConditionErrorAsFailure() { _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(variableSet.Select(x => x.Object).ToList())); + _ec.Setup(x => x.JobSteps).Returns(new List(variableSet.Select(x => x.Object).ToList())); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -417,7 +417,7 @@ public async Task StepEnvOverrideJobEnvContext() _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object })); + _ec.Setup(x => x.JobSteps).Returns(new List(new[] { step1.Object })); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -455,7 +455,7 @@ public async Task PopulateEnvContextForEachStep() _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object, step2.Object })); + _ec.Setup(x => x.JobSteps).Returns(new List(new[] { step1.Object, step2.Object })); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -493,7 +493,7 @@ public async Task PopulateEnvContextAfterSetupStepsContext() _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object, step2.Object })); + _ec.Setup(x => x.JobSteps).Returns(new List(new[] { step1.Object, step2.Object })); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -524,7 +524,7 @@ public async Task StepContextOutcome() _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object, step2.Object, step3.Object })); + _ec.Setup(x => x.JobSteps).Returns(new List(new[] { step1.Object, step2.Object, step3.Object })); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); @@ -560,7 +560,7 @@ public async Task StepContextConclusion() _ec.Object.Result = null; - _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object, step2.Object, step3.Object })); + _ec.Setup(x => x.JobSteps).Returns(new List(new[] { step1.Object, step2.Object, step3.Object })); // Act. await _stepsRunner.RunAsync(jobContext: _ec.Object); From 45ddd4233eb97e5bee566e1905ba8bbea9ec6911 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Sat, 20 Jun 2020 14:20:04 -0400 Subject: [PATCH 13/31] Fix env format --- src/Runner.Worker/ActionManifestManager.cs | 19 ++++++++++++++++--- src/Runner.Worker/action_yaml.json | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 710e4482530..bb9aef43cb1 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -75,6 +75,8 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani } var actionMapping = token.AssertMapping("action manifest root"); + var envComposite = default(MappingToken); + foreach (var actionPair in actionMapping) { var propertyName = actionPair.Key.AssertString($"action.yml property key"); @@ -93,9 +95,14 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani ConvertInputs(context, actionPair.Value, actionDefinition); break; + case "env": + envComposite = actionPair.Value.AssertMapping("env"); + break; + case "runs": - actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value); + actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value, envComposite); break; + default: Trace.Info($"Ignore action property {propertyName}."); break; @@ -340,7 +347,8 @@ private TemplateContext CreateContext( private ActionExecutionData ConvertRuns( IExecutionContext executionContext, TemplateContext context, - TemplateToken inputsToken) + TemplateToken inputsToken, + MappingToken envComposite) { var runsMapping = inputsToken.AssertMapping("runs"); var usingToken = default(StringToken); @@ -469,10 +477,15 @@ private ActionExecutionData ConvertRuns( return new CompositeActionExecutionData() { Steps = stepsLoaded, - Environment = envToken + Environment = envComposite }; } } + // If there is an env set and it's not for composite action, yield an error + if (envComposite != null && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) + { + throw new ArgumentException("You cannot use env unless you are using Composite Actions"); + } else { throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead."); diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index da937f2a919..03ceca84ea1 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -7,6 +7,7 @@ "name": "string", "description": "string", "inputs": "inputs", + "env": "runs-env", "runs": "runs" }, "loose-key-type": "non-empty-string", @@ -88,7 +89,6 @@ "mapping": { "properties": { "using": "non-empty-string", - "env": "runs-env", "steps": "composite-steps" } } From 37849dc6e34e31911f1818823f341fa47986749e Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Mon, 22 Jun 2020 10:23:52 -0400 Subject: [PATCH 14/31] Remove comment --- src/Runner.Worker/StepsRunner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index cc07c8772fb..e75d2e106f8 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -86,7 +86,6 @@ public async Task RunAsync(IExecutionContext jobContext) // Initialize scope if (InitializeScope(step, scopeInputs)) { - // TODO Check if this adds env context to each composite action step // Populate env context for each step Trace.Info("Initialize Env context for step"); #if OS_WINDOWS From 94e7b474e10a1cd8f8f60f24a6564b6eca1103c6 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Mon, 22 Jun 2020 11:31:43 -0400 Subject: [PATCH 15/31] Remove TODO message for context --- src/Runner.Worker/Handlers/CompositeActionHandler.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index d49bfc560ac..45e055faf2d 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -106,9 +106,7 @@ public Task RunAsync(ActionRunStage stage) actionRunner.Stage = stage; actionRunner.Condition = aStep.Condition; actionRunner.DisplayName = aStep.DisplayName; - // TODO: Do we need to add any context data from the job message? - // (See JobExtension.cs ~line 236) - + ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location, envData); location++; } From a254442dcc332ec02c3340172cbc5b961e221371 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Mon, 22 Jun 2020 11:37:52 -0400 Subject: [PATCH 16/31] Add verbose trace logs which are only viewable by devs --- src/Runner.Worker/ActionManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 55e3a2b973a..2dbef6fb515 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -398,7 +398,8 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) { var compositeAction = definition.Data.Execution as CompositeActionExecutionData; - Trace.Info($"Action steps: {compositeAction.Steps}."); + Trace.Info($"Load {compositeAction.Steps.Count} action steps."); + Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction.Steps)}"); } else { From 0d5e84b18355ba74086ee391b03389040c474f55 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 23 Jun 2020 12:20:12 -0400 Subject: [PATCH 17/31] Sort usings in Composite Action Handler --- .../Handlers/CompositeActionHandler.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 74c3d4c7e92..7c5b25ed5fb 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -1,15 +1,16 @@ +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; +using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Pipelines.ContextData; +using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common; using GitHub.Runner.Sdk; -using GitHub.DistributedTask.WebApi; using Pipelines = GitHub.DistributedTask.Pipelines; -using System; -using System.Linq; -using GitHub.DistributedTask.ObjectTemplating.Tokens; -using System.Collections.Generic; -using GitHub.DistributedTask.Pipelines.ContextData; + namespace GitHub.Runner.Worker.Handlers { From 9ec704744117868c967fedb9ca28dcffcaf994bb Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 23 Jun 2020 12:20:39 -0400 Subject: [PATCH 18/31] Change 0 to location --- src/Runner.Worker/ExecutionContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 67ef66cb78b..cea1e2fd4d7 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -277,7 +277,7 @@ public void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; // TODO: confirm whether not copying message contexts is safe - Root.JobSteps.Insert(0, step); + Root.JobSteps.Insert(location, step); } public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) From 8aadbbdb8ea93a77cd5c77c6d894f916e812cc5a Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 23 Jun 2020 12:21:34 -0400 Subject: [PATCH 19/31] Update context variables in composite action yaml --- src/Runner.Worker/action_yaml.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index da8fb79a962..cb1d90b2e0b 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -104,6 +104,10 @@ "job", "runner", "env", + "always(0,0)", + "failure(0,0)", + "cancelled(0,0)", + "success(0,0)", "hashFiles(1,255)" ], "sequence": { From f9b28c7210a833d76f70c98f87f1655fedbcbdf3 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 23 Jun 2020 12:23:02 -0400 Subject: [PATCH 20/31] Add helpful error message for null steps --- src/Runner.Worker/ActionManifestManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 1dbf35ace45..9095f498ddd 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -419,6 +419,7 @@ private ActionExecutionData ConvertRuns( { if (stepsLoaded == null) { + // TODO: Add a more helpful error message + including file name, etc. to show user that it's because of their yaml file throw new ArgumentNullException($"No steps provided."); } else From 57d59fcd6eb8e66948b3ead44da013ec36ebaa90 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Thu, 25 Jun 2020 13:53:41 -0400 Subject: [PATCH 21/31] Fix Workflow Step Env overiding Parent Env --- src/Runner.Worker/Handlers/CompositeActionHandler.cs | 2 +- .../Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 6fb543b7923..d626fde4ff3 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -58,7 +58,7 @@ public Task RunAsync(ActionRunStage stage) var envData = new Dictionary(); // Copy over parent environment - foreach (var env in ExecutionContext.EnvironmentVariables) + foreach (var env in Environment) { envData[env.Key] = env.Value; } diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs index 55076e670e5..f09a905bbe8 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs @@ -160,8 +160,7 @@ public String EvaluateStepDisplayName( } public List LoadCompositeSteps( - TemplateToken token - ) + TemplateToken token) { var result = default(List); if (token != null && token.Type != TokenType.Null) From b63f98714c9a814363a6c861d5652981e88ab108 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 12:38:39 -0400 Subject: [PATCH 22/31] Remove env in composite action scope --- src/Runner.Worker/ActionManager.cs | 3 - src/Runner.Worker/ActionManifestManager.cs | 57 +------------------ .../Handlers/CompositeActionHandler.cs | 19 +------ src/Runner.Worker/action_yaml.json | 5 +- 4 files changed, 6 insertions(+), 78 deletions(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 3bdba9df340..2dbef6fb515 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -400,8 +400,6 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio var compositeAction = definition.Data.Execution as CompositeActionExecutionData; Trace.Info($"Load {compositeAction.Steps.Count} action steps."); Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction.Steps)}"); - Trace.Info($"Load: {compositeAction.Environment} environment steps"); - Trace.Info($"Details: {StringUtil.ConvertToJson(compositeAction.Environment)}"); } else { @@ -1287,7 +1285,6 @@ public sealed class CompositeActionExecutionData : ActionExecutionData public override bool HasPre => false; public override bool HasPost => false; public List Steps { get; set; } - public MappingToken Environment { get; set; } } public abstract class ActionExecutionData diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index af7ee666f78..d4bcb5059b0 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -27,7 +27,6 @@ public interface IActionManifestManager : IRunnerService Dictionary EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary extraExpressionValues); - public Dictionary EvaluateCompositeActionEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary extraExpressionValues); string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token); } @@ -100,7 +99,7 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani break; case "runs": - actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value, envComposite); + actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value); break; default: @@ -222,48 +221,6 @@ public Dictionary EvaluateContainerEnvironment( return result; } - public Dictionary EvaluateCompositeActionEnvironment( - IExecutionContext executionContext, - MappingToken token, - IDictionary extraExpressionValues) - { - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if (token != null) - { - var context = CreateContext(executionContext, extraExpressionValues); - try - { - var evaluateResult = TemplateEvaluator.Evaluate(context, "runs-env", token, 0, null, omitHeader: true); - context.Errors.Check(); - - // Mapping - var mapping = evaluateResult.AssertMapping("composite env"); - - foreach (var pair in mapping) - { - // Literal key - var key = pair.Key.AssertString("composite env key"); - - // Literal value - var value = pair.Value.AssertString("composite env value"); - result[key.Value] = value.Value; - - Trace.Info($"Add env {key} = {value}"); - } - } - catch (Exception ex) when (!(ex is TemplateValidationException)) - { - Trace.Error(ex); - context.Errors.Add(ex); - } - - context.Errors.Check(); - } - - return result; - - } public string EvaluateDefaultInput( IExecutionContext executionContext, string inputName, @@ -347,8 +304,7 @@ private TemplateContext CreateContext( private ActionExecutionData ConvertRuns( IExecutionContext executionContext, TemplateContext context, - TemplateToken inputsToken, - MappingToken envComposite) + TemplateToken inputsToken) { var runsMapping = inputsToken.AssertMapping("runs"); var usingToken = default(StringToken); @@ -440,7 +396,6 @@ private ActionExecutionData ConvertRuns( Image = imageToken.Value, Arguments = argsToken, EntryPoint = entrypointToken?.Value, - Environment = envToken, Pre = preEntrypointToken?.Value, InitCondition = preIfToken?.Value ?? "always()", Post = postEntrypointToken?.Value, @@ -477,16 +432,10 @@ private ActionExecutionData ConvertRuns( { return new CompositeActionExecutionData() { - Steps = stepsLoaded, - Environment = envComposite + Steps = stepsLoaded }; } } - // If there is an env set and it's not for composite action, yield an error - if (envComposite != null && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) - { - throw new ArgumentException("You cannot use env unless you are using Composite Actions"); - } else { throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead."); diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index d626fde4ff3..62c67e1aa8e 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -51,23 +51,6 @@ public Task RunAsync(ActionRunStage stage) extraExpressionValues["inputs"] = inputsData; var manifestManager = HostContext.GetService(); - // Add the composite action environment variables to each step. - // If the key already exists, we override it since the composite action env variables will have higher precedence - // Note that for each composite action step, it's environment variables will be set in the StepRunner automatically - var compositeEnvData = manifestManager.EvaluateCompositeActionEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); - var envData = new Dictionary(); - - // Copy over parent environment - foreach (var env in Environment) - { - envData[env.Key] = env.Value; - } - // Overwrite with current env - foreach (var env in compositeEnvData) - { - envData[env.Key] = env.Value; - } - // Add each composite action step to the front of the queue int location = 0; foreach (Pipelines.ActionStep aStep in actionSteps) @@ -108,7 +91,7 @@ public Task RunAsync(ActionRunStage stage) actionRunner.Condition = aStep.Condition; actionRunner.DisplayName = aStep.DisplayName; - ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location, envData); + ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location, Environment); location++; } diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index a8ec3a2e439..cb1d90b2e0b 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -7,7 +7,6 @@ "name": "string", "description": "string", "inputs": "inputs", - "env": "runs-env", "runs": "runs" }, "loose-key-type": "non-empty-string", @@ -44,7 +43,7 @@ "image": "non-empty-string", "entrypoint": "non-empty-string", "args": "container-runs-args", - "env": "runs-env", + "env": "container-runs-env", "pre-entrypoint": "non-empty-string", "pre-if": "non-empty-string", "post-entrypoint": "non-empty-string", @@ -57,7 +56,7 @@ "item-type": "container-runs-context" } }, - "runs-env": { + "container-runs-env": { "context": [ "inputs" ], From da1e2b0a84331095595231c329e72109e25a44b5 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 12:40:41 -0400 Subject: [PATCH 23/31] Clean up --- src/Runner.Worker/ActionManifestManager.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index d4bcb5059b0..1f8572931b3 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -74,7 +74,6 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani } var actionMapping = token.AssertMapping("action manifest root"); - var envComposite = default(MappingToken); foreach (var actionPair in actionMapping) { @@ -94,10 +93,6 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani ConvertInputs(context, actionPair.Value, actionDefinition); break; - case "env": - envComposite = actionPair.Value.AssertMapping("env"); - break; - case "runs": actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value); break; @@ -150,7 +145,7 @@ public List EvaluateContainerArguments( var context = CreateContext(executionContext, extraExpressionValues); try { - var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true); + var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); context.Errors.Check(); Trace.Info($"Arguments evaluate result: {StringUtil.ConvertToJson(evaluateResult)}"); @@ -432,7 +427,7 @@ private ActionExecutionData ConvertRuns( { return new CompositeActionExecutionData() { - Steps = stepsLoaded + Steps = stepsLoaded, }; } } From 5d6114548ebb31ceac67a69aab3590c0ff422492 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 12:43:13 -0400 Subject: [PATCH 24/31] Revert back --- src/Runner.Worker/ActionManifestManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 1f8572931b3..267309c835e 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -74,7 +74,6 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani } var actionMapping = token.AssertMapping("action manifest root"); - foreach (var actionPair in actionMapping) { var propertyName = actionPair.Key.AssertString($"action.yml property key"); @@ -145,7 +144,7 @@ public List EvaluateContainerArguments( var context = CreateContext(executionContext, extraExpressionValues); try { - var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); + var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true); context.Errors.Check(); Trace.Info($"Arguments evaluate result: {StringUtil.ConvertToJson(evaluateResult)}"); @@ -184,7 +183,7 @@ public Dictionary EvaluateContainerEnvironment( var context = CreateContext(executionContext, extraExpressionValues); try { - var evaluateResult = TemplateEvaluator.Evaluate(context, "runs-env", token, 0, null, omitHeader: true); + var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); context.Errors.Check(); Trace.Info($"Environments evaluate result: {StringUtil.ConvertToJson(evaluateResult)}"); @@ -427,7 +426,7 @@ private ActionExecutionData ConvertRuns( { return new CompositeActionExecutionData() { - Steps = stepsLoaded, + Steps = stepsLoaded }; } } From 4ccac8c0e287467640035a3758bf25e53d5ae1d1 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 12:44:11 -0400 Subject: [PATCH 25/31] revert back --- src/Runner.Worker/ActionManifestManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 267309c835e..ff450441b03 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -95,7 +95,6 @@ public ActionDefinitionData Load(IExecutionContext executionContext, string mani case "runs": actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value); break; - default: Trace.Info($"Ignore action property {propertyName}."); break; @@ -426,7 +425,7 @@ private ActionExecutionData ConvertRuns( { return new CompositeActionExecutionData() { - Steps = stepsLoaded + Steps = stepsLoaded, }; } } From 0041023399d00d2226a568f824adedf0c678f09f Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 12:44:54 -0400 Subject: [PATCH 26/31] add back envToken --- src/Runner.Worker/ActionManifestManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index ff450441b03..9095f498ddd 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -389,6 +389,7 @@ private ActionExecutionData ConvertRuns( Image = imageToken.Value, Arguments = argsToken, EntryPoint = entrypointToken?.Value, + Environment = envToken, Pre = preEntrypointToken?.Value, InitCondition = preIfToken?.Value ?? "always()", Post = postEntrypointToken?.Value, From 4b3ec9fbe605f600dc5adbb9589e04e47094ce51 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 12:45:59 -0400 Subject: [PATCH 27/31] Remove unnecessary code --- src/Runner.Worker/Handlers/CompositeActionHandler.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 62c67e1aa8e..1a0a197c2e6 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -45,12 +45,6 @@ public Task RunAsync(ActionRunStage stage) inputsData[i.Key] = new StringContextData(i.Value); } - - // Get Environment Data for Composite Action - var extraExpressionValues = new Dictionary(StringComparer.OrdinalIgnoreCase); - extraExpressionValues["inputs"] = inputsData; - var manifestManager = HostContext.GetService(); - // Add each composite action step to the front of the queue int location = 0; foreach (Pipelines.ActionStep aStep in actionSteps) From 96bff6a2065cf6df1d93a94e8494a0e8811b44f0 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 18:16:54 -0400 Subject: [PATCH 28/31] Figure out how to handle set-env edge cases --- src/Runner.Worker/ExecutionContext.cs | 19 ++++++++++++------- src/Runner.Worker/StepsRunner.cs | 18 +++++++++++++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index b49fab72b5a..83e47c74c5f 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -105,7 +105,6 @@ public interface IExecutionContext : IRunnerService // others void ForceTaskComplete(); void RegisterPostJobStep(IStep step); - public void SetEnvironmentVariables(Dictionary dict); void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location, Dictionary envData); } @@ -281,13 +280,19 @@ public void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int // Add the composite action environment variables to each step. // If the key already exists, we override it since the composite action env variables will have higher precedence // Note that for each composite action step, it's environment variables will be set in the StepRunner automatically - step.ExecutionContext.SetEnvironmentVariables(envData); - Root.JobSteps.Insert(location, step); - } + // step.ExecutionContext.SetEnvironmentVariables(envData); +#if OS_WINDOWS + var envContext = new DictionaryContextData(); +#else + var envContext = new CaseSensitiveDictionaryContextData(); +#endif + foreach (var pair in envData) + { + envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); + } + step.ExecutionContext.ExpressionValues["env"] = envContext; - public void SetEnvironmentVariables(Dictionary dict) - { - this.EnvironmentVariables = dict; + Root.JobSteps.Insert(location, step); } public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary intraActionState = null, int? recordOrder = null) diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index e75d2e106f8..966f73dc1a4 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -93,12 +93,28 @@ public async Task RunAsync(IExecutionContext jobContext) #else var envContext = new CaseSensitiveDictionaryContextData(); #endif - step.ExecutionContext.ExpressionValues["env"] = envContext; + // Global env foreach (var pair in step.ExecutionContext.EnvironmentVariables) { envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); } + // Stomps over with outside step env + if (step.ExecutionContext.ExpressionValues.TryGetValue("env", out var envContextData)) + { +#if OS_WINDOWS + var dict = envContextData as DictionaryContextData; +#else + var dict = envContextData as CaseSensitiveDictionaryContextData; +#endif + foreach (var pair in dict) + { + envContext[pair.Key] = pair.Value; + } + } + + step.ExecutionContext.ExpressionValues["env"] = envContext; + bool evaluateStepEnvFailed = false; if (step is IActionRunner actionStep) { From 11b9cace750d1b4440fe14dae7a35af1e16f7b41 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Tue, 7 Jul 2020 18:18:09 -0400 Subject: [PATCH 29/31] formatting --- src/Runner.Worker/Handlers/CompositeActionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 1a0a197c2e6..ddf5821ad39 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -84,7 +84,7 @@ public Task RunAsync(ActionRunStage stage) actionRunner.Stage = stage; actionRunner.Condition = aStep.Condition; actionRunner.DisplayName = aStep.DisplayName; - + ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location, Environment); location++; } From a58ac2ef593ddd86b3d1f135578925306878708d Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Wed, 8 Jul 2020 10:00:24 -0400 Subject: [PATCH 30/31] fix unit tests --- src/Misc/dotnet-install.ps1 | 379 ++++++++++++++-------------- src/Test/L0/Worker/StepsRunnerL0.cs | 30 +-- 2 files changed, 209 insertions(+), 200 deletions(-) diff --git a/src/Misc/dotnet-install.ps1 b/src/Misc/dotnet-install.ps1 index c0122cdc3b7..206d4676192 100644 --- a/src/Misc/dotnet-install.ps1 +++ b/src/Misc/dotnet-install.ps1 @@ -154,7 +154,16 @@ function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [in function Get-Machine-Architecture() { Say-Invocation $MyInvocation - # possible values: amd64, x64, x86, arm64, arm + # On PS x86, PROCESSOR_ARCHITECTURE reports x86 even on x64 systems. + # To get the correct architecture, we need to use PROCESSOR_ARCHITEW6432. + # PS x64 doesn't define this, so we fall back to PROCESSOR_ARCHITECTURE. + # Possible values: amd64, x64, x86, arm64, arm + + if( $ENV:PROCESSOR_ARCHITEW6432 -ne $null ) + { + return $ENV:PROCESSOR_ARCHITEW6432 + } + return $ENV:PROCESSOR_ARCHITECTURE } @@ -686,194 +695,194 @@ Say "Installation finished" exit 0 # SIG # Begin signature block -# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# MIIjhwYJKoZIhvcNAQcCoIIjeDCCI3QCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAwp4UsNdAkvwY3 -# VhbuN9D6NGOz+qNqW2+62YubWa4qJaCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAiKYSY4KtkeThH +# d5M1aXqv1K0/pff07QwfUbYZ/qX5LqCCDYUwggYDMIID66ADAgECAhMzAAABiK9S +# 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG +# AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0 +# t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs +# 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd +# vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv +# V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W +# MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w +# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW +# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v +# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw +# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov +# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx +# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +# ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q +# qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X +# 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P +# mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM +# i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT +# GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz +# LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM +# SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa +# 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV +# Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+ +# r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK +# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm +# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw +# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD +# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG +# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la +# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc +# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D +# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ +# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk +# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 +# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd +# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL +# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd +# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 +# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS +# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI +# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL +# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD +# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv +# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF +# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h +# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA +# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn +# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 +# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b +# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ +# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy +# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp +# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi +# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb +# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS +# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL +# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX +# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFVgwghVUAgEBMIGVMH4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p +# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA +# AYgwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFxZ +# Yezh3liQqiGQuXNa+zYfoSIbLqOpdEn2ZKskBkisMEIGCisGAQQBgjcCAQwxNDAy +# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20wDQYJKoZIhvcNAQEBBQAEggEAjLUrwCXJCPHZulZuKAQSX+MfnIRFAhlN7ru2 +# 6H8rudvhkWgqMISkLb9gFDPR5FhR4sqdYgKW4P0ERao9ypCGi1FWDLqygC2XBbHj +# NEQHBxHJs5SMsMAXNSIcYHqVAvhF3nXoseaNBkhOTrkQ1FS/fW7AfDGRbsiiESzv +# lebf92shZylBFKOsKQLAL0mF/B7xrxHJIj5dgQoD1phATRNHOEQj3jgmkidFWowV +# 4r8MzbxRhAEORbnJexlUoDQJQH3YwxuUyXkTvrYMTKSbGJLlwRaZQbrcBU0k4gCH +# y8Sci+p9Rq+aOTzLCoNrZyh9E7OdwVDm1FJAtY30bV50T2WSFKGCEuIwghLeBgor +# BgEEAYI3AwMBMYISzjCCEsoGCSqGSIb3DQEHAqCCErswghK3AgEDMQ8wDQYJYIZI +# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE +# WQoDATAxMA0GCWCGSAFlAwQCAQUABCD7JNcBBSfhlKPL1tN3CEKRKJuT/dZ8RO9K +# orYLXJeLTwIGXvN89YD7GBMyMDIwMDcwMTE0MTYyMC40MDVaMASAAgH0oIHQpIHN +# MIHKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z +# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjoxNzlFLTRCQjAtODI0NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDjkwggTxMIID2aADAgECAhMzAAABDKp4btzMQkzBAAAA +# AAEMMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTAyMzIzMTkxNloXDTIxMDEyMTIzMTkxNlowgcoxCzAJBgNVBAYTAlVT +# MQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z +# b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy +# YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjE3OUUtNEJC +# MC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIB +# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq5011+XqVJmQKtiw39igeEMv +# CLcZ1forbmxsDkpnCN1SrThKI+n2Pr3zqTzJVgdJFCoKm1ks1gtRJ7HaL6tDkrOw +# 8XJmfJaxyQAluCQ+e40NI+A4w+u59Gy89AVY5lJNrmCva6gozfg1kxw6abV5WWr+ +# PjEpNCshO4hxv3UqgMcCKnT2YVSZzF1Gy7APub1fY0P1vNEuOFKrNCEEvWIKRrqs +# eyBB73G8KD2yw6jfz0VKxNSRAdhJV/ghOyrDt5a+L6C3m1rpr8sqiof3iohv3ANI +# gNqw6ex+4+G+B7JMbIHbGpPdebedL6ePbuBCnbgJoDn340k0aw6ij21GvvUnkQID +# AQABo4IBGzCCARcwHQYDVR0OBBYEFAlCOq9DDIa0A0oqgKtM5vjuZeK+MB8GA1Ud +# IwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0 +# dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKG +# Pmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENB +# XzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUH +# AwgwDQYJKoZIhvcNAQELBQADggEBAET3xBg/IZ9zdOfwbDGK7cK3qKYt/qUOlbRB +# zgeNjb32K86nGeRGkBee10dVOEGWUw6KtBeWh1LQ70b64/tLtiLcsf9JzaAyDYb1 +# sRmMi5fjRZ753TquaT8V7NJ7RfEuYfvZlubfQD0MVbU4tzsdZdYuxE37V2J9pN89 +# j7GoFNtAnSnCn1MRxENAILgt9XzeQzTEDhFYW0N2DNphTkRPXGjpDmwi6WtkJ5fv +# 0iTyB4dwEC+/ed0lGbFLcytJoMwfTNMdH6gcnHlMzsniornGFZa5PPiV78XoZ9Fe +# upKo8ZKNGhLLLB5GTtqfHex5no3ioVSq+NthvhX0I/V+iXJsopowggZxMIIEWaAD +# AgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzET +# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV +# TWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBD +# ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0yNTA3 +# MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjANBgkq +# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWl +# CgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/Fg +# iIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeR +# X4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/Xcf +# PfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogI +# Neh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQABo4IB +# 5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8RhvF +# M2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAP +# BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjE +# MFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kv +# Y3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEF +# BQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w +# a2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSABAf8E +# gZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5t +# aWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcC +# AjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUA +# bgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Ehb7Pr +# psz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7uVOM +# zPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqRUgCv +# OA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9Va8v +# /rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8+n99 +# lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+Y1kl +# D3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh2rBQ +# Hm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRyzR30 +# uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoouLGp +# 25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx16HS +# xVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341Hgi6 +# 2jbb01+P3nSISRKhggLLMIICNAIBATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMx +# CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv +# ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTc5RS00QkIw +# LTgyNDYxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB +# ATAHBgUrDgMCGgMVAMsg9FQ9pgPLXI2Ld5z7xDS0QAZ9oIGDMIGApH4wfDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQga11B1DE+ -# y9z0lmEO+MC+bhXPKfWALB7Snkn7G/wCUncwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBIgx+sFXkLXf7Xbx7opCD3uhpQGEQ4x/LsqTax0bu1 -# GC/cxiI+dodUz+T4hKj1ZQyUH0Zlce32GutY048O9tkr7fQyuohoFUgChdIATEOY -# qAIESFbDT07i7khJfO2pewlhgM+A5ClvBa8HAvV0wOd+2IVgv3pgow1LEJm0/5NB -# E3IFA+hFrqiWALOY0uUep4H20EHMrbqw3YoV3EodIkTj3fC76q4K/bF84EZLUgjY -# e4rmXac8n7A9qR18QzGl8usEJej4OHU4nlUT1J734m+AWIFmfb/Zr2MyXED0V4q4 -# Vbmw3O7xD9STeNYrn5RjPmGPEN04akHxhNUSqLIc9vxQoYIS8DCCEuwGCisGAQQB -# gjcDAwExghLcMIIS2AYJKoZIhvcNAQcCoIISyTCCEsUCAQMxDzANBglghkgBZQME -# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIPPK1A0D1n7ZEdgTjKPY4sWiOMtohMqGpFvG55NY -# SFHeAgZepuJh/dEYEjIwMjAwNTI5MTYyNzE1LjMxWjAEgAIB9KCB1KSB0TCBzjEL -# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v -# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj -# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU -# U1MgRVNOOjYwQkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T -# dGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAEm37pLIrmCggcAAAAA -# ASYwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp -# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw -# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw -# HhcNMTkxMjE5MDExNDU5WhcNMjEwMzE3MDExNDU5WjCBzjELMAkGA1UEBhMCVVMx -# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT -# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh -# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYwQkMt -# RTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl -# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnjC+hpxO8w2VdBO18X8L -# Hk6XdfR9yNQ0y+MuBOY7n5YdgkVunvbk/f6q8UoNFAdYQjVLPSAHbi6tUMiNeMGH -# k1U0lUxAkja2W2/szj/ghuFklvfHNBbsuiUShlhRlqcFNS7KXL2iwKDijmOhWJPY -# a2bLEr4W/mQLbSXail5p6m138Ttx4MAVEzzuGI0Kwr8ofIL7z6zCeWDiBM57LrNC -# qHOA2wboeuMsG4O0Oz2LMAzBLbJZPRPnZAD2HdD4HUL2mzZ8wox74Mekb7RzrUP3 -# hiHpxXZceJvhIEKfAgVkB5kTZQnio8A1JijMjw8f4TmsJPdJWpi8ei73sexe8/Yj -# cwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFEmrrB8XsH6YQo3RWKZfxqM0DmFBMB8G -# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG -# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp -# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH -# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh -# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB -# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAECW+51o6W/0J/O/npudfjVzMXq0u0cs -# HjqXpdRyH6o03jlmY5MXAui3cmPBKufijJxD2pMRPVMUNh3VA0PQuJeYrP06oFdq -# LpLxd3IJARm98vzaMgCz2nCwBDpe9X2M3Js9K1GAX+w4Az8N7J+Z6P1OD0VxHBdq -# eTaqDN1lk1vwagTN7t/WitxMXRDz0hRdYiWbATBAVgXXCOfzs3hnEv1n/EDab9HX -# OLMXKVY/+alqYKdV9lkuRp8Us1Q1WZy9z72Azu9x4mzft3fJ1puTjBHo5tHfixZo -# ummbI+WwjVCrku7pskJahfNi5amSgrqgR6nWAwvpJELccpVLdSxxmG0wggZxMIIE -# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV -# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE -# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v -# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y -# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN -# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU -# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE -# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50 -# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd -# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR -# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB -# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8 -# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB -# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO -# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w -# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr -# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv -# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB -# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF -# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt -# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh -# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7 -# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR -# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9 -# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8 -# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+ -# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh -# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy -# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo -# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx -# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341 -# Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w -# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYw -# QkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2 -# aWNloiMKAQEwBwYFKw4DAhoDFQAKZzI5aZnESumrToHx3Lqgxnr//KCBgzCBgKR+ -# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT -# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA -# 4nuQTDAiGA8yMDIwMDUyOTE3NDQ0NFoYDzIwMjAwNTMwMTc0NDQ0WjB3MD0GCisG -# AQQBhFkKBAExLzAtMAoCBQDie5BMAgEAMAoCAQACAiZJAgH/MAcCAQACAhEjMAoC -# BQDifOHMAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA -# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAprmyJTXdH9FmQZ0I -# mRSJdjc/RrSqDm8DUEq/h3FL73G/xvg9MbQj1J/h3hdlSIPcQXjrhL8hud/vyF0j -# IFaTK5YOcixkX++9t7Vz3Mn0KkQo8F4DNSyZEPpz682AyKKwLMJDy52pFFFKNP5l -# NpOz6YY1Od1xvk4nyN1WwfLnGswxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV -# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE -# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt -# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAASbfuksiuYKCBwAAAAABJjANBglghkgBZQME -# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ -# BDEiBCB0IE0Q6P23RQlh8TFyp57UQQUF/sbui7mOMStRgTFZxTCB+gYLKoZIhvcN -# AQkQAi8xgeowgecwgeQwgb0EIDb9z++evV5wDO9qk5ZnbEZ8CTOuR+kZyu8xbTsJ -# CXUPMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x -# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv -# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEm -# 37pLIrmCggcAAAAAASYwIgQgtwi02bvsGAOdpAxEF607G6g9PlyS8vc2bAUSHovH -# /IIwDQYJKoZIhvcNAQELBQAEggEAEMCfsXNudrjztjI6JNyNDVpdF1axRVcGiNy6 -# 67pgb1EePsjA2EaBB+5ZjgO/73JxuiVgsoXgH7em8tKG5RQJtcm5obVDb+jKksK4 -# qcFLA1f7seQRGfE06UAPnSFh2GqMtTNJGCXWwqWLH2LduTjOqPt8Nupo16ABFIT2 -# akTzBSJ81EHBkEU0Et6CgeaZiBYrCCXUtD+ASvLDkPSrjweQGu3Zk1SSROEzxMY9 -# jdlGfMkK2krMd9ub9UZ13RcQDijJqo+h6mz76pAuiFFvuQl6wMoSGFaaUQwfd+WQ -# gXlVVX/A9JFBihrxnDVglEPlsIOxCHkTeIxLfnAkCbax+9pevA== -# SIG # End signature block +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z +# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDipo0MMCIY +# DzIwMjAwNzAxMTIxODIwWhgPMjAyMDA3MDIxMjE4MjBaMHQwOgYKKwYBBAGEWQoE +# ATEsMCowCgIFAOKmjQwCAQAwBwIBAAICE70wBwIBAAICEeIwCgIFAOKn3owCAQAw +# NgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgC +# AQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQCOPjlHOH8nYtgt2XnpKXenxPUR03ED +# xPBm8XR5Z1vIq53RU9jG6yYcYNTdK+q38SGZtu0W/SgagTfKCQhjhRakuv7rGSs2 +# dlhx9LGCoc/q1vqmZpRSjkqWVcc/NzmldUWIWnLlV6rmLGoDmfCH5BcsiU6Eo6wU +# iUVwnnXoqsCaBzGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv +# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD +# QSAyMDEwAhMzAAABDKp4btzMQkzBAAAAAAEMMA0GCWCGSAFlAwQCAQUAoIIBSjAa +# BgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIDpwhjyu +# zgu3Kmxpnpz86ZlthBqEzG5vaEMOkYRyuFCaMIH6BgsqhkiG9w0BCRACLzGB6jCB +# 5zCB5DCBvQQgg5AWKX7M1+m2//+V7qmRvt1K/ww5Muu8XzGJBqygVCkwgZgwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAQyqeG7czEJMwQAA +# AAABDDAiBCD11urvv5vgo4gFVQ2NMVrzgxT87Yuiq16YdswYbaYeITANBgkqhkiG +# 9w0BAQsFAASCAQAi3q8hwcT2ft4b2EleaiyZxOImV/cKusmth1dtCh5/Jb0GbOld +# f5cSalrjf42MNPodWAtgmWozkYrQF6HxnsOiYiamfRA8E3E7xyRMy7AFfAhjcwMi +# xaW4Iye6E1Ec6LtULANxfDtG/KIdCWdZxKqOezL3nzFNQWmm1mXPV+UnKpnJkA3E +# DsQOUWk8J6ojDurhrP536WI+3arg8PcnppHBLd/xNKYdlsTb+6qndgzKXkDDt1CV +# 4zCyuZ7bO8eyZAmNoSZz22k7vus9UjBz/CDhXylo20N43nr29rWPItUgH4uvOGQn +# t26Y/yjBaQImz32psrfJEMbQ7cl789s8WOx8 +# SIG # End signature block \ No newline at end of file diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs index 1dfee2252ad..a48540ea654 100644 --- a/src/Test/L0/Worker/StepsRunnerL0.cs +++ b/src/Test/L0/Worker/StepsRunnerL0.cs @@ -426,11 +426,11 @@ public async Task StepEnvOverrideJobEnvContext() 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")); + Assert.Equal("100", step1.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("100")); + Assert.Equal("github_actions", step1.Object.ExecutionContext.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")); + Assert.Equal("100", step1.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("100")); + Assert.Equal("github_actions", step1.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env2"].AssertString("github_actions")); #endif } } @@ -463,13 +463,13 @@ public async Task PopulateEnvContextForEachStep() // 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")); + Assert.Equal("1000", step2.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("1000")); + Assert.Equal("github_actions", step2.Object.ExecutionContextExpressionValues["env"].AssertDictionary("env")["env3"].AssertString("github_actions")); + Assert.False(step2.Object.ExecutionContext.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")); + Assert.Equal("1000", step2.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("1000")); + Assert.Equal("github_actions", step2.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env3"].AssertString("github_actions")); + Assert.False(step2.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env").ContainsKey("env2")); #endif } } @@ -501,11 +501,11 @@ public async Task PopulateEnvContextAfterSetupStepsContext() // 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("something", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env2"].AssertString("something")); + Assert.Equal("1000", step2.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("1000")); + Assert.Equal("something", step2.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env")["env2"].AssertString("something")); #else - Assert.Equal("1000", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("1000")); - Assert.Equal("something", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env2"].AssertString("something")); + Assert.Equal("1000", step2.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("1000")); + Assert.Equal("something", step2.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env2"].AssertString("something")); #endif } } @@ -602,7 +602,7 @@ private Mock CreateStep(TestHostContext hc, TaskResult result, st stepContext.Setup(x => x.WriteDebug).Returns(true); 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.ExpressionValues).Returns(new DictionaryContextData()); stepContext.Setup(x => x.ExpressionFunctions).Returns(new List()); stepContext.Setup(x => x.JobContext).Returns(_jobContext); stepContext.Setup(x => x.StepsContext).Returns(_stepContext); From 881e8e72a38e3c7135ba72962d3679f7903b48a1 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Wed, 8 Jul 2020 10:06:03 -0400 Subject: [PATCH 31/31] Fix windows unit test syntax error --- src/Test/L0/Worker/StepsRunnerL0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs index a48540ea654..2fde1bcb675 100644 --- a/src/Test/L0/Worker/StepsRunnerL0.cs +++ b/src/Test/L0/Worker/StepsRunnerL0.cs @@ -464,7 +464,7 @@ public async Task PopulateEnvContextForEachStep() Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded); #if OS_WINDOWS Assert.Equal("1000", step2.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("1000")); - Assert.Equal("github_actions", step2.Object.ExecutionContextExpressionValues["env"].AssertDictionary("env")["env3"].AssertString("github_actions")); + Assert.Equal("github_actions", step2.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env")["env3"].AssertString("github_actions")); Assert.False(step2.Object.ExecutionContext.ExpressionValues["env"].AssertDictionary("env").ContainsKey("env2")); #else Assert.Equal("1000", step2.Object.ExecutionContext.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("1000"));