Compare commits

...

7 Commits

Author SHA1 Message Date
Thomas Boop
409920e9f0 Update releaseVersion 2021-08-19 09:10:58 -04:00
Thomas Boop
5ed80c753b Port master to release branch (#1277)
* For Main Steps, just run the step, don't check condition (#1273)

* For Main Steps, just run the step, don't check condition

* fix whitespace

* pr feedback

* Runner 2.280.3 Release (#1276)

Co-authored-by: Tingluo Huang <tingluohuang@github.com>
Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2021-08-19 08:56:06 -04:00
Thomas Boop
ccafb91bfb Update releaseVersion 2021-08-12 13:39:28 -04:00
Thomas Boop
bd711becc8 Thboop/merge main m280 (#1260)
* Runner release 2.280.2 (#1259)



Co-authored-by: Tingluo Huang <tingluohuang@github.com>
Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2021-08-12 13:26:50 -04:00
Thomas Boop
4d17f77f5e Update releaseVersion 2021-08-04 13:34:08 -04:00
Thomas Boop
3e46f55ac9 Prep for m280.1 release (#1245)
* Finish job when worker crashed with IOException. (#1239)

* fix continue on error (#1238)

* Correctly set post step step context (#1243)

* Release notes for 2.280.1 runner (#1244)

Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2021-08-04 13:32:44 -04:00
Thomas Boop
ed15c5389b Update releaseVersion 2021-08-03 11:30:11 -04:00
10 changed files with 259 additions and 183 deletions

View File

@@ -26,6 +26,23 @@ Run as a one-liner. NOTE: replace with yourorg/yourrepo (repo level) or just you
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s yourorg/yourrepo curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s yourorg/yourrepo
``` ```
You can call the script with additional arguments:
```bash
# Usage:
# export RUNNER_CFG_PAT=<yourPAT>
# ./create-latest-svc -s scope -g [ghe_domain] -n [name] -u [user] -l [labels]
# -s required scope: repo (:owner/:repo) or org (:organization)
# -g optional ghe_hostname: the fully qualified domain name of your GitHub Enterprise Server deployment
# -n optional name of the runner, defaults to hostname
# -u optional user svc will run as, defaults to current
# -l optional list of labels (split by comma) applied on the runner"
```
Use `--` to pass any number of optional named parameters:
```
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s -- -s myorg/myrepo -n myname -l label1,label2
```
### Why can't I use a container? ### Why can't I use a container?
The runner is installed as a service using `systemd` and `systemctl`. Docker does not support `systemd` for service configuration on a container. The runner is installed as a service using `systemd` and `systemctl`. Docker does not support `systemd` for service configuration on a container.

View File

@@ -1,18 +1,11 @@
## Features ## Features
- Adds support for composite actions if the server supports it (#1222)
- Adds `generateIdTokenUri` to env variables for actions (#1234)
## Bugs ## Bugs
- Prefer higher `libicu` versions in `installDependencies.sh` (#1228) - Fixed an issue where composite steps would not run on `failure()` or `always()` when the job failed (#1273)
## Misc ## Misc
- Send step telemetry to server on JobCompletion (#1229)
- Print out the resolved SHA for each downloaded action (#1233)
## Windows x64 ## 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. 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.

View File

@@ -1 +1 @@
<Update to ./src/runnerversion when creating release> 2.280.3

View File

@@ -2,36 +2,68 @@
set -e set -e
#
# Downloads latest releases (not pre-release) runner
# Configures as a service
#
# Examples:
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myuser/myrepo my.ghe.deployment.net
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myorg my.ghe.deployment.net
#
# Usage:
# export RUNNER_CFG_PAT=<yourPAT>
# ./create-latest-svc scope [ghe_domain] [name] [user] [labels]
#
# scope required repo (:owner/:repo) or org (:organization)
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
# name optional defaults to hostname
# user optional user svc will run as. defaults to current
# labels optional list of labels (split by comma) applied on the runner
#
# Notes: # Notes:
# PATS over envvars are more secure # PATS over envvars are more secure
# Downloads latest runner release (not pre-release)
# Configures it as a service more secure
# Should be used on VMs and not containers # Should be used on VMs and not containers
# Works on OSX and Linux # Works on OSX and Linux
# Assumes x64 arch # Assumes x64 arch
# # See EXAMPLES below
flags_found=false
while getopts 's:g:n:u:l:' opt; do
flags_found=true
case $opt in
s)
runner_scope=$OPTARG
;;
g)
ghe_hostname=$OPTARG
;;
n)
runner_name=$OPTARG
;;
u)
svc_user=$OPTARG
;;
l)
labels=$OPTARG
;;
*)
echo "
Runner Service Installer
Examples:
RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myuser/myrepo my.ghe.deployment.net
RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh -s myorg -u user_name -l label1,label2
Usage:
export RUNNER_CFG_PAT=<yourPAT>
./create-latest-svc scope [ghe_domain] [name] [user] [labels]
-s required scope: repo (:owner/:repo) or org (:organization)
-g optional ghe_hostname: the fully qualified domain name of your GitHub Enterprise Server deployment
-n optional name of the runner, defaults to hostname
-u optional user svc will run as, defaults to current
-l optional list of labels (split by comma) applied on the runner"
exit 0
;;
esac
done
shift "$((OPTIND - 1))"
if ! "$flags_found"; then
runner_scope=${1} runner_scope=${1}
ghe_hostname=${2} ghe_hostname=${2}
runner_name=${3:-$(hostname)} runner_name=${3:-$(hostname)}
svc_user=${4:-$USER} svc_user=${4:-$USER}
labels=${5} labels=${5}
fi
# apply defaults
runner_name=${runner_name:-$(hostname)}
svc_user=${svc_user:-$USER}
echo "Configuring runner @ ${runner_scope}" echo "Configuring runner @ ${runner_scope}"
sudo echo sudo echo

View File

@@ -507,7 +507,20 @@ namespace GitHub.Runner.Listener
{ {
detailInfo = string.Join(Environment.NewLine, workerOutput); detailInfo = string.Join(Environment.NewLine, workerOutput);
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result."); Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
await LogWorkerProcessUnhandledException(message, detailInfo);
var jobServer = HostContext.GetService<IJobServer>();
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
await jobServer.ConnectAsync(jobConnection);
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
{
Trace.Info($"Finish job with result 'Failed' due to IOException.");
await ForceFailJob(jobServer, message);
}
} }
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode); TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
@@ -915,53 +928,16 @@ namespace GitHub.Runner.Listener
} }
// log an error issue to job level timeline record // log an error issue to job level timeline record
private async Task LogWorkerProcessUnhandledException(Pipelines.AgentJobRequestMessage message, string errorMessage) private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, string errorMessage)
{ {
try try
{ {
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
var jobServer = HostContext.GetService<IJobServer>();
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
/* Below is the legacy 'OnPremises' code that is currently unused by the runner
ToDo: re-implement code as appropriate once GHES support is added.
// Make sure SystemConnection Url match Config Url base for OnPremises server
if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType) ||
string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase))
{
try
{
Uri result = null;
Uri configUri = new Uri(_runnerSetting.ServerUrl);
if (Uri.TryCreate(new Uri(configUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped)), jobServerUrl.PathAndQuery, out result))
{
//replace the schema and host portion of messageUri with the host from the
//server URI (which was set at config time)
jobServerUrl = result;
}
}
catch (InvalidOperationException ex)
{
//cannot parse the Uri - not a fatal error
Trace.Error(ex);
}
catch (UriFormatException ex)
{
//cannot parse the Uri - not a fatal error
Trace.Error(ex);
}
} */
await jobServer.ConnectAsync(jobConnection);
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None); var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
ArgUtil.NotNull(timeline, nameof(timeline)); ArgUtil.NotNull(timeline, nameof(timeline));
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job"); TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
ArgUtil.NotNull(jobRecord, nameof(jobRecord)); ArgUtil.NotNull(jobRecord, nameof(jobRecord));
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage }; var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash; unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
jobRecord.ErrorCount++; jobRecord.ErrorCount++;
@@ -975,6 +951,21 @@ namespace GitHub.Runner.Listener
} }
} }
// raise job completed event to fail the job.
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
{
try
{
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
}
catch (Exception ex)
{
Trace.Error("Fail to raise JobCompletedEvent back to service.");
Trace.Error(ex);
}
}
private class WorkerDispatcher : IDisposable private class WorkerDispatcher : IDisposable
{ {
public long RequestId { get; } public long RequestId { get; }

View File

@@ -610,6 +610,7 @@ namespace GitHub.Runner.Worker
{ {
NameWithOwner = repositoryReference.Name, NameWithOwner = repositoryReference.Name,
Ref = repositoryReference.Ref, Ref = repositoryReference.Ref,
Path = repositoryReference.Path,
}; };
}) })
.ToList(); .ToList();

