Skip to content

Commit 050c706

Browse files
Go actions via feature flag (#564)
* system.runner.server.go_actions
1 parent 54ef4d4 commit 050c706

4 files changed

Lines changed: 217 additions & 0 deletions

File tree

src/Runner.Worker/ActionManager.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,13 @@ public Definition LoadAction(IExecutionContext executionContext, Pipelines.Actio
440440
Trace.Info($"Action node.js file: {nodeAction.Script}.");
441441
Trace.Info($"Action post node.js file: {nodeAction.Post ?? "N/A"}.");
442442
}
443+
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Go)
444+
{
445+
var nodeAction = definition.Data.Execution as GoActionExecutionData;
446+
Trace.Info($"Action pre go file: {nodeAction.Pre ?? "N/A"}.");
447+
Trace.Info($"Action go file: {nodeAction.Main}.");
448+
Trace.Info($"Action post go file: {nodeAction.Post ?? "N/A"}.");
449+
}
443450
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Plugin)
444451
{
445452
var pluginAction = definition.Data.Execution as PluginActionExecutionData;
@@ -1203,6 +1210,7 @@ public enum ActionExecutionType
12031210
Plugin,
12041211
Script,
12051212
Composite,
1213+
Go,
12061214
}
12071215

12081216
public sealed class ContainerActionExecutionData : ActionExecutionData
@@ -1241,6 +1249,20 @@ public sealed class NodeJSActionExecutionData : ActionExecutionData
12411249
public string NodeVersion { get; set; }
12421250
}
12431251

