Files
runner/src/Runner.Worker/Handlers/Handler.cs
Ferenc Hammerl 7d5e9cd70f Runner Job Started/Completed Hooks (#1737)
* Prototype for pre job hook

* Remove debug log

* Enable hooks again

* Initialize with hostContext

* Add event_path, fix no-path bug

* Allow script post steps

* Call script handler with correct pre post stage

* Add job completed hook

* Make filecommand work and hardcode shell

* Conditionally print step details and no telemetry for hooks

* Figure out whih script to use

* Only check path for managed scripts

* Resture win dependency

* Nits

* Remove unused, add named params

* Telemetry + refactoring

* add message to job

* rename hooks remove stale comment

* cleanup

* Use .CreateService to create step

* Add L0s

* pr feedback

* update tests

* add disclaimer, clean up code

* spacing fix

* little more cleanup

* pr fix

* pr feedback

* Refactor to use JobExtension

* fix tests

* fix typo

* cleanup code

* more cleanup

* little more cleanup

* last bit of cleanup

* fix tests

* nit fix

* Update src/Runner.Worker/JobHookProvider.cs

Co-authored-by: Edward Thomson <ethomson@github.com>

* don't override runner telemtry

* pr feedback

* pr feedback

* pr feedback

Co-authored-by: Thomas Boop <thboop@github.com>
Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
Co-authored-by: Edward Thomson <ethomson@github.com>
2022-03-17 21:35:04 -04:00

236 lines
9.1 KiB
C#

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<string, string> Environment { get; set; }
IExecutionContext ExecutionContext { get; set; }
Variables RuntimeVariables { get; set; }
IStepHost StepHost { get; set; }
Dictionary<string, string> Inputs { get; set; }
string ActionDirectory { get; set; }
List<JobExtensionRunner> 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 bool IsActionStep => Action != null;
public Dictionary<string, string> Environment { get; set; }
public Variables RuntimeVariables { get; set; }
public IExecutionContext ExecutionContext { get; set; }
public IStepHost StepHost { get; set; }
public Dictionary<string, string> Inputs { get; set; }
public string ActionDirectory { get; set; }
public List<JobExtensionRunner> LocalActionContainerSetupSteps { get; set; }
public void PrepareExecution(ActionRunStage stage)
{
// Print out action details
PrintActionDetails(stage);
// Get telemetry for the action
PopulateActionTelemetry(stage);
}
protected void PopulateActionTelemetry(ActionRunStage stage)
{
if (!IsActionStep)
{
ExecutionContext.StepTelemetry.Type = "runner";
ExecutionContext.StepTelemetry.Action = $"{stage} Job Hook";
}
else 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<IActionCommandManager>();
}
protected void AddInputsToEnvironment()
{
// Validate args.
Trace.Entering();
ArgUtil.NotNull(Inputs, nameof(Inputs));
// Add the inputs to the environment variable dictionary.
foreach (KeyValuePair<string, string> 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<string>());
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);
}
}
}
}