View File

@@ -38,6 +38,7 @@ namespace GitHub.Runner.Worker
Guid Id { get; } Guid Id { get; }
Guid EmbeddedId { get; } Guid EmbeddedId { get; }
string ScopeName { get; } string ScopeName { get; }
string SiblingScopeName { get; }
string ContextName { get; } string ContextName { get; }
Task ForceCompleted { get; } Task ForceCompleted { get; }
TaskResult? Result { get; set; } TaskResult? Result { get; set; }
@@ -74,8 +75,8 @@ namespace GitHub.Runner.Worker
// Initialize // Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token); void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
void CancelToken(); 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)); 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); IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
// logging // logging
long Write(string tag, string message); long Write(string tag, string message);
@@ -140,6 +141,7 @@ namespace GitHub.Runner.Worker
public Guid Id => _record.Id; public Guid Id => _record.Id;
public Guid EmbeddedId { get; private set; } public Guid EmbeddedId { get; private set; }
public string ScopeName { get; private set; } public string ScopeName { get; private set; }
public string SiblingScopeName { get; private set; }
public string ContextName { get; private set; } public string ContextName { get; private set; }
public Task ForceCompleted => _forceCompleted.Task; public Task ForceCompleted => _forceCompleted.Task;
public CancellationToken CancellationToken => _cancellationTokenSource.Token; public CancellationToken CancellationToken => _cancellationTokenSource.Token;
@@ -258,6 +260,7 @@ namespace GitHub.Runner.Worker
public void RegisterPostJobStep(IStep step) public void RegisterPostJobStep(IStep step)
{ {
string siblingScopeName = null;
if (this.IsEmbedded) if (this.IsEmbedded)
{ {
if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id)) if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id))
@@ -271,12 +274,16 @@ namespace GitHub.Runner.Worker
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack."); Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
return; return;
} }
if (step is IActionRunner runner)
{
siblingScopeName = runner.Action.ContextName;
}
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState); step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState, siblingScopeName);
Root.PostJobSteps.Push(step); 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)) 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)
{ {
Trace.Entering(); Trace.Entering();
@@ -286,6 +293,7 @@ namespace GitHub.Runner.Worker
child.ScopeName = scopeName; child.ScopeName = scopeName;
child.ContextName = contextName; child.ContextName = contextName;
child.EmbeddedId = embeddedId; child.EmbeddedId = embeddedId;
child.SiblingScopeName = siblingScopeName;
if (intraActionState == null) if (intraActionState == null)
{ {
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -333,9 +341,9 @@ namespace GitHub.Runner.Worker
/// An embedded execution context shares the same record ID, record name, logger, /// An embedded execution context shares the same record ID, record name, logger,
/// and a linked cancellation token. /// and a linked cancellation token.
/// </summary> /// </summary>
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null) public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, 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); 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);
} }
public void Start(string currentOperation = null) public void Start(string currentOperation = null)
@@ -914,7 +922,7 @@ namespace GitHub.Runner.Worker
} }
} }
private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState) private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState, string siblingScopeName = null)
{ {
if (!_expandedForPostJob) if (!_expandedForPostJob)
{ {
@@ -924,7 +932,7 @@ namespace GitHub.Runner.Worker
} }
var newGuid = Guid.NewGuid(); var newGuid = Guid.NewGuid();
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count); return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
} }
} }

