/// <summary> /// 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. /// </summary> public IStep RegisterNestedStep( IActionRunner step, DictionaryContextData inputsData, int location, Dictionary <string, string> envData, bool cleanUp = false) { // If the context name is empty and the scope name is empty, we would generate a unique scope name for this child in the following format: // "__<GUID>" var safeContextName = !string.IsNullOrEmpty(ContextName) ? ContextName : $"__{Guid.NewGuid()}"; // Set Scope Name. Note, for our design, we consider each step in a composite action to have the same scope // This makes it much simpler to handle their outputs at the end of the Composite Action var childScopeName = !string.IsNullOrEmpty(ScopeName) ? $"{ScopeName}.{safeContextName}" : safeContextName; var childContextName = !string.IsNullOrEmpty(step.Action.ContextName) ? step.Action.ContextName : $"__{Guid.NewGuid()}"; step.ExecutionContext = Root.CreateChild(_record.Id, step.DisplayName, _record.Id.ToString("N"), childScopeName, childContextName, logger: _logger); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; // Set Parent Attribute for Clean Up Step if (cleanUp) { step.ExecutionContext.FinalizeContext = this; } // Add the composite action environment variables to each step. #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; Root.JobSteps.Insert(location, step); return(step); }
/// <summary> /// 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. /// </summary> public IStep CreateCompositeStep( string scopeName, IActionRunner step, DictionaryContextData inputsData, Dictionary <string, string> envData) { step.ExecutionContext = Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, step.Action.ContextName, logger: _logger, insideComposite: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token)); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; step.ExecutionContext.ExpressionValues["steps"] = Global.StepsContext.GetScope(step.ExecutionContext.GetFullyQualifiedContextName()); // Add the composite action environment variables to each step. #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; return(step); }
public void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) { Trace.Entering(); ArgUtil.NotNull(jobContext, nameof(jobContext)); // create a new timeline record node for 'Finalize job' IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null); using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); })) { try { context.Start(); context.Debug("Starting: Complete job"); Trace.Info("Initialize Env context"); #if OS_WINDOWS var envContext = new DictionaryContextData(); #else var envContext = new CaseSensitiveDictionaryContextData(); #endif context.ExpressionValues["env"] = envContext; foreach (var pair in context.Global.EnvironmentVariables) { envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); } // Populate env context for each step Trace.Info("Initialize steps context"); context.ExpressionValues["steps"] = context.Global.StepsContext.GetScope(context.ScopeName); var templateEvaluator = context.ToPipelineTemplateEvaluator(); // Evaluate job outputs if (message.JobOutputs != null && message.JobOutputs.Type != TokenType.Null) { try { context.Output($"Evaluate and set job outputs"); // Populate env context for each step Trace.Info("Initialize Env context for evaluating job outputs"); var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues, context.ExpressionFunctions); foreach (var output in outputs) { if (string.IsNullOrEmpty(output.Value)) { context.Debug($"Skip output '{output.Key}' since it's empty"); continue; } if (!string.Equals(output.Value, HostContext.SecretMasker.MaskSecrets(output.Value))) { context.Warning($"Skip output '{output.Key}' since it may contain secret."); continue; } context.Output($"Set output '{output.Key}'"); jobContext.JobOutputs[output.Key] = output.Value; } } catch (Exception ex) { context.Result = TaskResult.Failed; context.Error($"Fail to evaluate job outputs"); context.Error(ex); jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, TaskResult.Failed); } } // Evaluate environment data if (jobContext.ActionsEnvironment?.Url != null && jobContext.ActionsEnvironment?.Url.Type != TokenType.Null) { try { context.Output($"Evaluate and set environment url"); var environmentUrlToken = templateEvaluator.EvaluateEnvironmentUrl(jobContext.ActionsEnvironment.Url, context.ExpressionValues, context.ExpressionFunctions); var environmentUrl = environmentUrlToken.AssertString("environment.url"); if (!string.Equals(environmentUrl.Value, HostContext.SecretMasker.MaskSecrets(environmentUrl.Value))) { context.Warning($"Skip setting environment url as environment '{jobContext.ActionsEnvironment.Name}' may contain secret."); } else { context.Output($"Evaluated environment url: {environmentUrl}"); jobContext.ActionsEnvironment.Url = environmentUrlToken; } } catch (Exception ex) { context.Result = TaskResult.Failed; context.Error($"Failed to evaluate environment url"); context.Error(ex); jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, TaskResult.Failed); } } if (context.Global.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false) { Trace.Info("Support log upload starting."); context.Output("Uploading runner diagnostic logs"); IDiagnosticLogManager diagnosticLogManager = HostContext.GetService <IDiagnosticLogManager>(); try { diagnosticLogManager.UploadDiagnosticLogs(executionContext: context, parentContext: jobContext, message: message, jobStartTimeUtc: jobStartTimeUtc); Trace.Info("Support log upload complete."); context.Output("Completed runner diagnostic log upload"); } catch (Exception ex) { // Log the error but make sure we continue gracefully. Trace.Info("Error uploading support logs."); context.Output("Error uploading runner diagnostic logs"); Trace.Error(ex); } } if (_processCleanup) { context.Output("Cleaning up orphan processes"); // Only check environment variable for any process that doesn't run before we invoke our process. Dictionary <int, Process> currentProcesses = SnapshotProcesses(); foreach (var proc in currentProcesses) { if (proc.Key == Process.GetCurrentProcess().Id) { // skip for current process. continue; } if (_existingProcesses.Contains($"{proc.Key}_{proc.Value.ProcessName}")) { Trace.Verbose($"Skip existing process. PID: {proc.Key} ({proc.Value.ProcessName})"); } else { Trace.Info($"Inspecting process environment variables. PID: {proc.Key} ({proc.Value.ProcessName})"); string lookupId = null; try { lookupId = proc.Value.GetEnvironmentVariable(HostContext, Constants.ProcessTrackingId); } catch (Exception ex) { Trace.Warning($"Ignore exception during read process environment variables: {ex.Message}"); Trace.Verbose(ex.ToString()); } if (string.Equals(lookupId, _processLookupId, StringComparison.OrdinalIgnoreCase)) { context.Output($"Terminate orphan process: pid ({proc.Key}) ({proc.Value.ProcessName})"); try { proc.Value.Kill(); } catch (Exception ex) { Trace.Error("Catch exception during orphan process cleanup."); Trace.Error(ex); } } } } } if (_diskSpaceCheckTask != null) { _diskSpaceCheckToken.Cancel(); } } catch (Exception ex) { // Log and ignore the error from JobExtension finalization. Trace.Error($"Caught exception from JobExtension finalization: {ex}"); context.Output(ex.Message); } finally { context.Debug("Finishing: Complete job"); context.Complete(); } } }
private void Setup([CallerMemberName] string name = "") { _ecTokenSource?.Dispose(); _ecTokenSource = new CancellationTokenSource(); // Test host context. _hc = new TestHostContext(this, name); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "input1")); actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "")); actionInputs.Add(new StringToken(null, null, null, "input3"), new StringToken(null, null, null, "github")); var actionDefinition = new Definition() { Directory = _hc.GetDirectory(WellKnownDirectory.Work), Data = new ActionDefinitionData() { Name = name, Description = name, Inputs = actionInputs, Execution = new ScriptActionExecutionData() } }; // Mocks. _actionManager = new Mock <IActionManager>(); _actionManager.Setup(x => x.LoadAction(It.IsAny <IExecutionContext>(), It.IsAny <ActionStep>())).Returns(actionDefinition); _handlerFactory = new Mock <IHandlerFactory>(); _defaultStepHost = new Mock <IDefaultStepHost>(); _actionManifestManager = new ActionManifestManager(); _actionManifestManager.Initialize(_hc); var githubContext = new GitHubContext(); githubContext.Add("event", JToken.Parse("{\"foo\":\"bar\"}").ToPipelineContextData()); _context.Add("github", githubContext); #if OS_WINDOWS _context["env"] = new DictionaryContextData(); #else _context["env"] = new CaseSensitiveDictionaryContextData(); #endif _ec = new Mock <IExecutionContext>(); _ec.Setup(x => x.ExpressionValues).Returns(_context); _ec.Setup(x => x.ExpressionFunctions).Returns(new List <IFunctionInfo>()); _ec.Setup(x => x.IntraActionState).Returns(new Dictionary <string, string>()); _ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary <string, string>()); _ec.Setup(x => x.FileTable).Returns(new List <String>()); _ec.Setup(x => x.SetGitHubContext(It.IsAny <string>(), It.IsAny <string>())); _ec.Setup(x => x.GetGitHubContext(It.IsAny <string>())).Returns("{\"foo\":\"bar\"}"); _ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token); _ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary <string, VariableValue>())); _ec.Setup(x => x.Write(It.IsAny <string>(), It.IsAny <string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); }); _ec.Setup(x => x.AddIssue(It.IsAny <Issue>(), It.IsAny <string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); }); _hc.SetSingleton <IActionManager>(_actionManager.Object); _hc.SetSingleton <IHandlerFactory>(_handlerFactory.Object); _hc.SetSingleton <IActionManifestManager>(_actionManifestManager); _hc.EnqueueInstance <IDefaultStepHost>(_defaultStepHost.Object); // Instance to test. _actionRunner = new ActionRunner(); _actionRunner.Initialize(_hc); _actionRunner.ExecutionContext = _ec.Object; }
public void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token) { // Validation Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Resources, nameof(message.Resources)); ArgUtil.NotNull(message.Variables, nameof(message.Variables)); ArgUtil.NotNull(message.Plan, nameof(message.Plan)); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token); Global = new GlobalContext(); // Plan Global.Plan = message.Plan; Global.Features = PlanUtil.GetFeatures(message.Plan); // Endpoints Global.Endpoints = message.Resources.Endpoints; // Variables Global.Variables = new Variables(HostContext, message.Variables); // Environment variables shared across all actions Global.EnvironmentVariables = new Dictionary <string, string>(VarUtil.EnvironmentVariableKeyComparer); // Job defaults shared across all actions Global.JobDefaults = new Dictionary <string, IDictionary <string, string> >(StringComparer.OrdinalIgnoreCase); // Job Outputs JobOutputs = new Dictionary <string, VariableValue>(StringComparer.OrdinalIgnoreCase); // Actions environment ActionsEnvironment = message.ActionsEnvironment; // Service container info Global.ServiceContainers = new List <ContainerInfo>(); // Steps context (StepsRunner manages adding the scoped steps context) Global.StepsContext = new StepsContext(); // File table Global.FileTable = new List <String>(message.FileTable ?? new string[0]); // Expression values if (message.ContextData?.Count > 0) { foreach (var pair in message.ContextData) { ExpressionValues[pair.Key] = pair.Value; } } ExpressionValues["secrets"] = Global.Variables.ToSecretsContext(); ExpressionValues["runner"] = new RunnerContext(); ExpressionValues["job"] = new JobContext(); Trace.Info("Initialize GitHub context"); var githubAccessToken = new StringContextData(Global.Variables.Get("system.github.token")); var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}")); HostContext.SecretMasker.AddValue(base64EncodedToken); var githubJob = Global.Variables.Get("system.github.job"); var githubContext = new GitHubContext(); githubContext["token"] = githubAccessToken; if (!string.IsNullOrEmpty(githubJob)) { githubContext["job"] = new StringContextData(githubJob); } var githubDictionary = ExpressionValues["github"].AssertDictionary("github"); foreach (var pair in githubDictionary) { githubContext[pair.Key] = pair.Value; } ExpressionValues["github"] = githubContext; Trace.Info("Initialize Env context"); #if OS_WINDOWS ExpressionValues["env"] = new DictionaryContextData(); #else ExpressionValues["env"] = new CaseSensitiveDictionaryContextData(); #endif // Prepend Path Global.PrependPath = new List <string>(); // JobSteps for job ExecutionContext JobSteps = new Queue <IStep>(); // PostJobSteps for job ExecutionContext PostJobSteps = new Stack <IStep>(); // StepsWithPostRegistered for job ExecutionContext StepsWithPostRegistered = new HashSet <Guid>(); // Job timeline record. InitializeTimelineRecord( timelineId: message.Timeline.Id, timelineRecordId: message.JobId, parentTimelineRecordId: null, recordType: ExecutionContextType.Job, displayName: message.JobDisplayName, refName: message.JobName, order: null); // The job timeline record's order is set by server. // Logger (must be initialized before writing warnings). _logger = HostContext.CreateService <IPagingLogger>(); _logger.Setup(_mainTimelineId, _record.Id); // Initialize 'echo on action command success' property, default to false, unless Step_Debug is set EchoOnActionCommand = Global.Variables.Step_Debug ?? false; // Verbosity (from GitHub.Step_Debug). Global.WriteDebug = Global.Variables.Step_Debug ?? false; // Hook up JobServerQueueThrottling event, we will log warning on server tarpit. _jobServerQueue.JobServerQueueThrottling += JobServerQueueThrottling_EventReceived; }
// StepsRunner should never throw exception to caller public async Task RunAsync(IExecutionContext jobContext) { ArgUtil.NotNull(jobContext, nameof(jobContext)); ArgUtil.NotNull(jobContext.JobSteps, nameof(jobContext.JobSteps)); // TaskResult: // Abandoned (Server set this.) // Canceled // Failed // Skipped // Succeeded CancellationTokenRegistration?jobCancelRegister = null; jobContext.JobContext.Status = (jobContext.Result ?? TaskResult.Succeeded).ToActionResult(); var scopeInputs = new Dictionary <string, PipelineContextData>(StringComparer.OrdinalIgnoreCase); bool checkPostJobActions = false; while (jobContext.JobSteps.Count > 0 || !checkPostJobActions) { if (jobContext.JobSteps.Count == 0 && !checkPostJobActions) { checkPostJobActions = true; while (jobContext.PostJobSteps.TryPop(out var postStep)) { jobContext.JobSteps.Add(postStep); } continue; } var step = jobContext.JobSteps[0]; jobContext.JobSteps.RemoveAt(0); Trace.Info($"Processing step: DisplayName='{step.DisplayName}'"); ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext)); ArgUtil.NotNull(step.ExecutionContext.Variables, nameof(step.ExecutionContext.Variables)); // Start step.ExecutionContext.Start(); // Expression functions step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <FailureFunction>(PipelineTemplateConstants.Failure, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <SuccessFunction>(PipelineTemplateConstants.Success, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue)); step.ExecutionContext.ExpressionValues["steps"] = step.ExecutionContext.StepsContext.GetScope(step.ExecutionContext.ScopeName); // Populate env context for each step Trace.Info("Initialize Env context for step"); #if OS_WINDOWS var envContext = new DictionaryContextData(); #else var envContext = new CaseSensitiveDictionaryContextData(); #endif // 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) { // Set GITHUB_ACTION step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name); try { // Evaluate and merge action's env block to env context var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer); foreach (var env in actionEnvironment) { envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); } } catch (Exception ex) { // fail the step since there is an evaluate error. Trace.Info("Caught exception from expression for step.env"); evaluateStepEnvFailed = true; step.ExecutionContext.Error(ex); CompleteStep(step, TaskResult.Failed); } } if (!evaluateStepEnvFailed) { try { // Register job cancellation call back only if job cancellation token not been fire before each step run if (!jobContext.CancellationToken.IsCancellationRequested) { // Test the condition again. The job was canceled after the condition was originally evaluated. jobCancelRegister = jobContext.CancellationToken.Register(() => { // mark job as cancelled jobContext.Result = TaskResult.Canceled; jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'."); var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only var conditionReTestResult = false; if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown."); } else { try { var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter); var condition = new BasicExpressionToken(null, null, null, step.Condition); conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState()); } catch (Exception ex) { // Cancel the step since we get exception while re-evaluate step condition. Trace.Info("Caught exception from expression when re-test condition on job cancellation."); step.ExecutionContext.Error(ex); } } if (!conditionReTestResult) { // Cancel the step. Trace.Info("Cancel current running step."); step.ExecutionContext.CancelToken(); } }); } else { if (jobContext.Result != TaskResult.Canceled) { // mark job as cancelled jobContext.Result = TaskResult.Canceled; jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); } } // Evaluate condition. step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'"); var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext); var conditionResult = false; var conditionEvaluateError = default(Exception); if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown."); } else { try { var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter); var condition = new BasicExpressionToken(null, null, null, step.Condition); conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState()); } catch (Exception ex) { Trace.Info("Caught exception from expression."); Trace.Error(ex); conditionEvaluateError = ex; } } // no evaluate error but condition is false if (!conditionResult && conditionEvaluateError == null) { // Condition == false Trace.Info("Skipping step due to condition evaluation."); CompleteStep(step, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace); } else if (conditionEvaluateError != null) { // fail the step since there is an evaluate error. step.ExecutionContext.Error(conditionEvaluateError); CompleteStep(step, TaskResult.Failed); } else { // Run the step. await RunStepAsync(step, jobContext.CancellationToken); CompleteStep(step); } } finally { if (jobCancelRegister != null) { jobCancelRegister?.Dispose(); jobCancelRegister = null; } } } // Update the job result. if (step.ExecutionContext.Result == TaskResult.Failed) { Trace.Info($"Update job result with current step result '{step.ExecutionContext.Result}'."); jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, step.ExecutionContext.Result.Value); jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); } else { Trace.Info($"No need for updating job result with current step result '{step.ExecutionContext.Result}'."); } Trace.Info($"Current state: job state = '{jobContext.Result}'"); } }
private async Task RunStepsAsync(List <IStep> embeddedSteps, ActionRunStage stage) { ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps)); foreach (IStep step in embeddedSteps) { Trace.Info($"Processing embedded step: DisplayName='{step.DisplayName}'"); // Add Expression Functions step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <FailureFunction>(PipelineTemplateConstants.Failure, 0, 0)); step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo <SuccessFunction>(PipelineTemplateConstants.Success, 0, 0)); // Set action_status to the success of the current composite action var actionResult = ExecutionContext.Result?.ToActionResult() ?? ActionResult.Success; step.ExecutionContext.SetGitHubContext("action_status", actionResult.ToString()); // Initialize env context Trace.Info("Initialize Env context for embedded step"); #if OS_WINDOWS var envContext = new DictionaryContextData(); #else var envContext = new CaseSensitiveDictionaryContextData(); #endif step.ExecutionContext.ExpressionValues["env"] = envContext; // Merge global env foreach (var pair in ExecutionContext.Global.EnvironmentVariables) { envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); } // Merge composite-step env if (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; } } try { if (step is IActionRunner actionStep) { // Evaluate and merge embedded-step env var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, Common.Util.VarUtil.EnvironmentVariableKeyComparer); foreach (var env in actionEnvironment) { envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); } } } catch (Exception ex) { // Evaluation error Trace.Info("Caught exception from expression for embedded step.env"); step.ExecutionContext.Error(ex); step.ExecutionContext.Complete(TaskResult.Failed); } // Register Callback CancellationTokenRegistration?jobCancelRegister = null; try { // Register job cancellation call back only if job cancellation token not been fire before each step run if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested) { // Test the condition again. The job was canceled after the condition was originally evaluated. jobCancelRegister = ExecutionContext.Root.CancellationToken.Register(() => { // Mark job as cancelled ExecutionContext.Root.Result = TaskResult.Canceled; ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult(); step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'."); var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only var conditionReTestResult = false; if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown."); } else { try { var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter); var condition = new BasicExpressionToken(null, null, null, step.Condition); conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState()); } catch (Exception ex) { // Cancel the step since we get exception while re-evaluate step condition Trace.Info("Caught exception from expression when re-test condition on job cancellation."); step.ExecutionContext.Error(ex); } } if (!conditionReTestResult) { // Cancel the step Trace.Info("Cancel current running step."); step.ExecutionContext.CancelToken(); } }); } else { if (ExecutionContext.Root.Result != TaskResult.Canceled) { // Mark job as cancelled ExecutionContext.Root.Result = TaskResult.Canceled; ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult(); } } // Evaluate condition step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'"); var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext); var conditionResult = false; var conditionEvaluateError = default(Exception); if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown."); } else { try { var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter); var condition = new BasicExpressionToken(null, null, null, step.Condition); conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState()); } catch (Exception ex) { Trace.Info("Caught exception from expression."); Trace.Error(ex); conditionEvaluateError = ex; } } if (!conditionResult && conditionEvaluateError == null) { // Condition is false Trace.Info("Skipping step due to condition evaluation."); step.ExecutionContext.Result = TaskResult.Skipped; continue; } else if (conditionEvaluateError != null) { // Condition error step.ExecutionContext.Error(conditionEvaluateError); step.ExecutionContext.Result = TaskResult.Failed; ExecutionContext.Result = TaskResult.Failed; break; } else { await RunStepAsync(step); } } finally { if (jobCancelRegister != null) { jobCancelRegister?.Dispose(); jobCancelRegister = null; } } // Check failed or canceled if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled) { Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'."); ExecutionContext.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Result, step.ExecutionContext.Result.Value); } } }
// StepsRunner should never throw exception to caller public async Task RunAsync(IExecutionContext jobContext) { ArgUtil.NotNull(jobContext, nameof(jobContext)); ArgUtil.NotNull(jobContext.JobSteps, nameof(jobContext.JobSteps)); // TaskResult: // Abandoned (Server set this.) // Canceled // Failed // Skipped // Succeeded CancellationTokenRegistration?jobCancelRegister = null; jobContext.JobContext.Status = (jobContext.Result ?? TaskResult.Succeeded).ToActionResult(); var scopeInputs = new Dictionary <string, PipelineContextData>(StringComparer.OrdinalIgnoreCase); bool checkPostJobActions = false; while (jobContext.JobSteps.Count > 0 || !checkPostJobActions) { if (jobContext.JobSteps.Count == 0 && !checkPostJobActions) { checkPostJobActions = true; while (jobContext.PostJobSteps.TryPop(out var postStep)) { jobContext.JobSteps.Enqueue(postStep); } continue; } var step = jobContext.JobSteps.Dequeue(); IStep nextStep = null; if (jobContext.JobSteps.Count > 0) { nextStep = jobContext.JobSteps.Peek(); } Trace.Info($"Processing step: DisplayName='{step.DisplayName}'"); ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext)); ArgUtil.NotNull(step.ExecutionContext.Variables, nameof(step.ExecutionContext.Variables)); // Start step.ExecutionContext.Start(); // Initialize scope if (InitializeScope(step, scopeInputs)) { // Populate env context for each step Trace.Info("Initialize Env context for step"); #if OS_WINDOWS var envContext = new DictionaryContextData(); #else var envContext = new CaseSensitiveDictionaryContextData(); #endif step.ExecutionContext.ExpressionValues["env"] = envContext; foreach (var pair in step.ExecutionContext.EnvironmentVariables) { envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); } if (step is IActionRunner actionStep) { // Set GITHUB_ACTION step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name); // Evaluate and merge action's env block to env context var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer); foreach (var env in actionEnvironment) { envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); } } var expressionManager = HostContext.GetService <IExpressionManager>(); try { // Register job cancellation call back only if job cancellation token not been fire before each step run if (!jobContext.CancellationToken.IsCancellationRequested) { // Test the condition again. The job was canceled after the condition was originally evaluated. jobCancelRegister = jobContext.CancellationToken.Register(() => { // mark job as cancelled jobContext.Result = TaskResult.Canceled; jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'."); ConditionResult conditionReTestResult; if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown."); conditionReTestResult = false; } else { try { conditionReTestResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition, hostTracingOnly: true); } catch (Exception ex) { // Cancel the step since we get exception while re-evaluate step condition. Trace.Info("Caught exception from expression when re-test condition on job cancellation."); step.ExecutionContext.Error(ex); conditionReTestResult = false; } } if (!conditionReTestResult.Value) { // Cancel the step. Trace.Info("Cancel current running step."); step.ExecutionContext.CancelToken(); } }); } else { if (jobContext.Result != TaskResult.Canceled) { // mark job as cancelled jobContext.Result = TaskResult.Canceled; jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); } } // Evaluate condition. step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'"); Exception conditionEvaluateError = null; ConditionResult conditionResult; if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown."); conditionResult = false; } else { try { conditionResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition); } catch (Exception ex) { Trace.Info("Caught exception from expression."); Trace.Error(ex); conditionResult = false; conditionEvaluateError = ex; } } // no evaluate error but condition is false if (!conditionResult.Value && conditionEvaluateError == null) { // Condition == false Trace.Info("Skipping step due to condition evaluation."); CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionResult.Trace); } else if (conditionEvaluateError != null) { // fail the step since there is an evaluate error. step.ExecutionContext.Error(conditionEvaluateError); CompleteStep(step, nextStep, TaskResult.Failed); } else { // Run the step. await RunStepAsync(step, jobContext.CancellationToken); CompleteStep(step, nextStep); } } finally { if (jobCancelRegister != null) { jobCancelRegister?.Dispose(); jobCancelRegister = null; } } } // Update the job result. if (step.ExecutionContext.Result == TaskResult.Failed) { Trace.Info($"Update job result with current step result '{step.ExecutionContext.Result}'."); jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, step.ExecutionContext.Result.Value); jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); } else { Trace.Info($"No need for updating job result with current step result '{step.ExecutionContext.Result}'."); } Trace.Info($"Current state: job state = '{jobContext.Result}'"); } }
private async Task RunStepsAsync(List <IStep> compositeSteps) { ArgUtil.NotNull(compositeSteps, nameof(compositeSteps)); // The parent StepsRunner of the whole Composite Action Step handles the cancellation stuff already. foreach (IStep step in compositeSteps) { Trace.Info($"Processing composite step: DisplayName='{step.DisplayName}'"); step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(step.ExecutionContext.ScopeName); // Populate env context for each step Trace.Info("Initialize Env context for step"); #if OS_WINDOWS var envContext = new DictionaryContextData(); #else var envContext = new CaseSensitiveDictionaryContextData(); #endif // Global env foreach (var pair in ExecutionContext.Global.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; var actionStep = step as IActionRunner; // Set GITHUB_ACTION step.ExecutionContext.SetGitHubContext("action", step.ExecutionContext.GetFullyQualifiedContextName()); try { // Evaluate and merge action's env block to env context var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, Common.Util.VarUtil.EnvironmentVariableKeyComparer); foreach (var env in actionEnvironment) { envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); } } catch (Exception ex) { // fail the step since there is an evaluate error. Trace.Info("Caught exception in Composite Steps Runner from expression for step.env"); // evaluateStepEnvFailed = true; step.ExecutionContext.Error(ex); step.ExecutionContext.Complete(TaskResult.Failed); } await RunStepAsync(step); // Directly after the step, check if the step has failed or cancelled // If so, return that to the output if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled) { ExecutionContext.Result = step.ExecutionContext.Result; break; } // TODO: Add compat for other types of steps. } // Completion Status handled by StepsRunner for the whole Composite Action Step }