1252+
public sealed class GoActionExecutionData : ActionExecutionData
1253+
{
1254+
public override ActionExecutionType ExecutionType => ActionExecutionType.Go;
1255+
1256+
public override bool HasPre => !string.IsNullOrEmpty(Pre);
1257+
public override bool HasPost => !string.IsNullOrEmpty(Post);
1258+
1259+
public string Main { get; set; }
1260+
1261+
public string Pre { get; set; }
1262+
1263+
public string Post { get; set; }
1264+
}
1265+
12441266
public sealed class PluginActionExecutionData : ActionExecutionData
12451267
{
12461268
public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin;

src/Runner.Worker/ActionManifestManager.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,24 @@ private ActionExecutionData ConvertRuns(
498498
};
499499
}
500500
}
501+
else if (string.Equals(usingToken.Value, "go", StringComparison.OrdinalIgnoreCase) && executionContext.Global.Variables.GetBoolean("system.runner.server.go_actions") == true)
502+
{
503+
if (string.IsNullOrEmpty(mainToken?.Value))
504+
{
505+
throw new ArgumentNullException($"You are using a Go Action but there is not an entry Go file provided in {fileRelativePath}.");
506+
}
507+
else
508+
{
509+
return new GoActionExecutionData()
510+
{
511+
Main = mainToken.Value,
512+
Pre = preToken?.Value,
513+
InitCondition = preIfToken?.Value ?? "always()",
514+
Post = postToken?.Value,
515+
CleanupCondition = postIfToken?.Value ?? "always()"
516+
};
517+
}
518+
}
501519
else if (string.Equals(usingToken.Value, "composite", StringComparison.OrdinalIgnoreCase))
502520
{
503521
if (steps == null)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using GitHub.DistributedTask.Pipelines;
8+
using GitHub.DistributedTask.Pipelines.ContextData;
9+
using GitHub.DistributedTask.WebApi;
10+
using GitHub.Runner.Common;
11+
using GitHub.Runner.Common.Util;
12+
using GitHub.Runner.Sdk;
13+
using GitHub.Runner.Worker.Container;
14+
using GitHub.Runner.Worker.Container.ContainerHooks;
15+
16+
namespace GitHub.Runner.Worker.Handlers
17+
{
18+
[ServiceLocator(Default = typeof(GoActionHandler))]
19+
public interface IGoActionHandler : IHandler
20+
{
21+
GoActionExecutionData Data { get; set; }
22+
}
23+
24+
public sealed class GoActionHandler : Handler, IGoActionHandler
25+
{
26+
public GoActionExecutionData Data { get; set; }
27+
28+
public async Task RunAsync(ActionRunStage stage)
29+
{
30+
// Validate args.
31+
Trace.Entering();
32+
ArgUtil.NotNull(Data, nameof(Data));
33+
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
34+
ArgUtil.NotNull(Inputs, nameof(Inputs));
35+
ArgUtil.Directory(ActionDirectory, nameof(ActionDirectory));
36+
37+
// Update the env dictionary.
38+
AddInputsToEnvironment();
39+
AddPrependPathToEnvironment();
40+
41+
// expose context to environment
42+
foreach (var context in ExecutionContext.ExpressionValues)
43+
{
44+
if (context.Value is IEnvironmentContextData runtimeContext && runtimeContext != null)
45+
{
46+
foreach (var env in runtimeContext.GetRuntimeEnvironmentVariables())
47+
{
48+
Environment[env.Key] = env.Value;
49+
}
50+
}
51+
}
52+
53+
// Add Actions Runtime server info
54+
var systemConnection = ExecutionContext.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
55+
Environment["ACTIONS_RUNTIME_URL"] = systemConnection.Url.AbsoluteUri;
56+
Environment["ACTIONS_RUNTIME_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
57+
if (systemConnection.Data.TryGetValue("CacheServerUrl", out var cacheUrl) && !string.IsNullOrEmpty(cacheUrl))
58+
{
59+
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
60+
}
61+
if (systemConnection.Data.TryGetValue("PipelinesServiceUrl", out var pipelinesServiceUrl) && !string.IsNullOrEmpty(pipelinesServiceUrl))
62+
{
63+
Environment["ACTIONS_RUNTIME_URL"] = pipelinesServiceUrl;
64+
}
65+
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
66+
{
67+
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
68+
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
69+
}
70+
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
71+
{
72+
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
73+
}
74+
75+
if (ExecutionContext.Global.Variables.GetBoolean("actions_uses_cache_service_v2") ?? false)
76+
{
77+
Environment["ACTIONS_CACHE_SERVICE_V2"] = bool.TrueString;
78+
}
79+
80+
// Resolve the target script.
81+
string target = null;
82+
if (stage == ActionRunStage.Main)
83+
{
84+
target = Data.Main;
85+
}
86+
else if (stage == ActionRunStage.Pre)
87+
{
88+
target = Data.Pre;
89+
}
90+
else if (stage == ActionRunStage.Post)
91+
{
92+
target = Data.Post;
93+
}
94+
95+
// Set extra telemetry base on the current context.
96+
if (stage == ActionRunStage.Main)
97+
{
98+
ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
99+
ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
100+
}
101+
102+
ArgUtil.NotNullOrEmpty(target, nameof(target));
103+
var targetBaseName = target;
104+
target = Path.Combine(ActionDirectory, target + ".out");
105+
106+
// Resolve the working directory.
107+
string workingDirectory = ExecutionContext.GetGitHubContext("workspace");
108+
if (string.IsNullOrEmpty(workingDirectory))
109+
{
110+
workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
111+
}
112+
113+
// It appears that node.exe outputs UTF8 when not in TTY mode.
114+
Encoding outputEncoding = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) ? Encoding.UTF8 : null;
115+
116+
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
117+
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
118+
{
119+
StepHost.OutputDataReceived += stdoutManager.OnDataReceived;
120+
StepHost.ErrorDataReceived += stderrManager.OnDataReceived;
121+
122+
await StepHost.ExecuteAsync(ExecutionContext,
123+
workingDirectory: StepHost.ResolvePathForStepHost(ExecutionContext, ActionDirectory),
124+
fileName: StepHost.ResolvePathForStepHost(ExecutionContext, "go"),
125+
arguments: $"build -o {targetBaseName}.out {targetBaseName}",
126+
environment: Environment,
127+
requireExitCodeZero: false,
128+
outputEncoding: outputEncoding,
129+
killProcessOnCancel: false,
130+
inheritConsoleHandler: !ExecutionContext.Global.Variables.Retain_Default_Encoding,
131+
standardInInput: null,
132+
cancellationToken: ExecutionContext.CancellationToken);
133+
134+
ArgUtil.File(target, nameof(target));
135+
136+
// Execute the process. Exit code 0 should always be returned.
137+
// A non-zero exit code indicates infrastructural failure.
138+
// Task failure should be communicated over STDOUT using ## commands.
139+
Task<int> step = StepHost.ExecuteAsync(ExecutionContext,
140+
workingDirectory: StepHost.ResolvePathForStepHost(ExecutionContext, workingDirectory),
141+
fileName: StepHost.ResolvePathForStepHost(ExecutionContext, target),
142+
arguments: string.Empty,
143+
environment: Environment,
144+
requireExitCodeZero: false,
145+
outputEncoding: outputEncoding,
146+
killProcessOnCancel: false,
147+
inheritConsoleHandler: !ExecutionContext.Global.Variables.Retain_Default_Encoding,
148+
standardInInput: null,
149+
cancellationToken: ExecutionContext.CancellationToken);
150+
151+
// Wait for either the node exit or force finish through ##vso command
152+
await System.Threading.Tasks.Task.WhenAny(step, ExecutionContext.ForceCompleted);
153+
154+
if (ExecutionContext.ForceCompleted.IsCompleted)
155+
{
156+
ExecutionContext.Debug("The task was marked as \"done\", but the process has not closed after 5 seconds. Treating the task as complete.");
157+
}
158+
else
159+
{
160+
var exitCode = await step;
161+
ExecutionContext.Debug($"Go Action run completed with exit code {exitCode}");
162+
if (exitCode != 0)
163+
{
164+
ExecutionContext.Result = TaskResult.Failed;
165+
}
166+
}
167+
}
168+
}
169+
}
170+
}

src/Runner.Worker/Handlers/HandlerFactory.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ public IHandler Create(
7272

7373
(handler as INodeScriptActionHandler).Data = nodeData;
7474
}
75+
else if (data.ExecutionType == ActionExecutionType.Go)
76+
{
77+
handler = HostContext.CreateService<IGoActionHandler>();
78+
var goData = data as GoActionExecutionData;
79+
80+
(handler as IGoActionHandler).Data = goData;
81+
}
7582
else if (data.ExecutionType == ActionExecutionType.Script)
7683
{
7784
handler = HostContext.CreateService<IScriptHandler>();

0 commit comments

Comments
 (0)