View File

@@ -128,17 +128,31 @@ namespace GitHub.Runner.Worker.Handlers
embeddedSteps.Add(step); embeddedSteps.Add(step);
} }
} }
foreach (Pipelines.ActionStep stepData in steps) foreach (Pipelines.ActionStep stepData in steps)
{ {
// Compute child sibling scope names for post steps
// We need to use the main's scope to keep step context correct, makes inputs flow correctly
string siblingScopeName = null;
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName) && stage == ActionRunStage.Post)
{
siblingScopeName = $"{ExecutionContext.SiblingScopeName}.{stepData.ContextName}";
}
var step = HostContext.CreateService<IActionRunner>(); var step = HostContext.CreateService<IActionRunner>();
step.Action = stepData; step.Action = stepData;
step.Stage = stage; step.Stage = stage;
step.Condition = stepData.Condition; step.Condition = stepData.Condition;
ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState); ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState);
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState); step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
step.ExecutionContext.ExpressionValues["inputs"] = inputsData; step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName))
{
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(ExecutionContext.SiblingScopeName);
}
else
{
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName); step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
}
// Shallow copy github context // Shallow copy github context
var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext; var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext;
@@ -153,7 +167,7 @@ namespace GitHub.Runner.Worker.Handlers
} }
// Run embedded steps // Run embedded steps
await RunStepsAsync(embeddedSteps); await RunStepsAsync(embeddedSteps, stage);
// Set outputs // Set outputs
ExecutionContext.ExpressionValues["inputs"] = inputsData; ExecutionContext.ExpressionValues["inputs"] = inputsData;
@@ -212,7 +226,7 @@ namespace GitHub.Runner.Worker.Handlers
} }
} }
private async Task RunStepsAsync(List<IStep> embeddedSteps) private async Task RunStepsAsync(List<IStep> embeddedSteps, ActionRunStage stage)
{ {
ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps)); ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps));
@@ -280,6 +294,14 @@ namespace GitHub.Runner.Worker.Handlers
// Register Callback // Register Callback
CancellationTokenRegistration? jobCancelRegister = null; CancellationTokenRegistration? jobCancelRegister = null;
try try
{
// For main steps just run the action
if (stage == ActionRunStage.Main)
{
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 // Register job cancellation call back only if job cancellation token not been fire before each step run
if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested) if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested)
@@ -375,6 +397,7 @@ namespace GitHub.Runner.Worker.Handlers
await RunStepAsync(step); await RunStepAsync(step);
} }
} }
}
finally finally
{ {
if (jobCancelRegister != null) if (jobCancelRegister != null)
@@ -388,9 +411,13 @@ namespace GitHub.Runner.Worker.Handlers
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.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}'."); Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
ExecutionContext.Result = step.ExecutionContext.Result; ExecutionContext.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Result, step.ExecutionContext.Result.Value);
ExecutionContext.Root.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Root.Result, step.ExecutionContext.Result.Value);
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult(); // We should run cleanup even if one of the cleanup step fails
if (stage != ActionRunStage.Post)
{
break;
}
} }
} }
} }

View File

@@ -18,5 +18,12 @@ namespace GitHub.DistributedTask.WebApi
get; get;
set; set;
} }
[DataMember]
public string Path
{
get;
set;
}
} }
} }

View File

@@ -1 +1 @@
2.280.0 2.280.3