mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9027c154d0 | ||
|
|
85e1927754 | ||
|
|
b6dbf42746 | ||
|
|
67ba8a7d42 | ||
|
|
e4f9e6ae26 | ||
|
|
854d5e3bf3 | ||
|
|
57dec28f68 | ||
|
|
55a861f089 | ||
|
|
51b2031cbf | ||
|
|
400b2d879c | ||
|
|
c4b6d288d4 | ||
|
|
0699597876 | ||
|
|
a592b14ae3 | ||
|
|
04269f7b1b | ||
|
|
e89d2e84bd | ||
|
|
afe7066e39 | ||
|
|
da79ef4acb | ||
|
|
5afb52b272 | ||
|
|
cf87c55557 | ||
|
|
43fa351980 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
os: windows-latest
|
||||
devScript: ./dev
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
os: windows-latest
|
||||
devScript: ./dev
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -164,7 +164,6 @@ jobs:
|
||||
release_name: "v${{ steps.releaseNote.outputs.version }}"
|
||||
body: |
|
||||
${{ steps.releaseNote.outputs.note }}
|
||||
prerelease: true
|
||||
|
||||
# Upload release assets
|
||||
- name: Upload Release Asset (win-x64)
|
||||
|
||||
71
docs/adrs/1438-conditional-composite.md
Normal file
71
docs/adrs/1438-conditional-composite.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# ADR 1438: Support Conditionals In Composite Actions
|
||||
|
||||
**Date**: 2021-10-13
|
||||
|
||||
**Status**: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
We recently shipped composite actions, which allows you to reuse individual steps inside an action.
|
||||
However, one of the [most requested features](https://github.com/actions/runner/issues/834) has been a way to support the `if` keyword.
|
||||
|
||||
### Goals
|
||||
- We want to keep consistent with current behavior
|
||||
- We want to support conditionals via the `if` keyword
|
||||
- Our built in functions like `success` should be implementable without calling them, for example you can do `job.status == success` rather then `success()` currently.
|
||||
|
||||
### How does composite currently work?
|
||||
|
||||
Currently, we have limited conditional support in composite actions for `pre` and `post` steps.
|
||||
These are based on the `job status`, and support keywords like `always()`, `failed()`, `success()` and `cancelled()`.
|
||||
However, generic or main steps do **not** support conditionals.
|
||||
|
||||
By default, in a regular workflow, a step runs on the `success()` condition. Which looks at the **job** **status**, sees if it is successful and runs.
|
||||
|
||||
By default, in a composite action, main steps run until a single step fails in that composite action, then the composite action is halted early. It does **not** care about the job status.
|
||||
Pre, and post steps in composite actions use the job status to determine if they should run.
|
||||
|
||||
### How do we go forward?
|
||||
|
||||
Well, if we think about what composite actions are currently doing when invoking main steps, they are checking if the current composite action is successful.
|
||||
Lets formalize that concept into a "real" idea.
|
||||
|
||||
- We will add an `action_status` field to the github context to mimic the [job's context status](https://docs.github.com/en/actions/learn-github-actions/contexts#job-context).
|
||||
- We have an existing concept that does this `action_path` which is only set for composite actions on the github context.
|
||||
- In a composite action during a main step, the `success()` function will check if `action_status == success`, rather then `job_status == success`. Failure will work the same way.
|
||||
- Pre and post steps in composite actions will not change, they will continue to check the job status.
|
||||
|
||||
|
||||
### Nested Scenario
|
||||
For nested composite actions, we will follow the existing behavior, you only care about your current composite action, not any parents.
|
||||
For example, lets imagine a scenario with a simple nested composite action
|
||||
|
||||
```
|
||||
- Job
|
||||
- Regular Step
|
||||
- Composite Action
|
||||
- runs: exit 1
|
||||
- if: always()
|
||||
uses: A child composite action
|
||||
- if: success()
|
||||
runs: echo "this should print"
|
||||
- runs: echo "this should also print"
|
||||
- if: success()
|
||||
runs: echo "this will not print as the current composite action has failed already"
|
||||
|
||||
```
|
||||
The child composite actions steps should run in this example, the child composite action has not yet failed, so it should run all steps until a step fails. This is consistent with how a composite action currently works in production if the main job fails but a composite action is invoked with `if:always()` or `if: failure()`
|
||||
|
||||
### Other options explored
|
||||
We could add the `current_step_status` to the job context rather then `__status` to the steps context, however this comes with two major downsides:
|
||||
- We need to support the field for every type of step, because its non trivial to remove a field from the job context once it has been added (its readonly)
|
||||
- For all actions besides composite it would only every be `success`
|
||||
- Its weird to have a `current_step` value on the job context
|
||||
- We also explored a `__status` on the steps context.
|
||||
- The `__` is required to prevent us from colliding with a step with id: status
|
||||
- This felt wrong because the naming was not smooth, and did not fit into current conventions.
|
||||
|
||||
### Consequences
|
||||
- github context has a new field for the status of the current composite action.
|
||||
- We support conditional's in composite actions
|
||||
- We keep the existing behavior for all users, but allow them to expand that functionality.
|
||||
@@ -1,11 +1,20 @@
|
||||
## Features
|
||||
|
||||
- Expose GITHUB_REF_* as environment variable (#1314)
|
||||
- Add arch to runner context (#1372)
|
||||
- Support Conditional Steps in Composite Actions (#1438)
|
||||
- Log current runner version in terminal (#1441)
|
||||
|
||||
## Bugs
|
||||
|
||||
- Fixed an issue where container environment variables names or values could escape the docker command (#2108)
|
||||
- Makes the user keychains available to the service (#847)
|
||||
- Use Actions Service health and api.github.com endpoints after connection failure on Actions Server and Hosted (#1385)
|
||||
- Fix an issue where nested local composite actions did not correctly register post steps (#1433)
|
||||
|
||||
## Misc
|
||||
|
||||
- Cleanup Older versions on MacOS now that we recreate node versions as needed (#1410)
|
||||
|
||||
## Windows x64
|
||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.283.4
|
||||
2.284.0
|
||||
|
||||
@@ -25,5 +25,7 @@
|
||||
</dict>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>SessionCreate</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -161,59 +161,13 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||
fi
|
||||
else
|
||||
runproc=$(ps x -o pgid,command | grep "run.sh" | grep -v grep | awk '{print $1}')
|
||||
if [[ $? -eq 0 && -n "$runproc" ]]
|
||||
then
|
||||
date "+[%F %T-%4N] Running as ephemeral using run.sh, no need to recreate node folder" >> "$logfile" 2>&1
|
||||
else
|
||||
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||
fi
|
||||
|
||||
if [ $attemptedtargetedfix -eq 0 ]
|
||||
then
|
||||
|
||||
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$logfile" 2>&1
|
||||
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$telemetryfile" 2>&1
|
||||
if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.280.3/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node"
|
||||
fi
|
||||
|
||||
if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.280.2/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node"
|
||||
fi
|
||||
|
||||
if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.280.1/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node"
|
||||
fi
|
||||
|
||||
# GHES 3.2
|
||||
if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.279.0/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node"
|
||||
fi
|
||||
|
||||
# GHES 3.1.2 or later
|
||||
if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.278.0/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node"
|
||||
fi
|
||||
|
||||
# GHES 3.1.0
|
||||
if [[ ! -e "$rootfolder/externals.2.276.1/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.276.1/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.276.1/node12/bin/node"
|
||||
fi
|
||||
|
||||
# GHES 3.0
|
||||
if [[ ! -e "$rootfolder/externals.2.273.5/node12/bin/node" ]]
|
||||
then
|
||||
mkdir -p "$rootfolder/externals.2.273.5/node12/bin"
|
||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.273.5/node12/bin/node"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
@@ -35,7 +38,11 @@ namespace GitHub.Runner.Common
|
||||
public async Task ConnectAsync(VssConnection jobConnection)
|
||||
{
|
||||
_connection = jobConnection;
|
||||
int attemptCount = 5;
|
||||
int totalAttempts = 5;
|
||||
int attemptCount = totalAttempts;
|
||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||
var runnerSettings = configurationStore.GetSettings();
|
||||
|
||||
while (!_connection.HasAuthenticated && attemptCount-- > 0)
|
||||
{
|
||||
try
|
||||
@@ -45,17 +52,71 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
catch (Exception ex) when (attemptCount > 0)
|
||||
{
|
||||
Trace.Info($"Catch exception during connect. {attemptCount} attemp left.");
|
||||
Trace.Info($"Catch exception during connect. {attemptCount} attempts left.");
|
||||
Trace.Error(ex);
|
||||
|
||||
if (runnerSettings.IsHostedServer)
|
||||
{
|
||||
await CheckNetworkEndpointsAsync(attemptCount);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
int attempt = totalAttempts - attemptCount;
|
||||
TimeSpan backoff = BackoffTimerHelper.GetExponentialBackoff(attempt, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(3.2), TimeSpan.FromMilliseconds(100));
|
||||
|
||||
await Task.Delay(backoff);
|
||||
}
|
||||
|
||||
_taskClient = _connection.GetClient<TaskHttpClient>();
|
||||
_hasConnection = true;
|
||||
}
|
||||
|
||||
private async Task CheckNetworkEndpointsAsync(int attemptsLeft)
|
||||
{
|
||||
try
|
||||
{
|
||||
Trace.Info("Requesting Actions Service health endpoint status");
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var actionsClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
var baseUri = new Uri(_connection.Uri.GetLeftPart(UriPartial.Authority));
|
||||
|
||||
actionsClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
|
||||
// Call the _apis/health endpoint, and include how many attempts are left as a URL query for easy tracking
|
||||
var response = await actionsClient.GetAsync(new Uri(baseUri, $"_apis/health?_internalRunnerAttemptsLeft={attemptsLeft}"));
|
||||
Trace.Info($"Actions health status code: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error, but continue as this call is best-effort
|
||||
Trace.Info($"Actions Service health endpoint failed due to {ex.GetType().Name}");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Trace.Info("Requesting Github API endpoint status");
|
||||
// This is a dotcom public API... just call it directly
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var gitHubClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
gitHubClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
|
||||
// Call the api.github.com endpoint, and include how many attempts are left as a URL query for easy tracking
|
||||
var response = await gitHubClient.GetAsync($"https://api.github.com?_internalRunnerAttemptsLeft={attemptsLeft}");
|
||||
Trace.Info($"api.github.com status code: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error, but continue as this call is best-effort
|
||||
Trace.Info($"Github API endpoint failed due to {ex.GetType().Name}");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckConnection()
|
||||
{
|
||||
if (!_hasConnection)
|
||||
|
||||
@@ -312,6 +312,8 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
HostContext.WritePerfCounter("SessionCreated");
|
||||
|
||||
_term.WriteLine($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
|
||||
_term.WriteLine($"{DateTime.UtcNow:u}: Listening for Jobs");
|
||||
|
||||
IJobDispatcher jobDispatcher = null;
|
||||
|
||||
@@ -75,11 +75,9 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info($"All running job has exited.");
|
||||
|
||||
// We need to keep runner backup around for macOS until we fixed https://github.com/actions/runner/issues/743
|
||||
#if !OS_OSX
|
||||
// delete runner backup
|
||||
DeletePreviousVersionRunnerBackup(token);
|
||||
Trace.Info($"Delete old version runner backup.");
|
||||
#endif
|
||||
// generate update script from template
|
||||
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
|
||||
|
||||
|
||||
@@ -267,6 +267,19 @@ namespace GitHub.Runner.Worker
|
||||
_cachedEmbeddedPostSteps[parentStepId].Push(clonedAction);
|
||||
}
|
||||
}
|
||||
else if (depth > 0)
|
||||
{
|
||||
// if we're in a composite action and haven't loaded the local action yet
|
||||
// we assume it has a post step
|
||||
if (!_cachedEmbeddedPostSteps.ContainsKey(parentStepId))
|
||||
{
|
||||
// If we haven't done so already, add the parent to the post steps
|
||||
_cachedEmbeddedPostSteps[parentStepId] = new Stack<Pipelines.ActionStep>();
|
||||
}
|
||||
// Clone action so we can modify the condition without affecting the original
|
||||
var clonedAction = action.Clone() as Pipelines.ActionStep;
|
||||
_cachedEmbeddedPostSteps[parentStepId].Push(clonedAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1034,7 +1047,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once we remove the DistributedTask.EnableCompositeActions FF
|
||||
foreach (var step in compositeAction.Steps)
|
||||
{
|
||||
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && step.Reference.Type != Pipelines.ActionSourceType.Script)
|
||||
|
||||
@@ -131,11 +131,11 @@ namespace GitHub.Runner.Worker.Container
|
||||
{
|
||||
if (String.IsNullOrEmpty(env.Value))
|
||||
{
|
||||
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
|
||||
dockerOptions.Add($"-e \"{env.Key}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key, env.Value));
|
||||
dockerOptions.Add($"-e \"{env.Key}={env.Value.Replace("\"", "\\\"")}\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace GitHub.Runner.Worker.Container
|
||||
{
|
||||
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
||||
// the value directly in the command
|
||||
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
|
||||
dockerOptions.Add($"-e {env.Key}");
|
||||
}
|
||||
|
||||
// Watermark for GitHub Action environment
|
||||
|
||||
@@ -6,9 +6,6 @@ namespace GitHub.Runner.Worker.Container
|
||||
{
|
||||
public class DockerUtil
|
||||
{
|
||||
private static readonly Regex QuoteEscape = new Regex(@"(\\*)" + "\"", RegexOptions.Compiled);
|
||||
private static readonly Regex EndOfStringEscape = new Regex(@"(\\+)$", RegexOptions.Compiled);
|
||||
|
||||
public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines)
|
||||
{
|
||||
const string targetPort = "targetPort";
|
||||
@@ -20,7 +17,7 @@ namespace GitHub.Runner.Worker.Container
|
||||
string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$";
|
||||
|
||||
List<PortMapping> portMappings = new List<PortMapping>();
|
||||
foreach (var line in portMappingLines)
|
||||
foreach(var line in portMappingLines)
|
||||
{
|
||||
Match m = Regex.Match(line, pattern, RegexOptions.None, TimeSpan.FromSeconds(1));
|
||||
if (m.Success)
|
||||
@@ -64,44 +61,5 @@ namespace GitHub.Runner.Worker.Container
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static string CreateEscapedOption(string flag, string key)
|
||||
{
|
||||
if (String.IsNullOrEmpty(key))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return $"{flag} {EscapeString(key)}";
|
||||
}
|
||||
|
||||
public static string CreateEscapedOption(string flag, string key, string value)
|
||||
{
|
||||
if (String.IsNullOrEmpty(key))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
var escapedString = EscapeString($"{key}={value}");
|
||||
return $"{flag} {escapedString}";
|
||||
}
|
||||
|
||||
private static string EscapeString(string value)
|
||||
{
|
||||
if (String.IsNullOrEmpty(value))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// Dotnet escaping rules are weird here, we can only escape \ if it precedes a "
|
||||
// If a double quotation mark follows two or an even number of backslashes, each proceeding backslash pair is replaced with one backslash and the double quotation mark is removed.
|
||||
// If a double quotation mark follows an odd number of backslashes, including just one, each preceding pair is replaced with one backslash and the remaining backslash is removed; however, in this case the double quotation mark is not removed.
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.getcommandlineargs?redirectedfrom=MSDN&view=net-6.0#remarks
|
||||
|
||||
// First, find any \ followed by a " and double the number of \ + 1.
|
||||
value = QuoteEscape.Replace(value, @"$1$1\" + "\"");
|
||||
// Next, what if it ends in `\`, it would escape the end quote. So, we need to detect that at the end of the string and perform the same escape
|
||||
// Luckily, we can just use the $ character with detects the end of string in regex
|
||||
value = EndOfStringEscape.Replace(value, @"$1$1");
|
||||
// Finally, wrap it in quotes
|
||||
return $"\"{value}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace GitHub.Runner.Worker
|
||||
string ScopeName { get; }
|
||||
string SiblingScopeName { get; }
|
||||
string ContextName { get; }
|
||||
ActionRunStage Stage { get; }
|
||||
Task ForceCompleted { get; }
|
||||
TaskResult? Result { get; set; }
|
||||
TaskResult? Outcome { get; set; }
|
||||
@@ -62,7 +63,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
// Only job level ExecutionContext has PostJobSteps
|
||||
Stack<IStep> PostJobSteps { get; }
|
||||
HashSet<Guid> EmbeddedStepsWithPostRegistered{ get; }
|
||||
Dictionary<Guid, string> EmbeddedStepsWithPostRegistered { get; }
|
||||
|
||||
// Keep track of embedded steps states
|
||||
Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; }
|
||||
@@ -76,8 +77,8 @@ namespace GitHub.Runner.Worker
|
||||
// Initialize
|
||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||
void CancelToken();
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
|
||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
|
||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||
|
||||
// logging
|
||||
long Write(string tag, string message);
|
||||
@@ -144,6 +145,7 @@ namespace GitHub.Runner.Worker
|
||||
public string ScopeName { get; private set; }
|
||||
public string SiblingScopeName { get; private set; }
|
||||
public string ContextName { get; private set; }
|
||||
public ActionRunStage Stage { get; private set; }
|
||||
public Task ForceCompleted => _forceCompleted.Task;
|
||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||
@@ -168,7 +170,7 @@ namespace GitHub.Runner.Worker
|
||||
public HashSet<Guid> StepsWithPostRegistered { get; private set; }
|
||||
|
||||
// Only job level ExecutionContext has EmbeddedStepsWithPostRegistered
|
||||
public HashSet<Guid> EmbeddedStepsWithPostRegistered { get; private set; }
|
||||
public Dictionary<Guid, string> EmbeddedStepsWithPostRegistered { get; private set; }
|
||||
|
||||
public Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; private set; }
|
||||
|
||||
@@ -265,12 +267,19 @@ namespace GitHub.Runner.Worker
|
||||
string siblingScopeName = null;
|
||||
if (this.IsEmbedded)
|
||||
{
|
||||
if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||
if (step is IActionRunner actionRunner)
|
||||
{
|
||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to child post step stack.");
|
||||
if (Root.EmbeddedStepsWithPostRegistered.ContainsKey(actionRunner.Action.Id))
|
||||
{
|
||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to child post step stack.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Root.EmbeddedStepsWithPostRegistered[actionRunner.Action.Id] = actionRunner.Condition;
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||
{
|
||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
||||
@@ -285,7 +294,7 @@ namespace GitHub.Runner.Worker
|
||||
Root.PostJobSteps.Push(step);
|
||||
}
|
||||
|
||||
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null)
|
||||
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -294,6 +303,7 @@ namespace GitHub.Runner.Worker
|
||||
child.Global = Global;
|
||||
child.ScopeName = scopeName;
|
||||
child.ContextName = contextName;
|
||||
child.Stage = stage;
|
||||
child.EmbeddedId = embeddedId;
|
||||
child.SiblingScopeName = siblingScopeName;
|
||||
child.JobTelemetry = JobTelemetry;
|
||||
@@ -344,9 +354,9 @@ namespace GitHub.Runner.Worker
|
||||
/// An embedded execution context shares the same record ID, record name, logger,
|
||||
/// and a linked cancellation token.
|
||||
/// </summary>
|
||||
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null)
|
||||
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null)
|
||||
{
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
|
||||
}
|
||||
|
||||
public void Start(string currentOperation = null)
|
||||
@@ -543,8 +553,8 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
|
||||
_record.WarningCount++;
|
||||
}
|
||||
else if (issue.Type == IssueType.Notice)
|
||||
}
|
||||
else if (issue.Type == IssueType.Notice)
|
||||
{
|
||||
|
||||
// tracking line number for each issue in log file
|
||||
@@ -716,10 +726,10 @@ namespace GitHub.Runner.Worker
|
||||
StepsWithPostRegistered = new HashSet<Guid>();
|
||||
|
||||
// EmbeddedStepsWithPostRegistered for job ExecutionContext
|
||||
EmbeddedStepsWithPostRegistered = new HashSet<Guid>();
|
||||
EmbeddedStepsWithPostRegistered = new Dictionary<Guid, string>();
|
||||
|
||||
// EmbeddedIntraActionState for job ExecutionContext
|
||||
EmbeddedIntraActionState = new Dictionary<Guid, Dictionary<string,string>>();
|
||||
EmbeddedIntraActionState = new Dictionary<Guid, Dictionary<string, string>>();
|
||||
|
||||
// Job timeline record.
|
||||
InitializeTimelineRecord(
|
||||
@@ -937,7 +947,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
|
||||
var newGuid = Guid.NewGuid();
|
||||
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
|
||||
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,7 +980,7 @@ namespace GitHub.Runner.Worker
|
||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||
public static void InfrastructureError(this IExecutionContext context, string message)
|
||||
{
|
||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true});
|
||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true });
|
||||
}
|
||||
|
||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||
|
||||
@@ -24,8 +24,19 @@ namespace GitHub.Runner.Worker.Expressions
|
||||
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||
return jobStatus == ActionResult.Failure;
|
||||
|
||||
// Decide based on 'action_status' for composite MAIN steps and 'job.status' for pre, post and job-level steps
|
||||
var isCompositeMainStep = executionContext.IsEmbedded && executionContext.Stage == ActionRunStage.Main;
|
||||
if (isCompositeMainStep)
|
||||
{
|
||||
ActionResult actionStatus = EnumUtil.TryParse<ActionResult>(executionContext.GetGitHubContext("action_status")) ?? ActionResult.Success;
|
||||
return actionStatus == ActionResult.Failure;
|
||||
}
|
||||
else
|
||||
{
|
||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||
return jobStatus == ActionResult.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,19 @@ namespace GitHub.Runner.Worker.Expressions
|
||||
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||
return jobStatus == ActionResult.Success;
|
||||
|
||||
// Decide based on 'action_status' for composite MAIN steps and 'job.status' for pre, post and job-level steps
|
||||
var isCompositeMainStep = executionContext.IsEmbedded && executionContext.Stage == ActionRunStage.Main;
|
||||
if (isCompositeMainStep)
|
||||
{
|
||||
ActionResult actionStatus = EnumUtil.TryParse<ActionResult>(executionContext.GetGitHubContext("action_status")) ?? ActionResult.Success;
|
||||
return actionStatus == ActionResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||
return jobStatus == ActionResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace GitHub.Runner.Worker
|
||||
"job",
|
||||
"path",
|
||||
"ref",
|
||||
"ref_name",
|
||||
"ref_protected",
|
||||
"ref_type",
|
||||
"repository",
|
||||
"repository_owner",
|
||||
"retention_days",
|
||||
@@ -39,9 +42,16 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
foreach (var data in this)
|
||||
{
|
||||
if (_contextEnvAllowlist.Contains(data.Key) && data.Value is StringContextData value)
|
||||
if (_contextEnvAllowlist.Contains(data.Key))
|
||||
{
|
||||
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", value);
|
||||
if (data.Value is StringContextData value)
|
||||
{
|
||||
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", value);
|
||||
}
|
||||
else if (data.Value is BooleanContextData booleanValue)
|
||||
{
|
||||
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", booleanValue.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,9 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Only register post steps for steps that actually ran
|
||||
foreach (var step in Data.PostSteps.ToList())
|
||||
{
|
||||
if (ExecutionContext.Root.EmbeddedStepsWithPostRegistered.Contains(step.Id))
|
||||
if (ExecutionContext.Root.EmbeddedStepsWithPostRegistered.ContainsKey(step.Id))
|
||||
{
|
||||
step.Condition = ExecutionContext.Root.EmbeddedStepsWithPostRegistered[step.Id];
|
||||
steps.Add(step);
|
||||
}
|
||||
else
|
||||
@@ -124,7 +125,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
ArgUtil.NotNull(step, step.DisplayName);
|
||||
var stepId = $"__{Guid.NewGuid()}";
|
||||
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepId, Guid.NewGuid());
|
||||
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepId, Guid.NewGuid(), stage);
|
||||
embeddedSteps.Add(step);
|
||||
}
|
||||
}
|
||||
@@ -143,7 +144,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
step.Stage = stage;
|
||||
step.Condition = stepData.Condition;
|
||||
ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState);
|
||||
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
|
||||
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, stage, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
|
||||
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName))
|
||||
{
|
||||
@@ -241,6 +242,10 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
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
|
||||
@@ -295,108 +300,100 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
CancellationTokenRegistration? jobCancelRegister = null;
|
||||
try
|
||||
{
|
||||
// For main steps just run the action
|
||||
if (stage == ActionRunStage.Main)
|
||||
// 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);
|
||||
}
|
||||
// We need to evaluate conditions for pre/post steps
|
||||
else
|
||||
{
|
||||
// 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
|
||||
{
|
||||
@@ -412,12 +409,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
|
||||
ExecutionContext.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Result, step.ExecutionContext.Result.Value);
|
||||
|
||||
// We should run cleanup even if one of the cleanup step fails
|
||||
if (stage != ActionRunStage.Post)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
||||
// the value directly in the command
|
||||
dockerCommandArgs.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
|
||||
dockerCommandArgs.Add($"-e {env.Key}");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(PrependPath))
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace GitHub.Runner.Worker
|
||||
ArgUtil.NotNull(message, nameof(message));
|
||||
|
||||
// Create a new timeline record for 'Set up job'
|
||||
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Set up job", $"{nameof(JobExtension)}_Init", null, null);
|
||||
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Set up job", $"{nameof(JobExtension)}_Init", null, null, ActionRunStage.Pre);
|
||||
|
||||
List<IStep> preJobSteps = new List<IStep>();
|
||||
List<IStep> jobSteps = new List<IStep>();
|
||||
@@ -306,13 +306,13 @@ namespace GitHub.Runner.Worker
|
||||
JobExtensionRunner extensionStep = step as JobExtensionRunner;
|
||||
ArgUtil.NotNull(extensionStep, extensionStep.DisplayName);
|
||||
Guid stepId = Guid.NewGuid();
|
||||
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
||||
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"), ActionRunStage.Pre);
|
||||
}
|
||||
else if (step is IActionRunner actionStep)
|
||||
{
|
||||
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||
Guid stepId = Guid.NewGuid();
|
||||
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, intraActionStates[actionStep.Action.Id]);
|
||||
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, ActionRunStage.Pre, intraActionStates[actionStep.Action.Id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||
intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
|
||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, null, actionStep.Action.ContextName, intraActionState);
|
||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, null, actionStep.Action.ContextName, ActionRunStage.Main, intraActionState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ namespace GitHub.Runner.Worker
|
||||
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);
|
||||
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post);
|
||||
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
|
||||
{
|
||||
try
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
|
||||
jobContext.SetRunnerContext("os", VarUtil.OS);
|
||||
jobContext.SetRunnerContext("arch", VarUtil.OSArchitecture);
|
||||
|
||||
var runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||
jobContext.SetRunnerContext("name", runnerSettings.AgentName);
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"properties": {
|
||||
"name": "string-steps-context",
|
||||
"id": "non-empty-string",
|
||||
"if": "step-if",
|
||||
"run": {
|
||||
"type": "string-steps-context",
|
||||
"required": true
|
||||
@@ -141,6 +142,7 @@
|
||||
"properties": {
|
||||
"name": "string-steps-context",
|
||||
"id": "non-empty-string",
|
||||
"if": "step-if",
|
||||
"uses": {
|
||||
"type": "non-empty-string",
|
||||
"required": true
|
||||
@@ -216,6 +218,24 @@
|
||||
"loose-value-type": "string"
|
||||
}
|
||||
},
|
||||
"step-if": {
|
||||
"context": [
|
||||
"github",
|
||||
"inputs",
|
||||
"strategy",
|
||||
"matrix",
|
||||
"steps",
|
||||
"job",
|
||||
"runner",
|
||||
"env",
|
||||
"always(0,0)",
|
||||
"failure(0,0)",
|
||||
"cancelled(0,0)",
|
||||
"success(0,0)",
|
||||
"hashFiles(1,255)"
|
||||
],
|
||||
"string": {}
|
||||
},
|
||||
"step-with": {
|
||||
"context": [
|
||||
"github",
|
||||
|
||||
@@ -638,6 +638,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Matrix),
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Steps),
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Inputs),
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Job),
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Runner),
|
||||
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Env),
|
||||
|
||||
@@ -144,54 +144,5 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
|
||||
var actual = DockerUtil.ParseRegistryHostnameFromImageName(input);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData("", "")]
|
||||
[InlineData("foo", "foo")]
|
||||
[InlineData("foo \\ bar", "foo \\ bar")]
|
||||
[InlineData("foo \\", "foo \\\\")]
|
||||
[InlineData("foo \\\\", "foo \\\\\\\\")]
|
||||
[InlineData("foo \\\" bar", "foo \\\\\\\" bar")]
|
||||
[InlineData("foo \\\\\" bar", "foo \\\\\\\\\\\" bar")]
|
||||
public void CreateEscapedOption_keyOnly(string input, string escaped)
|
||||
{
|
||||
var flag = "--example";
|
||||
var actual = DockerUtil.CreateEscapedOption(flag, input);
|
||||
string expected;
|
||||
if (String.IsNullOrEmpty(input))
|
||||
{
|
||||
expected = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
expected = $"{flag} \"{escaped}\"";
|
||||
}
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData("foo", "bar", "foo=bar")]
|
||||
[InlineData("foo\\", "bar", "foo\\=bar")]
|
||||
[InlineData("foo\\", "bar\\", "foo\\=bar\\\\")]
|
||||
[InlineData("foo \\","bar \\", "foo \\=bar \\\\")]
|
||||
public void CreateEscapedOption_keyValue(string keyInput, string valueInput, string escapedString)
|
||||
{
|
||||
var flag = "--example";
|
||||
var actual = DockerUtil.CreateEscapedOption(flag, keyInput, valueInput);
|
||||
string expected;
|
||||
if (String.IsNullOrEmpty(keyInput))
|
||||
{
|
||||
expected = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
expected = $"{flag} \"{escapedString}\"";
|
||||
}
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,6 +626,32 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Load_ConditionalCompositeAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Arrange
|
||||
Setup();
|
||||
|
||||
var actionManifest = new ActionManifestManager();
|
||||
actionManifest.Initialize(_hc);
|
||||
|
||||
//Act
|
||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "conditional_composite_action.yml"));
|
||||
|
||||
//Assert
|
||||
Assert.Equal("Conditional Composite", result.Name);
|
||||
Assert.Equal(ActionExecutionType.Composite, result.Execution.ExecutionType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -193,9 +193,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
// Act.
|
||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1", "action_1", null, null);
|
||||
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1", "action_1", null, null, 0);
|
||||
action1.IntraActionState["state"] = "1";
|
||||
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_2", "action_2", null, null);
|
||||
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_2", "action_2", null, null, 0);
|
||||
action2.IntraActionState["state"] = "2";
|
||||
|
||||
|
||||
@@ -291,8 +291,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
// Act.
|
||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null);
|
||||
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null);
|
||||
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, 0);
|
||||
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null, 0);
|
||||
|
||||
var actionId = Guid.NewGuid();
|
||||
var postRunner1 = hc.CreateService<IActionRunner>();
|
||||
|
||||
@@ -105,6 +105,36 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData(ActionResult.Failure, ActionResult.Failure, true)]
|
||||
[InlineData(ActionResult.Failure, ActionResult.Success, false)]
|
||||
[InlineData(ActionResult.Success, ActionResult.Failure, true)]
|
||||
[InlineData(ActionResult.Success, ActionResult.Success, false)]
|
||||
[InlineData(ActionResult.Success, null, false)]
|
||||
public void FailureFunctionComposite(ActionResult jobStatus, ActionResult? actionStatus, bool expected)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var executionContext = InitializeExecutionContext(hc);
|
||||
executionContext.Setup(x => x.GetGitHubContext("action_status")).Returns(actionStatus.ToString());
|
||||
executionContext.Setup( x=> x.IsEmbedded).Returns(true);
|
||||
executionContext.Setup( x=> x.Stage).Returns(ActionRunStage.Main);
|
||||
|
||||
_jobContext.Status = jobStatus;
|
||||
|
||||
// Act.
|
||||
bool actual = Evaluate("failure()");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -134,12 +164,43 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData(ActionResult.Failure, ActionResult.Failure, false)]
|
||||
[InlineData(ActionResult.Failure, ActionResult.Success, true)]
|
||||
[InlineData(ActionResult.Success, ActionResult.Failure, false)]
|
||||
[InlineData(ActionResult.Success, ActionResult.Success, true)]
|
||||
[InlineData(ActionResult.Success, null, true)]
|
||||
public void SuccessFunctionComposite(ActionResult jobStatus, ActionResult? actionStatus, bool expected)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var executionContext = InitializeExecutionContext(hc);
|
||||
executionContext.Setup(x => x.GetGitHubContext("action_status")).Returns(actionStatus.ToString());
|
||||
executionContext.Setup( x=> x.IsEmbedded).Returns(true);
|
||||
executionContext.Setup( x=> x.Stage).Returns(ActionRunStage.Main);
|
||||
|
||||
_jobContext.Status = jobStatus;
|
||||
|
||||
// Act.
|
||||
bool actual = Evaluate("success()");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(expected, actual);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||
{
|
||||
return new TestHostContext(this, testName);
|
||||
}
|
||||
|
||||
private void InitializeExecutionContext(TestHostContext hc)
|
||||
private Mock<IExecutionContext> InitializeExecutionContext(TestHostContext hc)
|
||||
{
|
||||
_jobContext = new JobContext();
|
||||
|
||||
@@ -149,6 +210,8 @@ namespace GitHub.Runner.Common.Tests.Worker.Expressions
|
||||
|
||||
_templateContext = new TemplateContext();
|
||||
_templateContext.State[nameof(IExecutionContext)] = executionContext.Object;
|
||||
|
||||
return executionContext;
|
||||
}
|
||||
|
||||
private bool Evaluate(string expression)
|
||||
|
||||
49
src/Test/TestData/conditional_composite_action.yml
Normal file
49
src/Test/TestData/conditional_composite_action.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
name: 'Conditional Composite'
|
||||
description: 'Test composite run step conditionals'
|
||||
inputs:
|
||||
exit-code:
|
||||
description: 'Action fails if set to non-zero'
|
||||
default: '0'
|
||||
outputs:
|
||||
default:
|
||||
description: "Did step run with default?"
|
||||
value: ${{ steps.default-conditional.outputs.default }}
|
||||
success:
|
||||
description: "Did step run with success?"
|
||||
value: ${{ steps.success-conditional.outputs.success }}
|
||||
failure:
|
||||
description: "Did step run with failure?"
|
||||
value: ${{ steps.failure-conditional.outputs.failure }}
|
||||
always:
|
||||
description: "Did step run with always?"
|
||||
value: ${{ steps.always-conditional.outputs.always }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: exit ${{ inputs.exit-code }}
|
||||
shell: bash
|
||||
|
||||
- run: echo "::set-output name=default::true"
|
||||
id: default-conditional
|
||||
shell: bash
|
||||
|
||||
- run: echo "::set-output name=success::true"
|
||||
id: success-conditional
|
||||
shell: bash
|
||||
if: success()
|
||||
|
||||
- run: echo "::set-output name=failure::true"
|
||||
id: failure-conditional
|
||||
shell: bash
|
||||
if: failure()
|
||||
|
||||
- run: echo "::set-output name=always::true"
|
||||
id: always-conditional
|
||||
shell: bash
|
||||
if: always()
|
||||
|
||||
- run: echo "failed"
|
||||
shell: bash
|
||||
if: ${{ inputs.exit-code == 1 && failure() }}
|
||||
@@ -1 +1 @@
|
||||
2.283.4
|
||||
2.284.0
|
||||
|
||||
Reference in New Issue
Block a user