using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common; using GitHub.Runner.Common.Util; using GitHub.Runner.Sdk; using Pipelines = GitHub.DistributedTask.Pipelines; namespace GitHub.Runner.Worker.Handlers { public interface IHandler : IRunnerService { Pipelines.ActionStepDefinitionReference Action { get; set; } Dictionary Environment { get; set; } IExecutionContext ExecutionContext { get; set; } Variables RuntimeVariables { get; set; } IStepHost StepHost { get; set; } Dictionary Inputs { get; set; } string ActionDirectory { get; set; } List LocalActionContainerSetupSteps { get; set; } Task RunAsync(ActionRunStage stage); void PrepareExecution(ActionRunStage stage); } public abstract class Handler : RunnerService { #if OS_WINDOWS // In windows OS the maximum supported size of a environment variable value is 32k. // You can set environment variable greater then 32K, but that variable will not be able to read in node.exe. private const int _environmentVariableMaximumSize = 32766; #endif protected IActionCommandManager ActionCommandManager { get; private set; } public Pipelines.ActionStepDefinitionReference Action { get; set; } public Dictionary Environment { get; set; } public Variables RuntimeVariables { get; set; } public IExecutionContext ExecutionContext { get; set; } public IStepHost StepHost { get; set; } public Dictionary Inputs { get; set; } public string ActionDirectory { get; set; } public List LocalActionContainerSetupSteps { get; set; } public void PrepareExecution(ActionRunStage stage) { // Print out action details PrintActionDetails(stage); // Get telemetry for the action. PopulateActionTelemetry(); } protected void PopulateActionTelemetry() { if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry) { ExecutionContext.StepTelemetry.Type = "docker"; var registryAction = Action as Pipelines.ContainerRegistryReference; ExecutionContext.StepTelemetry.Action = registryAction.Image; } else if (Action.Type == Pipelines.ActionSourceType.Script) { ExecutionContext.StepTelemetry.Type = "run"; } else if (Action.Type == Pipelines.ActionSourceType.Repository) { ExecutionContext.StepTelemetry.Type = "repository"; var repoAction = Action as Pipelines.RepositoryPathReference; if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase)) { ExecutionContext.StepTelemetry.Action = repoAction.Path; } else { ExecutionContext.StepTelemetry.Ref = repoAction.Ref; if (string.IsNullOrEmpty(repoAction.Path)) { ExecutionContext.StepTelemetry.Action = repoAction.Name; } else { ExecutionContext.StepTelemetry.Action = $"{repoAction.Name}/{repoAction.Path}"; } } } else { // this should never happen Trace.Error($"Can't generate ref for {Action.Type.ToString()}"); } } protected virtual void PrintActionDetails(ActionRunStage stage) { if (stage == ActionRunStage.Post) { ExecutionContext.Output($"Post job cleanup."); return; } string groupName = ""; if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry) { var registryAction = Action as Pipelines.ContainerRegistryReference; groupName = $"Run docker://{registryAction.Image}"; } else if (Action.Type == Pipelines.ActionSourceType.Repository) { var repoAction = Action as Pipelines.RepositoryPathReference; if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase)) { groupName = $"Run {repoAction.Path}"; } else { if (string.IsNullOrEmpty(repoAction.Path)) { groupName = $"Run {repoAction.Name}@{repoAction.Ref}"; } else { groupName = $"Run {repoAction.Name}/{repoAction.Path}@{repoAction.Ref}"; } } } else { // this should never happen Trace.Error($"Can't generate default folding group name for action {Action.Type.ToString()}"); groupName = "Action details"; } ExecutionContext.Output($"##[group]{groupName}"); if (this.Inputs?.Count > 0) { ExecutionContext.Output("with:"); foreach (var input in this.Inputs) { if (!string.IsNullOrEmpty(input.Value)) { ExecutionContext.Output($" {input.Key}: {input.Value}"); } } } if (this.Environment?.Count > 0) { ExecutionContext.Output("env:"); foreach (var env in this.Environment) { ExecutionContext.Output($" {env.Key}: {env.Value}"); } } ExecutionContext.Output("##[endgroup]"); } public override void Initialize(IHostContext hostContext) { base.Initialize(hostContext); ActionCommandManager = hostContext.CreateService(); } protected void AddInputsToEnvironment() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Inputs, nameof(Inputs)); // Add the inputs to the environment variable dictionary. foreach (KeyValuePair pair in Inputs) { AddEnvironmentVariable( key: $"INPUT_{pair.Key?.Replace(' ', '_').ToUpperInvariant()}", value: pair.Value); } } protected void AddEnvironmentVariable(string key, string value) { ArgUtil.NotNullOrEmpty(key, nameof(key)); Trace.Verbose($"Setting env '{key}' to '{value}'."); Environment[key] = value ?? string.Empty; #if OS_WINDOWS if (Environment[key].Length > _environmentVariableMaximumSize) { ExecutionContext.Warning($"Environment variable '{key}' exceeds the maximum supported length. Environment variable length: {value.Length} , Maximum supported length: {_environmentVariableMaximumSize}"); } #endif } protected void AddPrependPathToEnvironment() { // Validate args. Trace.Entering(); ArgUtil.NotNull(ExecutionContext.Global.PrependPath, nameof(ExecutionContext.Global.PrependPath)); if (ExecutionContext.Global.PrependPath.Count == 0) { return; } // Prepend path. string prepend = string.Join(Path.PathSeparator.ToString(), ExecutionContext.Global.PrependPath.Reverse()); var containerStepHost = StepHost as ContainerStepHost; if (containerStepHost != null) { containerStepHost.PrependPath = prepend; } else { string taskEnvPATH; Environment.TryGetValue(Constants.PathVariable, out taskEnvPATH); string originalPath = RuntimeVariables.Get(Constants.PathVariable) ?? // Prefer a job variable. taskEnvPATH ?? // Then a task-environment variable. System.Environment.GetEnvironmentVariable(Constants.PathVariable) ?? // Then an environment variable. string.Empty; string newPath = PathUtil.PrependPath(prepend, originalPath); AddEnvironmentVariable(Constants.PathVariable, newPath); } } } }