Compare commits

..

4 Commits

Author SHA1 Message Date
Julio Barba
b1bb7b0279 More PR feedback, standardize 'OAuth token' terms 2020-03-09 12:56:49 -04:00
Julio Barba
62e700f052 Add diagrams 2020-03-06 12:21:47 -05:00
Julio Barba
d81e5099a0 Address PR suggestions from Ting 2020-03-05 13:24:14 -05:00
Julio Barba
28312e5637 Add runner auth documentation 2020-03-04 17:12:22 -05:00
47 changed files with 419 additions and 2922 deletions

View File

@@ -1,62 +0,0 @@
# ADR 0274: Step outcome and conclusion
**Date**: 2020-01-13
**Status**: Accepted
## Context
This ADR proposes adding `steps.<id>.outcome` and `steps.<id>.conclusion` to the steps context.
This allows downstream a step to run based on whether a previous step succeeded or failed.
Reminder, currently the steps contains `steps.<id>.outputs`.
## Decision
For steps that have completed, populate `steps.<id>.outcome` and `steps.<id>.conclusion` with one of the following values:
- `success`
- `failure`
- `cancelled`
- `skipped`
When a continue-on-error step fails, the outcome will be `failure` even though the final conclusion is `success`.
### Example
```yaml
steps:
- id: experimental
continue-on-error: true
run: ./build.sh experimental
- if: ${{ steps.experimental.outcome == 'success' }}
run: ./publish.sh experimental
```
### Terminology
The runs API uses the term `conclusion`.
Therefore we use a different term `outcome` for the value prior to continue-on-error.
The following is a snippet from the runs API response payload:
```json
"steps": [
{
"name": "Set up job",
"status": "completed",
"conclusion": "success",
"number": 1,
"started_at": "2020-01-09T11:06:16.000-05:00",
"completed_at": "2020-01-09T11:06:18.000-05:00"
},
```
## Consequences
- Update runner
- Update [docs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#steps-context)

View File

@@ -1,35 +0,0 @@
# ADR 354: Expose runner machine info
**Date**: 2020-03-02
**Status**: Pending
## Context
- Provide a mechanism in the runner to include extra information in `Set up job` step's log.
Ex: Include OS/Software info from Hosted image.
## Decision
The runner will look for a file `.setup_info` under the runner's root directory, The file can be a JSON with a simple schema.
```json
[
{
"group": "OS Detail",
"detail": "........"
},
{
"group": "Software Detail",
"detail": "........"
}
]
```
The runner will use `##[group]` and `##[endgroup]` to fold all detail info into an expandable group.
Both [virtual-environments](https://github.com/actions/virtual-environments) and self-hosted runners can use this mechanism to add extra logging info to the `Set up job` step's log.
## Consequences
1. Change the runner to best effort read/parse `.extra_setup_info` file under runner root directory.
2. [virtual-environments](https://github.com/actions/virtual-environments) generate the file during image generation.
3. Change MMS provisioner to properly copy the file to runner root directory at runtime.

View File

@@ -44,7 +44,7 @@ Sample developer flow:
```bash ```bash
git clone https://github.com/actions/runner git clone https://github.com/actions/runner
cd ./src cd ./src
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout ./dev.(sh/cmd) layout # the runner that build from source is in {root}/_layout
<make code changes> <make code changes>
./dev.(sh/cmd) build # {root}/_layout will get updated ./dev.(sh/cmd) build # {root}/_layout will get updated
./dev.(sh/cmd) test # run all unit tests before git commit/push ./dev.(sh/cmd) test # run all unit tests before git commit/push

View File

@@ -78,10 +78,8 @@ namespace GitHub.Runner.Common
bool IsServiceConfigured(); bool IsServiceConfigured();
bool HasCredentials(); bool HasCredentials();
CredentialData GetCredentials(); CredentialData GetCredentials();
CredentialData GetMigratedCredentials();
RunnerSettings GetSettings(); RunnerSettings GetSettings();
void SaveCredential(CredentialData credential); void SaveCredential(CredentialData credential);
void SaveMigratedCredential(CredentialData credential);
void SaveSettings(RunnerSettings settings); void SaveSettings(RunnerSettings settings);
void DeleteCredential(); void DeleteCredential();
void DeleteSettings(); void DeleteSettings();
@@ -92,11 +90,9 @@ namespace GitHub.Runner.Common
private string _binPath; private string _binPath;
private string _configFilePath; private string _configFilePath;
private string _credFilePath; private string _credFilePath;
private string _migratedCredFilePath;
private string _serviceConfigFilePath; private string _serviceConfigFilePath;
private CredentialData _creds; private CredentialData _creds;
private CredentialData _migratedCreds;
private RunnerSettings _settings; private RunnerSettings _settings;
public override void Initialize(IHostContext hostContext) public override void Initialize(IHostContext hostContext)
@@ -118,9 +114,6 @@ namespace GitHub.Runner.Common
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials); _credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
Trace.Info("CredFilePath: {0}", _credFilePath); Trace.Info("CredFilePath: {0}", _credFilePath);
_migratedCredFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedCredentials);
Trace.Info("MigratedCredFilePath: {0}", _migratedCredFilePath);
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service); _serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath); Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
} }
@@ -130,7 +123,7 @@ namespace GitHub.Runner.Common
public bool HasCredentials() public bool HasCredentials()
{ {
Trace.Info("HasCredentials()"); Trace.Info("HasCredentials()");
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists; bool credsStored = (new FileInfo(_credFilePath)).Exists;
Trace.Info("stored {0}", credsStored); Trace.Info("stored {0}", credsStored);
return credsStored; return credsStored;
} }
@@ -161,16 +154,6 @@ namespace GitHub.Runner.Common
return _creds; return _creds;
} }
public CredentialData GetMigratedCredentials()
{
if (_migratedCreds == null && File.Exists(_migratedCredFilePath))
{
_migratedCreds = IOUtil.LoadObject<CredentialData>(_migratedCredFilePath);
}
return _migratedCreds;
}
public RunnerSettings GetSettings() public RunnerSettings GetSettings()
{ {
if (_settings == null) if (_settings == null)
@@ -205,21 +188,6 @@ namespace GitHub.Runner.Common
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden); File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
} }
public void SaveMigratedCredential(CredentialData credential)
{
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
if (File.Exists(_migratedCredFilePath))
{
// Delete existing credential file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runner migrated credential file.");
IOUtil.DeleteFile(_migratedCredFilePath);
}
IOUtil.SaveObject(credential, _migratedCredFilePath);
Trace.Info("Migrated Credentials Saved.");
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
}
public void SaveSettings(RunnerSettings settings) public void SaveSettings(RunnerSettings settings)
{ {
Trace.Info("Saving runner settings."); Trace.Info("Saving runner settings.");
@@ -238,7 +206,6 @@ namespace GitHub.Runner.Common
public void DeleteCredential() public void DeleteCredential()
{ {
IOUtil.Delete(_credFilePath, default(CancellationToken)); IOUtil.Delete(_credFilePath, default(CancellationToken));
IOUtil.Delete(_migratedCredFilePath, default(CancellationToken));
} }
public void DeleteSettings() public void DeleteSettings()

View File

@@ -19,13 +19,11 @@ namespace GitHub.Runner.Common
{ {
Runner, Runner,
Credentials, Credentials,
MigratedCredentials,
RSACredentials, RSACredentials,
Service, Service,
CredentialStore, CredentialStore,
Certificates, Certificates,
Options, Options,
SetupInfo,
} }
public static class Constants public static class Constants

View File

@@ -281,12 +281,6 @@ namespace GitHub.Runner.Common
".credentials"); ".credentials");
break; break;
case WellKnownConfigFile.MigratedCredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credentials_migrated");
break;
case WellKnownConfigFile.RSACredentials: case WellKnownConfigFile.RSACredentials:
path = Path.Combine( path = Path.Combine(
GetDirectory(WellKnownDirectory.Root), GetDirectory(WellKnownDirectory.Root),
@@ -322,13 +316,6 @@ namespace GitHub.Runner.Common
GetDirectory(WellKnownDirectory.Root), GetDirectory(WellKnownDirectory.Root),
".options"); ".options");
break; break;
case WellKnownConfigFile.SetupInfo:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".setup_info");
break;
default: default:
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'"); throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
} }

View File

@@ -50,10 +50,6 @@ namespace GitHub.Runner.Common
// agent update // agent update
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState); Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState);
// runner authorization url
Task<string> GetRunnerAuthUrlAsync(int runnerPoolId, int runnerId);
Task ReportRunnerAuthUrlErrorAsync(int runnerPoolId, int runnerId, string error);
} }
public sealed class RunnerServer : RunnerService, IRunnerServer public sealed class RunnerServer : RunnerService, IRunnerServer
@@ -338,20 +334,5 @@ namespace GitHub.Runner.Common
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState); return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState);
} }
//-----------------------------------------------------------------
// Runner Auth Url
//-----------------------------------------------------------------
public Task<string> GetRunnerAuthUrlAsync(int runnerPoolId, int runnerId)
{
CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.GetAgentAuthUrlAsync(runnerPoolId, runnerId);
}
public Task ReportRunnerAuthUrlErrorAsync(int runnerPoolId, int runnerId, string error)
{
CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.ReportAgentAuthUrlMigrationErrorAsync(runnerPoolId, runnerId, error);
}
} }
} }

View File

@@ -13,7 +13,7 @@ namespace GitHub.Runner.Listener.Configuration
public interface ICredentialManager : IRunnerService public interface ICredentialManager : IRunnerService
{ {
ICredentialProvider GetCredentialProvider(string credType); ICredentialProvider GetCredentialProvider(string credType);
VssCredentials LoadCredentials(bool preferMigrated = true); VssCredentials LoadCredentials();
} }
public class CredentialManager : RunnerService, ICredentialManager public class CredentialManager : RunnerService, ICredentialManager
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Listener.Configuration
return creds; return creds;
} }
public VssCredentials LoadCredentials(bool preferMigrated = true) public VssCredentials LoadCredentials()
{ {
IConfigurationStore store = HostContext.GetService<IConfigurationStore>(); IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
@@ -50,16 +50,6 @@ namespace GitHub.Runner.Listener.Configuration
} }
CredentialData credData = store.GetCredentials(); CredentialData credData = store.GetCredentials();
if (preferMigrated)
{
var migratedCred = store.GetMigratedCredentials();
if (migratedCred != null)
{
credData = migratedCred;
}
}
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme); ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
credProv.CredentialData = credData; credProv.CredentialData = credData;

View File

@@ -1,5 +1,6 @@
using System; using System;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Services.OAuth; using GitHub.Services.OAuth;
@@ -28,7 +29,7 @@ namespace GitHub.Runner.Listener.Configuration
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null); var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section // For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl); var oathEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
ArgUtil.NotNullOrEmpty(clientId, nameof(clientId)); ArgUtil.NotNullOrEmpty(clientId, nameof(clientId));
ArgUtil.NotNullOrEmpty(authorizationUrl, nameof(authorizationUrl)); ArgUtil.NotNullOrEmpty(authorizationUrl, nameof(authorizationUrl));
@@ -38,7 +39,7 @@ namespace GitHub.Runner.Listener.Configuration
var keyManager = context.GetService<IRSAKeyManager>(); var keyManager = context.GetService<IRSAKeyManager>();
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey()); var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials); var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential); var agentCredential = new VssOAuthCredential(new Uri(oathEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
// Construct a credentials cache with a single OAuth credential for communication. The windows credential // Construct a credentials cache with a single OAuth credential for communication. The windows credential
// is explicitly set to null to ensure we never do that negotiation. // is explicitly set to null to ensure we never do that negotiation.

View File

@@ -18,7 +18,6 @@ namespace GitHub.Runner.Listener
[ServiceLocator(Default = typeof(JobDispatcher))] [ServiceLocator(Default = typeof(JobDispatcher))]
public interface IJobDispatcher : IRunnerService public interface IJobDispatcher : IRunnerService
{ {
bool Busy { get; }
TaskCompletionSource<bool> RunOnceJobCompleted { get; } TaskCompletionSource<bool> RunOnceJobCompleted { get; }
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false); void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
bool Cancel(JobCancelMessage message); bool Cancel(JobCancelMessage message);
@@ -70,8 +69,6 @@ namespace GitHub.Runner.Listener
public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted; public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted;
public bool Busy { get; private set; }
public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false) public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false)
{ {
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received."); Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
@@ -250,7 +247,7 @@ namespace GitHub.Runner.Listener
Task completedTask = await Task.WhenAny(jobDispatch.WorkerDispatch, Task.Delay(TimeSpan.FromSeconds(45))); Task completedTask = await Task.WhenAny(jobDispatch.WorkerDispatch, Task.Delay(TimeSpan.FromSeconds(45)));
if (completedTask != jobDispatch.WorkerDispatch) if (completedTask != jobDispatch.WorkerDispatch)
{ {
// at this point, the job execution might encounter some dead lock and even not able to be cancelled. // at this point, the job exectuion might encounter some dead lock and even not able to be canclled.
// no need to localize the exception string should never happen. // no need to localize the exception string should never happen.
throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds."); throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds.");
} }
@@ -299,143 +296,269 @@ namespace GitHub.Runner.Listener
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken) private async Task RunAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
{ {
Busy = true; if (previousJobDispatch != null)
try
{ {
if (previousJobDispatch != null) Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker.");
await EnsureDispatchFinished(previousJobDispatch);
}
else
{
Trace.Verbose($"This is the first job request.");
}
var term = HostContext.GetService<ITerminal>();
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
// first job request renew succeed.
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
var notification = HostContext.GetService<IJobNotification>();
// lock renew cancellation token.
using (var lockRenewalTokenSource = new CancellationTokenSource())
using (var workerProcessCancelTokenSource = new CancellationTokenSource())
{
long requestId = message.RequestId;
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
// start renew job request
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, firstJobRequestRenewed, lockRenewalTokenSource.Token);
// wait till first renew succeed or job request is canceled
// not even start worker if the first renew fail
await Task.WhenAny(firstJobRequestRenewed.Task, renewJobRequest, Task.Delay(-1, jobRequestCancellationToken));
if (renewJobRequest.IsCompleted)
{ {
Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker."); // renew job request task complete means we run out of retry for the first job request renew.
await EnsureDispatchFinished(previousJobDispatch); Trace.Info($"Unable to renew job request for job {message.JobId} for the first time, stop dispatching job to worker.");
} return;
else
{
Trace.Verbose($"This is the first job request.");
} }
var term = HostContext.GetService<ITerminal>(); if (jobRequestCancellationToken.IsCancellationRequested)
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
// first job request renew succeed.
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
var notification = HostContext.GetService<IJobNotification>();
// lock renew cancellation token.
using (var lockRenewalTokenSource = new CancellationTokenSource())
using (var workerProcessCancelTokenSource = new CancellationTokenSource())
{ {
long requestId = message.RequestId; Trace.Info($"Stop renew job request for job {message.JobId}.");
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat // stop renew lock
lockRenewalTokenSource.Cancel();
// renew job request should never blows up.
await renewJobRequest;
// start renew job request // complete job request with result Cancelled
Trace.Info($"Start renew job request {requestId} for job {message.JobId}."); await CompleteJobRequestAsync(_poolId, message, lockToken, TaskResult.Canceled);
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, firstJobRequestRenewed, lockRenewalTokenSource.Token); return;
}
// wait till first renew succeed or job request is canceled HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
// not even start worker if the first renew fail
await Task.WhenAny(firstJobRequestRenewed.Task, renewJobRequest, Task.Delay(-1, jobRequestCancellationToken));
if (renewJobRequest.IsCompleted) Task<int> workerProcessTask = null;
object _outputLock = new object();
List<string> workerOutput = new List<string>();
using (var processChannel = HostContext.CreateService<IProcessChannel>())
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
{
// Start the process channel.
// It's OK if StartServer bubbles an execption after the worker process has already started.
// The worker will shutdown after 30 seconds if it hasn't received the job message.
processChannel.StartServer(
// Delegate to start the child process.
startProcess: (string pipeHandleOut, string pipeHandleIn) =>
{
// Validate args.
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut));
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn));
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (_outputLock)
{
workerOutput.Add(stdout.Data);
}
}
};
// Save STDERR from worker, worker will use STDERR on crash.
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (_outputLock)
{
workerOutput.Add(stderr.Data);
}
}
};
// Start the child process.
HostContext.WritePerfCounter("StartingWorkerProcess");
var assemblyDirectory = HostContext.GetDirectory(WellKnownDirectory.Bin);
string workerFileName = Path.Combine(assemblyDirectory, _workerProcessName);
workerProcessTask = processInvoker.ExecuteAsync(
workingDirectory: assemblyDirectory,
fileName: workerFileName,
arguments: "spawnclient " + pipeHandleOut + " " + pipeHandleIn,
environment: null,
requireExitCodeZero: false,
outputEncoding: null,
killProcessOnCancel: true,
redirectStandardIn: null,
inheritConsoleHandler: false,
keepStandardInOpen: false,
highPriorityProcess: true,
cancellationToken: workerProcessCancelTokenSource.Token);
});
// Send the job request message.
// Kill the worker process if sending the job message times out. The worker
// process may have successfully received the job message.
try
{ {
// renew job request task complete means we run out of retry for the first job request renew. Trace.Info($"Send job request message to worker for job {message.JobId}.");
Trace.Info($"Unable to renew job request for job {message.JobId} for the first time, stop dispatching job to worker."); HostContext.WritePerfCounter($"RunnerSendingJobToWorker_{message.JobId}");
return; using (var csSendJobRequest = new CancellationTokenSource(_channelTimeout))
{
await processChannel.SendAsync(
messageType: MessageType.NewJobRequest,
body: JsonUtility.ToString(message),
cancellationToken: csSendJobRequest.Token);
}
} }
catch (OperationCanceledException)
if (jobRequestCancellationToken.IsCancellationRequested)
{ {
// message send been cancelled.
// timeout 30 sec. kill worker.
Trace.Info($"Job request message sending for job {message.JobId} been cancelled, kill running worker.");
workerProcessCancelTokenSource.Cancel();
try
{
await workerProcessTask;
}
catch (OperationCanceledException)
{
Trace.Info("worker process has been killed.");
}
Trace.Info($"Stop renew job request for job {message.JobId}."); Trace.Info($"Stop renew job request for job {message.JobId}.");
// stop renew lock // stop renew lock
lockRenewalTokenSource.Cancel(); lockRenewalTokenSource.Cancel();
// renew job request should never blows up. // renew job request should never blows up.
await renewJobRequest; await renewJobRequest;
// complete job request with result Cancelled // not finish the job request since the job haven't run on worker at all, we will not going to set a result to server.
await CompleteJobRequestAsync(_poolId, message, lockToken, TaskResult.Canceled);
return; return;
} }
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}"); // we get first jobrequest renew succeed and start the worker process with the job message.
// send notification to machine provisioner.
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
Task<int> workerProcessTask = null; HostContext.WritePerfCounter($"SentJobToWorker_{requestId.ToString()}");
object _outputLock = new object();
List<string> workerOutput = new List<string>(); try
using (var processChannel = HostContext.CreateService<IProcessChannel>())
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
{ {
// Start the process channel. TaskResult resultOnAbandonOrCancel = TaskResult.Succeeded;
// It's OK if StartServer bubbles an execption after the worker process has already started. // wait for renewlock, worker process or cancellation token been fired.
// The worker will shutdown after 30 seconds if it hasn't received the job message. var completedTask = await Task.WhenAny(renewJobRequest, workerProcessTask, Task.Delay(-1, jobRequestCancellationToken));
processChannel.StartServer( if (completedTask == workerProcessTask)
// Delegate to start the child process. {
startProcess: (string pipeHandleOut, string pipeHandleIn) => // worker finished successfully, complete job request with result, attach unhandled exception reported by worker, stop renew lock, job has finished.
int returnCode = await workerProcessTask;
Trace.Info($"Worker finished for job {message.JobId}. Code: " + returnCode);
string detailInfo = null;
if (!TaskResultUtil.IsValidReturnCode(returnCode))
{ {
// Validate args. detailInfo = string.Join(Environment.NewLine, workerOutput);
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut)); Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn)); await LogWorkerProcessUnhandledException(message, detailInfo);
}
// Save STDOUT from worker, worker will use STDOUT report unhandle exception. TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout) Trace.Info($"finish job request for job {message.JobId} with result: {result}");
{ term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {result}");
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (_outputLock)
{
workerOutput.Add(stdout.Data);
}
}
};
// Save STDERR from worker, worker will use STDERR on crash. Trace.Info($"Stop renew job request for job {message.JobId}.");
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr) // stop renew lock
{ lockRenewalTokenSource.Cancel();
if (!string.IsNullOrEmpty(stderr.Data)) // renew job request should never blows up.
{ await renewJobRequest;
lock (_outputLock)
{
workerOutput.Add(stderr.Data);
}
}
};
// Start the child process. // complete job request
HostContext.WritePerfCounter("StartingWorkerProcess"); await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo);
var assemblyDirectory = HostContext.GetDirectory(WellKnownDirectory.Bin);
string workerFileName = Path.Combine(assemblyDirectory, _workerProcessName);
workerProcessTask = processInvoker.ExecuteAsync(
workingDirectory: assemblyDirectory,
fileName: workerFileName,
arguments: "spawnclient " + pipeHandleOut + " " + pipeHandleIn,
environment: null,
requireExitCodeZero: false,
outputEncoding: null,
killProcessOnCancel: true,
redirectStandardIn: null,
inheritConsoleHandler: false,
keepStandardInOpen: false,
highPriorityProcess: true,
cancellationToken: workerProcessCancelTokenSource.Token);
});
// Send the job request message. // print out unhandled exception happened in worker after we complete job request.
// Kill the worker process if sending the job message times out. The worker // when we run out of disk space, report back to server has higher priority.
// process may have successfully received the job message. if (!string.IsNullOrEmpty(detailInfo))
{
Trace.Error("Unhandled exception happened in worker:");
Trace.Error(detailInfo);
}
return;
}
else if (completedTask == renewJobRequest)
{
resultOnAbandonOrCancel = TaskResult.Abandoned;
}
else
{
resultOnAbandonOrCancel = TaskResult.Canceled;
}
// renew job request completed or job request cancellation token been fired for RunAsync(jobrequestmessage)
// cancel worker gracefully first, then kill it after worker cancel timeout
try try
{ {
Trace.Info($"Send job request message to worker for job {message.JobId}."); Trace.Info($"Send job cancellation message to worker for job {message.JobId}.");
HostContext.WritePerfCounter($"RunnerSendingJobToWorker_{message.JobId}"); using (var csSendCancel = new CancellationTokenSource(_channelTimeout))
using (var csSendJobRequest = new CancellationTokenSource(_channelTimeout))
{ {
var messageType = MessageType.CancelRequest;
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
switch (HostContext.RunnerShutdownReason)
{
case ShutdownReason.UserCancelled:
messageType = MessageType.RunnerShutdown;
break;
case ShutdownReason.OperatingSystemShutdown:
messageType = MessageType.OperatingSystemShutdown;
break;
}
}
await processChannel.SendAsync( await processChannel.SendAsync(
messageType: MessageType.NewJobRequest, messageType: messageType,
body: JsonUtility.ToString(message), body: string.Empty,
cancellationToken: csSendJobRequest.Token); cancellationToken: csSendCancel.Token);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// message send been cancelled. // message send been cancelled.
// timeout 30 sec. kill worker. Trace.Info($"Job cancel message sending for job {message.JobId} been cancelled, kill running worker.");
Trace.Info($"Job request message sending for job {message.JobId} been cancelled, kill running worker."); workerProcessCancelTokenSource.Cancel();
try
{
await workerProcessTask;
}
catch (OperationCanceledException)
{
Trace.Info("worker process has been killed.");
}
}
// wait worker to exit
// if worker doesn't exit within timeout, then kill worker.
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
// worker haven't exit within cancellation timeout.
if (completedTask != workerProcessTask)
{
Trace.Info($"worker process for job {message.JobId} haven't exit within cancellation timout, kill running worker.");
workerProcessCancelTokenSource.Cancel(); workerProcessCancelTokenSource.Cancel();
try try
{ {
@@ -446,165 +569,31 @@ namespace GitHub.Runner.Listener
Trace.Info("worker process has been killed."); Trace.Info("worker process has been killed.");
} }
Trace.Info($"Stop renew job request for job {message.JobId}."); // When worker doesn't exit within cancel timeout, the runner will kill the worker process and worker won't finish upload job logs.
// stop renew lock // The runner will try to upload these logs at this time.
lockRenewalTokenSource.Cancel(); await TryUploadUnfinishedLogs(message);
// renew job request should never blows up.
await renewJobRequest;
// not finish the job request since the job haven't run on worker at all, we will not going to set a result to server.
return;
} }
// we get first jobrequest renew succeed and start the worker process with the job message. Trace.Info($"finish job request for job {message.JobId} with result: {resultOnAbandonOrCancel}");
// send notification to machine provisioner. term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {resultOnAbandonOrCancel}");
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); // complete job request with cancel result, stop renew lock, job has finished.
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
HostContext.WritePerfCounter($"SentJobToWorker_{requestId.ToString()}"); Trace.Info($"Stop renew job request for job {message.JobId}.");
// stop renew lock
lockRenewalTokenSource.Cancel();
// renew job request should never blows up.
await renewJobRequest;
try // complete job request
{ await CompleteJobRequestAsync(_poolId, message, lockToken, resultOnAbandonOrCancel);
TaskResult resultOnAbandonOrCancel = TaskResult.Succeeded; }
// wait for renewlock, worker process or cancellation token been fired. finally
var completedTask = await Task.WhenAny(renewJobRequest, workerProcessTask, Task.Delay(-1, jobRequestCancellationToken)); {
if (completedTask == workerProcessTask) // This should be the last thing to run so we don't notify external parties until actually finished
{ await notification.JobCompleted(message.JobId);
// worker finished successfully, complete job request with result, attach unhandled exception reported by worker, stop renew lock, job has finished.
int returnCode = await workerProcessTask;
Trace.Info($"Worker finished for job {message.JobId}. Code: " + returnCode);
string detailInfo = null;
if (!TaskResultUtil.IsValidReturnCode(returnCode))
{
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.");
await LogWorkerProcessUnhandledException(message, detailInfo);
}
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
Trace.Info($"finish job request for job {message.JobId} with result: {result}");
term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {result}");
Trace.Info($"Stop renew job request for job {message.JobId}.");
// stop renew lock
lockRenewalTokenSource.Cancel();
// renew job request should never blows up.
await renewJobRequest;
// complete job request
await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo);
// print out unhandled exception happened in worker after we complete job request.
// when we run out of disk space, report back to server has higher priority.
if (!string.IsNullOrEmpty(detailInfo))
{
Trace.Error("Unhandled exception happened in worker:");
Trace.Error(detailInfo);
}
return;
}
else if (completedTask == renewJobRequest)
{
resultOnAbandonOrCancel = TaskResult.Abandoned;
}
else
{
resultOnAbandonOrCancel = TaskResult.Canceled;
}
// renew job request completed or job request cancellation token been fired for RunAsync(jobrequestmessage)
// cancel worker gracefully first, then kill it after worker cancel timeout
try
{
Trace.Info($"Send job cancellation message to worker for job {message.JobId}.");
using (var csSendCancel = new CancellationTokenSource(_channelTimeout))
{
var messageType = MessageType.CancelRequest;
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
{
switch (HostContext.RunnerShutdownReason)
{
case ShutdownReason.UserCancelled:
messageType = MessageType.RunnerShutdown;
break;
case ShutdownReason.OperatingSystemShutdown:
messageType = MessageType.OperatingSystemShutdown;
break;
}
}
await processChannel.SendAsync(
messageType: messageType,
body: string.Empty,
cancellationToken: csSendCancel.Token);
}
}
catch (OperationCanceledException)
{
// message send been cancelled.
Trace.Info($"Job cancel message sending for job {message.JobId} been cancelled, kill running worker.");
workerProcessCancelTokenSource.Cancel();
try
{
await workerProcessTask;
}
catch (OperationCanceledException)
{
Trace.Info("worker process has been killed.");
}
}
// wait worker to exit
// if worker doesn't exit within timeout, then kill worker.
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
// worker haven't exit within cancellation timeout.
if (completedTask != workerProcessTask)
{
Trace.Info($"worker process for job {message.JobId} haven't exit within cancellation timout, kill running worker.");
workerProcessCancelTokenSource.Cancel();
try
{
await workerProcessTask;
}
catch (OperationCanceledException)
{
Trace.Info("worker process has been killed.");
}
// When worker doesn't exit within cancel timeout, the runner will kill the worker process and worker won't finish upload job logs.
// The runner will try to upload these logs at this time.
await TryUploadUnfinishedLogs(message);
}
Trace.Info($"finish job request for job {message.JobId} with result: {resultOnAbandonOrCancel}");
term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {resultOnAbandonOrCancel}");
// complete job request with cancel result, stop renew lock, job has finished.
Trace.Info($"Stop renew job request for job {message.JobId}.");
// stop renew lock
lockRenewalTokenSource.Cancel();
// renew job request should never blows up.
await renewJobRequest;
// complete job request
await CompleteJobRequestAsync(_poolId, message, lockToken, resultOnAbandonOrCancel);
}
finally
{
// This should be the last thing to run so we don't notify external parties until actually finished
await notification.JobCompleted(message.JobId);
}
} }
} }
} }
finally
{
Busy = false;
}
} }
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token) public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)

View File

@@ -13,10 +13,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.WebApi;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Test")]
namespace GitHub.Runner.Listener namespace GitHub.Runner.Listener
{ {
[ServiceLocator(Default = typeof(MessageListener))] [ServiceLocator(Default = typeof(MessageListener))]
@@ -35,30 +32,18 @@ namespace GitHub.Runner.Listener
private ITerminal _term; private ITerminal _term;
private IRunnerServer _runnerServer; private IRunnerServer _runnerServer;
private TaskAgentSession _session; private TaskAgentSession _session;
private ICredentialManager _credMgr;
private IConfigurationStore _configStore;
private TimeSpan _getNextMessageRetryInterval; private TimeSpan _getNextMessageRetryInterval;
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30); private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4); private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30); private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>(); private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
// Whether load credentials from .credentials_migrated file
internal bool _useMigratedCredentials;
// need to check auth url if there is only .credentials and auth schema is OAuth
internal bool _needToCheckAuthorizationUrlUpdate;
internal Task<VssCredentials> _authorizationUrlMigrationBackgroundTask;
internal Task _authorizationUrlRollbackReattemptDelayBackgroundTask;
public override void Initialize(IHostContext hostContext) public override void Initialize(IHostContext hostContext)
{ {
base.Initialize(hostContext); base.Initialize(hostContext);
_term = HostContext.GetService<ITerminal>(); _term = HostContext.GetService<ITerminal>();
_runnerServer = HostContext.GetService<IRunnerServer>(); _runnerServer = HostContext.GetService<IRunnerServer>();
_credMgr = HostContext.GetService<ICredentialManager>();
_configStore = HostContext.GetService<IConfigurationStore>();
} }
public async Task<Boolean> CreateSessionAsync(CancellationToken token) public async Task<Boolean> CreateSessionAsync(CancellationToken token)
@@ -73,8 +58,8 @@ namespace GitHub.Runner.Listener
// Create connection. // Create connection.
Trace.Info("Loading Credentials"); Trace.Info("Loading Credentials");
_useMigratedCredentials = !StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_SPSAUTHURL")); var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials creds = _credMgr.LoadCredentials(_useMigratedCredentials); VssCredentials creds = credMgr.LoadCredentials();
var agent = new TaskAgentReference var agent = new TaskAgentReference
{ {
@@ -89,17 +74,6 @@ namespace GitHub.Runner.Listener
string errorMessage = string.Empty; string errorMessage = string.Empty;
bool encounteringError = false; bool encounteringError = false;
var originalCreds = _configStore.GetCredentials();
var migratedCreds = _configStore.GetMigratedCredentials();
if (migratedCreds == null)
{
_useMigratedCredentials = false;
if (originalCreds.Scheme == Constants.Configuration.OAuth)
{
_needToCheckAuthorizationUrlUpdate = true;
}
}
while (true) while (true)
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@@ -109,7 +83,7 @@ namespace GitHub.Runner.Listener
Trace.Info("Connecting to the Runner Server..."); Trace.Info("Connecting to the Runner Server...");
await _runnerServer.ConnectAsync(new Uri(serverUrl), creds); await _runnerServer.ConnectAsync(new Uri(serverUrl), creds);
Trace.Info("VssConnection created"); Trace.Info("VssConnection created");
_term.WriteLine(); _term.WriteLine();
_term.WriteSuccessMessage("Connected to GitHub"); _term.WriteSuccessMessage("Connected to GitHub");
_term.WriteLine(); _term.WriteLine();
@@ -127,12 +101,6 @@ namespace GitHub.Runner.Listener
encounteringError = false; encounteringError = false;
} }
if (_needToCheckAuthorizationUrlUpdate)
{
// start background task try to get new authorization url
_authorizationUrlMigrationBackgroundTask = GetNewOAuthAuthorizationSetting(token);
}
return true; return true;
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
@@ -152,21 +120,8 @@ namespace GitHub.Runner.Listener
if (!IsSessionCreationExceptionRetriable(ex)) if (!IsSessionCreationExceptionRetriable(ex))
{ {
if (_useMigratedCredentials) _term.WriteError($"Failed to create session. {ex.Message}");
{ return false;
// migrated credentials might cause lose permission during permission check,
// we will force to use original credential and try again
_useMigratedCredentials = false;
var reattemptBackoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromHours(24), TimeSpan.FromHours(36));
_authorizationUrlRollbackReattemptDelayBackgroundTask = HostContext.Delay(reattemptBackoff, token); // retry migrated creds in 24-36 hours.
creds = _credMgr.LoadCredentials(false);
Trace.Error("Fallback to original credentials and try again.");
}
else
{
_term.WriteError($"Failed to create session. {ex.Message}");
return false;
}
} }
if (!encounteringError) //print the message only on the first error if (!encounteringError) //print the message only on the first error
@@ -227,51 +182,6 @@ namespace GitHub.Runner.Listener
encounteringError = false; encounteringError = false;
continuousError = 0; continuousError = 0;
} }
if (_needToCheckAuthorizationUrlUpdate &&
_authorizationUrlMigrationBackgroundTask?.IsCompleted == true)
{
if (HostContext.GetService<IJobDispatcher>().Busy ||
HostContext.GetService<ISelfUpdater>().Busy)
{
Trace.Info("Job or runner updates in progress, update credentials next time.");
}
else
{
try
{
var newCred = await _authorizationUrlMigrationBackgroundTask;
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), newCred);
Trace.Info("Updated connection to use migrated credential for next GetMessage call.");
_useMigratedCredentials = true;
_authorizationUrlMigrationBackgroundTask = null;
_needToCheckAuthorizationUrlUpdate = false;
}
catch (Exception ex)
{
Trace.Error("Fail to refresh connection with new authorization url.");
Trace.Error(ex);
}
}
}
if (_authorizationUrlRollbackReattemptDelayBackgroundTask?.IsCompleted == true)
{
try
{
// we rolled back to use original creds about 2 days before, now it's a good time to try migrated creds again.
Trace.Info("Re-attempt to use migrated credential");
var migratedCreds = _credMgr.LoadCredentials();
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), migratedCreds);
_useMigratedCredentials = true;
_authorizationUrlRollbackReattemptDelayBackgroundTask = null;
}
catch (Exception ex)
{
Trace.Error("Fail to refresh connection with new authorization url on rollback reattempt.");
Trace.Error(ex);
}
}
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
@@ -295,21 +205,7 @@ namespace GitHub.Runner.Listener
} }
else if (!IsGetNextMessageExceptionRetriable(ex)) else if (!IsGetNextMessageExceptionRetriable(ex))
{ {
if (_useMigratedCredentials) throw;
{
// migrated credentials might cause lose permission during permission check,
// we will force to use original credential and try again
_useMigratedCredentials = false;
var reattemptBackoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromHours(24), TimeSpan.FromHours(36));
_authorizationUrlRollbackReattemptDelayBackgroundTask = HostContext.Delay(reattemptBackoff, token); // retry migrated creds in 24-36 hours.
var originalCreds = _credMgr.LoadCredentials(false);
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), originalCreds);
Trace.Error("Fallback to original credentials and try again.");
}
else
{
throw;
}
} }
else else
{ {
@@ -501,80 +397,5 @@ namespace GitHub.Runner.Listener
return true; return true;
} }
} }
private async Task<VssCredentials> GetNewOAuthAuthorizationSetting(CancellationToken token)
{
Trace.Info("Start checking oauth authorization url update.");
while (true)
{
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(45));
await HostContext.Delay(backoff, token);
try
{
var migratedAuthorizationUrl = await _runnerServer.GetRunnerAuthUrlAsync(_settings.PoolId, _settings.AgentId);
if (!string.IsNullOrEmpty(migratedAuthorizationUrl))
{
var credData = _configStore.GetCredentials();
var clientId = credData.Data.GetValueOrDefault("clientId", null);
var currentAuthorizationUrl = credData.Data.GetValueOrDefault("authorizationUrl", null);
Trace.Info($"Current authorization url: {currentAuthorizationUrl}, new authorization url: {migratedAuthorizationUrl}");
if (string.Equals(currentAuthorizationUrl, migratedAuthorizationUrl, StringComparison.OrdinalIgnoreCase))
{
// We don't need to update credentials.
Trace.Info("No needs to update authorization url");
await Task.Delay(TimeSpan.FromMilliseconds(-1), token);
}
var keyManager = HostContext.GetService<IRSAKeyManager>();
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
var migratedClientCredential = new VssOAuthJwtBearerClientCredential(clientId, migratedAuthorizationUrl, signingCredentials);
var migratedRunnerCredential = new VssOAuthCredential(new Uri(migratedAuthorizationUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, migratedClientCredential);
Trace.Info("Try connect service with Token Service OAuth endpoint.");
var runnerServer = HostContext.CreateService<IRunnerServer>();
await runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), migratedRunnerCredential);
await runnerServer.GetAgentPoolsAsync();
Trace.Info($"Successfully connected service with new authorization url.");
var migratedCredData = new CredentialData
{
Scheme = Constants.Configuration.OAuth,
Data =
{
{ "clientId", clientId },
{ "authorizationUrl", migratedAuthorizationUrl },
{ "oauthEndpointUrl", migratedAuthorizationUrl },
},
};
_configStore.SaveMigratedCredential(migratedCredData);
return migratedRunnerCredential;
}
else
{
Trace.Verbose("No authorization url updates");
}
}
catch (Exception ex)
{
Trace.Error("Fail to get/test new authorization url.");
Trace.Error(ex);
try
{
await _runnerServer.ReportRunnerAuthUrlErrorAsync(_settings.PoolId, _settings.AgentId, ex.ToString());
}
catch (Exception e)
{
// best effort
Trace.Error("Fail to report the migration error");
Trace.Error(e);
}
}
}
}
} }
} }

View File

@@ -17,7 +17,6 @@ namespace GitHub.Runner.Listener
[ServiceLocator(Default = typeof(SelfUpdater))] [ServiceLocator(Default = typeof(SelfUpdater))]
public interface ISelfUpdater : IRunnerService public interface ISelfUpdater : IRunnerService
{ {
bool Busy { get; }
Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token); Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
} }
@@ -32,8 +31,6 @@ namespace GitHub.Runner.Listener
private int _poolId; private int _poolId;
private int _agentId; private int _agentId;
public bool Busy { get; private set; }
public override void Initialize(IHostContext hostContext) public override void Initialize(IHostContext hostContext)
{ {
base.Initialize(hostContext); base.Initialize(hostContext);
@@ -48,60 +45,52 @@ namespace GitHub.Runner.Listener
public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token) public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
{ {
Busy = true; if (!await UpdateNeeded(updateMessage.TargetVersion, token))
try
{ {
if (!await UpdateNeeded(updateMessage.TargetVersion, token)) Trace.Info($"Can't find available update package.");
{ return false;
Trace.Info($"Can't find available update package."); }
return false;
}
Trace.Info($"An update is available."); Trace.Info($"An update is available.");
// Print console line that warn user not shutdown runner. // Print console line that warn user not shutdown runner.
await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner."); await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner.");
await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner"); await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner");
await DownloadLatestRunner(token); await DownloadLatestRunner(token);
Trace.Info($"Download latest runner and unzip into runner root."); Trace.Info($"Download latest runner and unzip into runner root.");
// wait till all running job finish // wait till all running job finish
await UpdateRunnerUpdateStateAsync("Waiting for current job finish running."); await UpdateRunnerUpdateStateAsync("Waiting for current job finish running.");
await jobDispatcher.WaitAsync(token); await jobDispatcher.WaitAsync(token);
Trace.Info($"All running job has exited."); Trace.Info($"All running job has exited.");
// delete runner backup // delete runner backup
DeletePreviousVersionRunnerBackup(token); DeletePreviousVersionRunnerBackup(token);
Trace.Info($"Delete old version runner backup."); Trace.Info($"Delete old version runner backup.");
// generate update script from template // generate update script from template
await UpdateRunnerUpdateStateAsync("Generate and execute update script."); await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
string updateScript = GenerateUpdateScript(restartInteractiveRunner); string updateScript = GenerateUpdateScript(restartInteractiveRunner);
Trace.Info($"Generate update script into: {updateScript}"); Trace.Info($"Generate update script into: {updateScript}");
// kick off update script // kick off update script
Process invokeScript = new Process(); Process invokeScript = new Process();
#if OS_WINDOWS #if OS_WINDOWS
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace); invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\""; invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
#elif (OS_OSX || OS_LINUX) #elif (OS_OSX || OS_LINUX)
invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace); invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace);
invokeScript.StartInfo.Arguments = $"\"{updateScript}\""; invokeScript.StartInfo.Arguments = $"\"{updateScript}\"";
#endif #endif
invokeScript.Start(); invokeScript.Start();
Trace.Info($"Update script start running"); Trace.Info($"Update script start running");
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should back online within 10 seconds."); await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should back online within 10 seconds.");
return true; return true;
}
finally
{
Busy = false;
}
} }
private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token) private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token)

View File

@@ -271,14 +271,6 @@ namespace GitHub.Runner.Sdk
// Indicate GitHub Actions process. // Indicate GitHub Actions process.
_proc.StartInfo.Environment["GITHUB_ACTIONS"] = "true"; _proc.StartInfo.Environment["GITHUB_ACTIONS"] = "true";
// Set CI=true when no one else already set it.
// CI=true is common set in most CI provider in GitHub
if (!_proc.StartInfo.Environment.ContainsKey("CI") &&
Environment.GetEnvironmentVariable("CI") == null)
{
_proc.StartInfo.Environment["CI"] = "true";
}
// Hook up the events. // Hook up the events.
_proc.EnableRaisingEvents = true; _proc.EnableRaisingEvents = true;
_proc.Exited += ProcessExitedHandler; _proc.Exited += ProcessExitedHandler;

View File

@@ -39,7 +39,6 @@ namespace GitHub.Runner.Worker
string ContextName { get; } string ContextName { get; }
Task ForceCompleted { get; } Task ForceCompleted { get; }
TaskResult? Result { get; set; } TaskResult? Result { get; set; }
TaskResult? Outcome { get; set; }
string ResultCode { get; set; } string ResultCode { get; set; }
TaskResult? CommandResult { get; set; } TaskResult? CommandResult { get; set; }
CancellationToken CancellationToken { get; } CancellationToken CancellationToken { get; }
@@ -48,8 +47,7 @@ namespace GitHub.Runner.Worker
PlanFeatures Features { get; } PlanFeatures Features { get; }
Variables Variables { get; } Variables Variables { get; }
Dictionary<string, string> IntraActionState { get; } Dictionary<string, string> IntraActionState { get; }
IDictionary<String, IDictionary<String, String>> JobDefaults { get; } HashSet<string> OutputVariables { get; }
Dictionary<string, VariableValue> JobOutputs { get; }
IDictionary<String, String> EnvironmentVariables { get; } IDictionary<String, String> EnvironmentVariables { get; }
IDictionary<String, ContextScope> Scopes { get; } IDictionary<String, ContextScope> Scopes { get; }
IList<String> FileTable { get; } IList<String> FileTable { get; }
@@ -112,6 +110,7 @@ namespace GitHub.Runner.Worker
private readonly TimelineRecord _record = new TimelineRecord(); private readonly TimelineRecord _record = new TimelineRecord();
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>(); private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
private readonly object _loggerLock = new object(); private readonly object _loggerLock = new object();
private readonly HashSet<string> _outputvariables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly object _matchersLock = new object(); private readonly object _matchersLock = new object();
private event OnMatcherChanged _onMatcherChanged; private event OnMatcherChanged _onMatcherChanged;
@@ -141,8 +140,7 @@ namespace GitHub.Runner.Worker
public List<ServiceEndpoint> Endpoints { get; private set; } public List<ServiceEndpoint> Endpoints { get; private set; }
public Variables Variables { get; private set; } public Variables Variables { get; private set; }
public Dictionary<string, string> IntraActionState { get; private set; } public Dictionary<string, string> IntraActionState { get; private set; }
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; } public HashSet<string> OutputVariables => _outputvariables;
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
public IDictionary<String, String> EnvironmentVariables { get; private set; } public IDictionary<String, String> EnvironmentVariables { get; private set; }
public IDictionary<String, ContextScope> Scopes { get; private set; } public IDictionary<String, ContextScope> Scopes { get; private set; }
public IList<String> FileTable { get; private set; } public IList<String> FileTable { get; private set; }
@@ -174,8 +172,6 @@ namespace GitHub.Runner.Worker
} }
} }
public TaskResult? Outcome { get; set; }
public TaskResult? CommandResult { get; set; } public TaskResult? CommandResult { get; set; }
private string ContextType => _record.RecordType; private string ContextType => _record.RecordType;
@@ -272,7 +268,6 @@ namespace GitHub.Runner.Worker
child.IntraActionState = intraActionState; child.IntraActionState = intraActionState;
} }
child.EnvironmentVariables = EnvironmentVariables; child.EnvironmentVariables = EnvironmentVariables;
child.JobDefaults = JobDefaults;
child.Scopes = Scopes; child.Scopes = Scopes;
child.FileTable = FileTable; child.FileTable = FileTable;
child.StepsContext = StepsContext; child.StepsContext = StepsContext;
@@ -352,12 +347,6 @@ namespace GitHub.Runner.Worker
_logger.End(); _logger.End();
if (!string.IsNullOrEmpty(ContextName))
{
StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult().ToString());
StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult().ToString());
}
return Result.Value; return Result.Value;
} }
@@ -568,12 +557,6 @@ namespace GitHub.Runner.Worker
// Environment variables shared across all actions // Environment variables shared across all actions
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer); EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
// Job defaults shared across all actions
JobDefaults = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
// Job Outputs
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
// Service container info // Service container info
ServiceContainers = new List<ContainerInfo>(); ServiceContainers = new List<ContainerInfo>();
@@ -616,13 +599,8 @@ namespace GitHub.Runner.Worker
var githubAccessToken = new StringContextData(Variables.Get("system.github.token")); var githubAccessToken = new StringContextData(Variables.Get("system.github.token"));
var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}")); var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}"));
HostContext.SecretMasker.AddValue(base64EncodedToken); HostContext.SecretMasker.AddValue(base64EncodedToken);
var githubJob = Variables.Get("system.github.job");
var githubContext = new GitHubContext(); var githubContext = new GitHubContext();
githubContext["token"] = githubAccessToken; githubContext["token"] = githubAccessToken;
if (!string.IsNullOrEmpty(githubJob))
{
githubContext["job"] = new StringContextData(githubJob);
}
var githubDictionary = ExpressionValues["github"].AssertDictionary("github"); var githubDictionary = ExpressionValues["github"].AssertDictionary("github");
foreach (var pair in githubDictionary) foreach (var pair in githubDictionary)
{ {
@@ -758,7 +736,7 @@ namespace GitHub.Runner.Worker
var owners = config.Matchers.Select(x => $"'{x.Owner}'"); var owners = config.Matchers.Select(x => $"'{x.Owner}'");
var joinedOwners = string.Join(", ", owners); var joinedOwners = string.Join(", ", owners);
// todo: loc // todo: loc
this.Debug($"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline."); this.Output($"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline.");
} }
} }
@@ -800,7 +778,7 @@ namespace GitHub.Runner.Worker
owners = removedMatchers.Select(x => $"'{x.Owner}'"); owners = removedMatchers.Select(x => $"'{x.Owner}'");
var joinedOwners = string.Join(", ", owners); var joinedOwners = string.Join(", ", owners);
// todo: loc // todo: loc
this.Debug($"Removed matchers: {joinedOwners}"); this.Output($"Removed matchers: {joinedOwners}");
} }
} }

View File

@@ -14,7 +14,6 @@ namespace GitHub.Runner.Worker
"event_name", "event_name",
"event_path", "event_path",
"head_ref", "head_ref",
"job",
"ref", "ref",
"repository", "repository",
"run_id", "run_id",

View File

@@ -58,21 +58,12 @@ namespace GitHub.Runner.Worker.Handlers
string shellCommandPath = null; string shellCommandPath = null;
bool validateShellOnHost = !(StepHost is ContainerStepHost); bool validateShellOnHost = !(StepHost is ContainerStepHost);
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>()); string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
string shell = null; Inputs.TryGetValue("shell", out var shell);
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
{
// TODO: figure out how defaults interact with template later
// for now, we won't check job.defaults if we are inside a template.
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
{
runDefaults.TryGetValue("shell", out shell);
}
}
if (string.IsNullOrEmpty(shell)) if (string.IsNullOrEmpty(shell))
{ {
#if OS_WINDOWS #if OS_WINDOWS
shellCommand = "pwsh"; shellCommand = "pwsh";
if (validateShellOnHost) if(validateShellOnHost)
{ {
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath); shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
if (string.IsNullOrEmpty(shellCommandPath)) if (string.IsNullOrEmpty(shellCommandPath))
@@ -148,36 +139,11 @@ namespace GitHub.Runner.Worker.Handlers
Inputs.TryGetValue("script", out var contents); Inputs.TryGetValue("script", out var contents);
contents = contents ?? string.Empty; contents = contents ?? string.Empty;
string workingDirectory = null; Inputs.TryGetValue("workingDirectory", out var workingDirectory);
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
{
// TODO: figure out how defaults interact with template later
// for now, we won't check job.defaults if we are inside a template.
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
{
if (runDefaults.TryGetValue("working-directory", out workingDirectory))
{
ExecutionContext.Debug("Overwrite 'working-directory' base on job defaults.");
}
}
}
var workspaceDir = githubContext["workspace"] as StringContextData; var workspaceDir = githubContext["workspace"] as StringContextData;
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty); workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
string shell = null; Inputs.TryGetValue("shell", out var shell);
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
{
// TODO: figure out how defaults interact with template later
// for now, we won't check job.defaults if we are inside a template.
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
{
if (runDefaults.TryGetValue("shell", out shell))
{
ExecutionContext.Debug("Overwrite 'shell' base on job defaults.");
}
}
}
var isContainerStepHost = StepHost is ContainerStepHost; var isContainerStepHost = StepHost is ContainerStepHost;
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>()); string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());

View File

@@ -3,11 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating; using GitHub.DistributedTask.Pipelines.ObjectTemplating;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
@@ -17,16 +14,6 @@ using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker namespace GitHub.Runner.Worker
{ {
[DataContract]
public class SetupInfo
{
[DataMember]
public string Group { get; set; }
[DataMember]
public string Detail { get; set; }
}
[ServiceLocator(Default = typeof(JobExtension))] [ServiceLocator(Default = typeof(JobExtension))]
public interface IJobExtension : IRunnerService public interface IJobExtension : IRunnerService
@@ -62,44 +49,6 @@ namespace GitHub.Runner.Worker
context.Start(); context.Start();
context.Debug($"Starting: Set up job"); context.Debug($"Starting: Set up job");
context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'"); context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
var setupInfoFile = HostContext.GetConfigFile(WellKnownConfigFile.SetupInfo);
if (File.Exists(setupInfoFile))
{
Trace.Info($"Load machine setup info from {setupInfoFile}");
try
{
var setupInfo = IOUtil.LoadObject<List<SetupInfo>>(setupInfoFile);
if (setupInfo?.Count > 0)
{
foreach (var info in setupInfo)
{
if (!string.IsNullOrEmpty(info?.Detail))
{
var groupName = info.Group;
if (string.IsNullOrEmpty(groupName))
{
groupName = "Machine Setup Info";
}
context.Output($"##[group]{groupName}");
var multiLines = info.Detail.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
foreach (var line in multiLines)
{
context.Output(line);
}
context.Output("##[endgroup]");
}
}
}
}
catch (Exception ex)
{
context.Output($"Fail to load and print machine setup info: {ex.Message}");
Trace.Error(ex);
}
}
var repoFullName = context.GetGitHubContext("repository"); var repoFullName = context.GetGitHubContext("repository");
ArgUtil.NotNull(repoFullName, nameof(repoFullName)); ArgUtil.NotNull(repoFullName, nameof(repoFullName));
context.Debug($"Primary repository: {repoFullName}"); context.Debug($"Primary repository: {repoFullName}");
@@ -161,26 +110,6 @@ namespace GitHub.Runner.Worker
} }
} }
// Evaluate the job defaults
context.Debug("Evaluating job defaults");
foreach (var token in message.Defaults)
{
var defaults = token.AssertMapping("defaults");
if (defaults.Any(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase)))
{
context.JobDefaults["run"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase));
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues);
foreach (var pair in jobDefaults)
{
if (!string.IsNullOrEmpty(pair.Value))
{
context.JobDefaults["run"][pair.Key] = pair.Value;
}
}
}
}
// Build up 2 lists of steps, pre-job, job // Build up 2 lists of steps, pre-job, job
// Download actions not already in the cache // Download actions not already in the cache
Trace.Info("Downloading actions"); Trace.Info("Downloading actions");
@@ -313,58 +242,6 @@ namespace GitHub.Runner.Worker
context.Start(); context.Start();
context.Debug("Starting: Complete job"); context.Debug("Starting: Complete job");
// Evaluate job outputs
if (message.JobOutputs != null && message.JobOutputs.Type != TokenType.Null)
{
try
{
context.Output($"Evaluate and set job outputs");
// Populate env context for each step
Trace.Info("Initialize Env context for evaluating job outputs");
#if OS_WINDOWS
var envContext = new DictionaryContextData();
#else
var envContext = new CaseSensitiveDictionaryContextData();
#endif
context.ExpressionValues["env"] = envContext;
foreach (var pair in context.EnvironmentVariables)
{
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
}
Trace.Info("Initialize steps context for evaluating job outputs");
context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName);
var templateEvaluator = context.ToPipelineTemplateEvaluator();
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues);
foreach (var output in outputs)
{
if (string.IsNullOrEmpty(output.Value))
{
context.Debug($"Skip output '{output.Key}' since it's empty");
continue;
}
if (!string.Equals(output.Value, HostContext.SecretMasker.MaskSecrets(output.Value)))
{
context.Warning($"Skip output '{output.Key}' since it may contain secret.");
continue;
}
context.Output($"Set output '{output.Key}'");
jobContext.JobOutputs[output.Key] = output.Value;
}
}
catch (Exception ex)
{
context.Result = TaskResult.Failed;
context.Error($"Fail to evaluate job outputs");
context.Error(ex);
jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, TaskResult.Failed);
}
}
if (context.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false) if (context.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false)
{ {
Trace.Info("Support log upload starting."); Trace.Info("Support log upload starting.");

View File

@@ -231,7 +231,7 @@ namespace GitHub.Runner.Worker
} }
Trace.Info("Raising job completed event."); Trace.Info("Raising job completed event.");
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs); var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result);
var completeJobRetryLimit = 5; var completeJobRetryLimit = 5;
var exceptions = new List<Exception>(); var exceptions = new List<Exception>();

View File

@@ -56,22 +56,13 @@ namespace GitHub.Runner.Worker
} }
} }
public void SetConclusion( public void SetResult(
string scopeName, string scopeName,
string stepName, string stepName,
string conclusion) string result)
{ {
var step = GetStep(scopeName, stepName); var step = GetStep(scopeName, stepName);
step["conclusion"] = new StringContextData(conclusion); step["result"] = new StringContextData(result);
}
public void SetOutcome(
string scopeName,
string stepName,
string outcome)
{
var step = GetStep(scopeName, stepName);
step["outcome"] = new StringContextData(outcome);
} }
private DictionaryContextData GetStep(string scopeName, string stepName) private DictionaryContextData GetStep(string scopeName, string stepName)

View File

@@ -351,7 +351,6 @@ namespace GitHub.Runner.Worker
if (continueOnError) if (continueOnError)
{ {
step.ExecutionContext.Outcome = step.ExecutionContext.Result;
step.ExecutionContext.Result = TaskResult.Succeeded; step.ExecutionContext.Result = TaskResult.Succeeded;
Trace.Info($"Updated step result (continue on error)"); Trace.Info($"Updated step result (continue on error)");
} }

View File

@@ -90,8 +90,10 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"steps",
"job", "job",
"runner" "runner",
"env"
], ],
"string": {} "string": {}
}, },

View File

@@ -15,7 +15,6 @@ namespace GitHub.DistributedTask.Expressions2
AddFunction<Join>("join", 1, 2); AddFunction<Join>("join", 1, 2);
AddFunction<StartsWith>("startsWith", 2, 2); AddFunction<StartsWith>("startsWith", 2, 2);
AddFunction<ToJson>("toJson", 1, 1); AddFunction<ToJson>("toJson", 1, 1);
AddFunction<FromJson>("fromJson", 1, 1);
AddFunction<HashFiles>("hashFiles", 1, 1); AddFunction<HashFiles>("hashFiles", 1, 1);
} }

View File

@@ -1,24 +0,0 @@
using System;
using System.IO;
using GitHub.DistributedTask.Pipelines.ContextData;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
{
internal sealed class FromJson : Function
{
protected sealed override Object EvaluateCore(
EvaluationContext context,
out ResultMemory resultMemory)
{
resultMemory = null;
var json = Parameters[0].Evaluate(context).ConvertToString();
using (var stringReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(stringReader) { DateParseHandling = DateParseHandling.None, FloatParseHandling = FloatParseHandling.Double })
{
var token = JToken.ReadFrom(jsonReader);
return token.ToPipelineContextData();
}
}
}}

View File

@@ -779,65 +779,5 @@ namespace GitHub.DistributedTask.WebApi
userState: userState, userState: userState,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
/// <summary>
/// [Preview API]
/// </summary>
/// <param name="poolId"></param>
/// <param name="agentId"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public Task<String> GetAgentAuthUrlAsync(
int poolId,
int agentId,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("GET");
Guid locationId = new Guid("a82a119c-1e46-44b6-8d75-c82a79cf975b");
object routeValues = new { poolId = poolId, agentId = agentId };
return SendAsync<String>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken);
}
/// <summary>
/// [Preview API]
/// </summary>
/// <param name="poolId"></param>
/// <param name="agentId"></param>
/// <param name="error"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual async Task ReportAgentAuthUrlMigrationErrorAsync(
int poolId,
int agentId,
string error,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("a82a119c-1e46-44b6-8d75-c82a79cf975b");
object routeValues = new { poolId = poolId, agentId = agentId };
HttpContent content = new ObjectContent<string>(error, new VssJsonMediaTypeFormatter(true));
using (HttpResponseMessage response = await SendAsync(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken,
content: content).ConfigureAwait(false))
{
return;
}
}
} }
} }

View File

@@ -30,7 +30,8 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
foreach (var propertiesPair in properties) foreach (var propertiesPair in properties)
{ {
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key"); var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
Properties.Add(propertyName.Value, new PropertyValue(propertiesPair.Value)); var propertyValue = propertiesPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} value");
Properties.Add(propertyName.Value, new PropertyValue(propertyValue.Value));
} }
break; break;
@@ -84,7 +85,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
} }
else else
{ {
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined on '{name}'"); throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined");
} }
} }
// Otherwise validate loose value type not be defined // Otherwise validate loose value type not be defined
@@ -94,14 +95,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
} }
// Lookup each property // Lookup each property
foreach (var property in Properties) foreach (var property in Properties.Values)
{ {
if (String.IsNullOrEmpty(property.Value.Type)) schema.GetDefinition(property.Type);
{
throw new ArgumentException($"Type not specified for the '{property.Key}' property on the '{name}' type");
}
schema.GetDefinition(property.Value.Type);
} }
if (!String.IsNullOrEmpty(Inherits)) if (!String.IsNullOrEmpty(Inherits))

View File

@@ -1,40 +1,18 @@
using System; using System;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
namespace GitHub.DistributedTask.ObjectTemplating.Schema namespace GitHub.DistributedTask.ObjectTemplating.Schema
{ {
internal sealed class PropertyValue internal sealed class PropertyValue
{ {
internal PropertyValue(TemplateToken token) internal PropertyValue()
{ {
if (token is StringToken stringToken) }
{
Type = stringToken.Value; internal PropertyValue(String type)
} {
else Type = type;
{
var mapping = token.AssertMapping($"{TemplateConstants.MappingPropertyValue}");
foreach (var mappingPair in mapping)
{
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.MappingPropertyValue} key");
switch (mappingKey.Value)
{
case TemplateConstants.Type:
Type = mappingPair.Value.AssertString($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Type}").Value;
break;
case TemplateConstants.Required:
Required = mappingPair.Value.AssertBoolean($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Required}").Value;
break;
default:
mappingKey.AssertUnexpectedValue($"{TemplateConstants.MappingPropertyValue} key");
break;
}
}
}
} }
internal String Type { get; set; } internal String Type { get; set; }
internal Boolean Required { get; set; }
} }
} }

View File

@@ -312,8 +312,8 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
// template-schema // template-schema
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(TemplateConstants.NonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Definitions))); mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(TemplateConstants.Definitions));
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition); schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
// definitions // definitions
@@ -335,9 +335,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
// null-definition // null-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NullDefinitionProperties))); mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(TemplateConstants.NullDefinitionProperties));
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
// null-definition-properties // null-definition-properties
@@ -346,9 +346,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
// boolean-definition // boolean-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(new StringToken(null, null, null, TemplateConstants.BooleanDefinitionProperties))); mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(TemplateConstants.BooleanDefinitionProperties));
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
// boolean-definition-properties // boolean-definition-properties
@@ -357,9 +357,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
// number-definition // number-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NumberDefinitionProperties))); mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(TemplateConstants.NumberDefinitionProperties));
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
// number-definition-properties // number-definition-properties
@@ -368,68 +368,55 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
// string-definition // string-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(new StringToken(null, null, null, TemplateConstants.StringDefinitionProperties))); mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(TemplateConstants.StringDefinitionProperties));
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
// string-definition-properties // string-definition-properties
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(TemplateConstants.NonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(new StringToken(null, null, null,TemplateConstants.Boolean))); mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(TemplateConstants.Boolean));
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean))); mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(TemplateConstants.Boolean));
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition); schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
// sequence-definition // sequence-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceDefinitionProperties))); mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(TemplateConstants.SequenceDefinitionProperties));
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
// sequence-definition-properties // sequence-definition-properties
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(TemplateConstants.NonEmptyString));
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition); schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
// mapping-definition // mapping-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(new StringToken(null, null, null, TemplateConstants.MappingDefinitionProperties))); mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(TemplateConstants.MappingDefinitionProperties));
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
// mapping-definition-properties // mapping-definition-properties
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Properties))); mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(TemplateConstants.Properties));
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(TemplateConstants.NonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(TemplateConstants.NonEmptyString));
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition); schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
// properties // properties
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString; mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
mappingDefinition.LooseValueType = TemplateConstants.PropertyValue; mappingDefinition.LooseValueType = TemplateConstants.NonEmptyString;
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition); schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
// property-value
oneOfDefinition = new OneOfDefinition();
oneOfDefinition.OneOf.Add(TemplateConstants.NonEmptyString);
oneOfDefinition.OneOf.Add(TemplateConstants.MappingPropertyValue);
schema.Definitions.Add(TemplateConstants.PropertyValue, oneOfDefinition);
// mapping-property-value
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Type, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Required, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
schema.Definitions.Add(TemplateConstants.MappingPropertyValue, mappingDefinition);
// one-of-definition // one-of-definition
mappingDefinition = new MappingDefinition(); mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String))); mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString))); mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition); schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
// non-empty-string // non-empty-string
@@ -490,4 +477,4 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled); private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled);
private static TemplateSchema s_schema; private static TemplateSchema s_schema;
} }
} }

View File

@@ -22,11 +22,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
internal const String ItemType = "item-type"; internal const String ItemType = "item-type";
internal const String LooseKeyType = "loose-key-type"; internal const String LooseKeyType = "loose-key-type";
internal const String LooseValueType = "loose-value-type"; internal const String LooseValueType = "loose-value-type";
internal const String MaxConstant = "MAX";
internal const String Mapping = "mapping"; internal const String Mapping = "mapping";
internal const String MappingDefinition = "mapping-definition"; internal const String MappingDefinition = "mapping-definition";
internal const String MappingDefinitionProperties = "mapping-definition-properties"; internal const String MappingDefinitionProperties = "mapping-definition-properties";
internal const String MappingPropertyValue = "mapping-property-value";
internal const String NonEmptyString = "non-empty-string"; internal const String NonEmptyString = "non-empty-string";
internal const String Null = "null"; internal const String Null = "null";
internal const String NullDefinition = "null-definition"; internal const String NullDefinition = "null-definition";
@@ -37,9 +35,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
internal const String OneOf = "one-of"; internal const String OneOf = "one-of";
internal const String OneOfDefinition = "one-of-definition"; internal const String OneOfDefinition = "one-of-definition";
internal const String OpenExpression = "${{"; internal const String OpenExpression = "${{";
internal const String PropertyValue = "property-value";
internal const String Properties = "properties"; internal const String Properties = "properties";
internal const String Required = "required";
internal const String RequireNonEmpty = "require-non-empty"; internal const String RequireNonEmpty = "require-non-empty";
internal const String Scalar = "scalar"; internal const String Scalar = "scalar";
internal const String ScalarDefinition = "scalar-definition"; internal const String ScalarDefinition = "scalar-definition";
@@ -47,7 +43,6 @@ namespace GitHub.DistributedTask.ObjectTemplating
internal const String Sequence = "sequence"; internal const String Sequence = "sequence";
internal const String SequenceDefinition = "sequence-definition"; internal const String SequenceDefinition = "sequence-definition";
internal const String SequenceDefinitionProperties = "sequence-definition-properties"; internal const String SequenceDefinitionProperties = "sequence-definition-properties";
internal const String Type = "type";
internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string"; internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string";
internal const String String = "string"; internal const String String = "string";
internal const String StringDefinition = "string-definition"; internal const String StringDefinition = "string-definition";

View File

@@ -47,7 +47,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
var evaluator = new TemplateEvaluator(context, template, removeBytes); var evaluator = new TemplateEvaluator(context, template, removeBytes);
try try
{ {
var availableContext = new HashSet<String>(context.ExpressionValues.Keys.Concat(context.ExpressionFunctions.Select(x => $"{x.Name}({x.MinParameters},{x.MaxParameters})"))); var availableContext = new HashSet<String>(context.ExpressionValues.Keys);
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext); var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
result = evaluator.Evaluate(definitionInfo); result = evaluator.Evaluate(definitionInfo);
@@ -182,14 +182,12 @@ namespace GitHub.DistributedTask.ObjectTemplating
} }
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase); var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
var hasExpressionKey = false;
while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar)) while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
{ {
// Expression // Expression
if (nextKeyScalar is ExpressionToken) if (nextKeyScalar is ExpressionToken)
{ {
hasExpressionKey = true;
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any); var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
mapping.Add(nextKeyScalar, Evaluate(anyDefinition)); mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
continue; continue;
@@ -270,19 +268,6 @@ namespace GitHub.DistributedTask.ObjectTemplating
String listToDeDuplicate = String.Join(", ", nonDuplicates); String listToDeDuplicate = String.Join(", ", nonDuplicates);
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate)); m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
} }
else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
{
foreach (var property in mappingDefinitions[0].Properties)
{
if (property.Value.Required)
{
if (!keys.Contains(property.Key))
{
m_context.Error(mapping, $"Required property is missing: {property.Key}");
}
}
}
}
m_unraveler.ReadMappingEnd(); m_unraveler.ReadMappingEnd();
} }

View File

@@ -178,15 +178,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
} }
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase); var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
var hasExpressionKey = false;
while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral)) while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral))
{ {
var nextKeyScalar = ParseScalar(rawLiteral, definition.AllowedContext); var nextKeyScalar = ParseScalar(rawLiteral, definition.AllowedContext);
// Expression // Expression
if (nextKeyScalar is ExpressionToken) if (nextKeyScalar is ExpressionToken)
{ {
hasExpressionKey = true;
// Legal // Legal
if (definition.AllowedContext.Length > 0) if (definition.AllowedContext.Length > 0)
{ {
@@ -281,19 +280,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
String listToDeDuplicate = String.Join(", ", nonDuplicates); String listToDeDuplicate = String.Join(", ", nonDuplicates);
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate)); m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
} }
else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
{
foreach (var property in mappingDefinitions[0].Properties)
{
if (property.Value.Required)
{
if (!keys.Contains(property.Key))
{
m_context.Error(mapping, $"Required property is missing: {property.Key}");
}
}
}
}
ExpectMappingEnd(); ExpectMappingEnd();
} }

View File

@@ -40,9 +40,7 @@ namespace GitHub.DistributedTask.Pipelines
WorkspaceOptions workspaceOptions, WorkspaceOptions workspaceOptions,
IEnumerable<JobStep> steps, IEnumerable<JobStep> steps,
IEnumerable<ContextScope> scopes, IEnumerable<ContextScope> scopes,
IList<String> fileTable, IList<String> fileTable)
TemplateToken jobOutputs,
IList<TemplateToken> defaults)
{ {
this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest; this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest;
this.Plan = plan; this.Plan = plan;
@@ -54,7 +52,6 @@ namespace GitHub.DistributedTask.Pipelines
this.Timeline = timeline; this.Timeline = timeline;
this.Resources = jobResources; this.Resources = jobResources;
this.Workspace = workspaceOptions; this.Workspace = workspaceOptions;
this.JobOutputs = jobOutputs;
m_variables = new Dictionary<String, VariableValue>(variables, StringComparer.OrdinalIgnoreCase); m_variables = new Dictionary<String, VariableValue>(variables, StringComparer.OrdinalIgnoreCase);
m_maskHints = new List<MaskHint>(maskHints); m_maskHints = new List<MaskHint>(maskHints);
@@ -70,11 +67,6 @@ namespace GitHub.DistributedTask.Pipelines
m_environmentVariables = new List<TemplateToken>(environmentVariables); m_environmentVariables = new List<TemplateToken>(environmentVariables);
} }
if (defaults?.Count > 0)
{
m_defaults = new List<TemplateToken>(defaults);
}
this.ContextData = new Dictionary<String, PipelineContextData>(StringComparer.OrdinalIgnoreCase); this.ContextData = new Dictionary<String, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
if (contextData?.Count > 0) if (contextData?.Count > 0)
{ {
@@ -146,13 +138,6 @@ namespace GitHub.DistributedTask.Pipelines
private set; private set;
} }
[DataMember(EmitDefaultValue = false)]
public TemplateToken JobOutputs
{
get;
private set;
}
[DataMember] [DataMember]
public Int64 RequestId public Int64 RequestId
{ {
@@ -219,21 +204,6 @@ namespace GitHub.DistributedTask.Pipelines
} }
} }
/// <summary>
/// Gets the hierarchy of defaults to overlay, last wins.
/// </summary>
public IList<TemplateToken> Defaults
{
get
{
if (m_defaults == null)
{
m_defaults = new List<TemplateToken>();
}
return m_defaults;
}
}
/// <summary> /// <summary>
/// Gets the collection of variables associated with the current context. /// Gets the collection of variables associated with the current context.
/// </summary> /// </summary>
@@ -273,9 +243,6 @@ namespace GitHub.DistributedTask.Pipelines
} }
} }
/// <summary>
/// Gets the table of files used when parsing the pipeline (e.g. yaml files)
/// </summary>
public IList<String> FileTable public IList<String> FileTable
{ {
get get
@@ -396,11 +363,6 @@ namespace GitHub.DistributedTask.Pipelines
m_environmentVariables = null; m_environmentVariables = null;
} }
if (m_defaults?.Count == 0)
{
m_defaults = null;
}
if (m_fileTable?.Count == 0) if (m_fileTable?.Count == 0)
{ {
m_fileTable = null; m_fileTable = null;
@@ -435,9 +397,6 @@ namespace GitHub.DistributedTask.Pipelines
[DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)] [DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)]
private List<TemplateToken> m_environmentVariables; private List<TemplateToken> m_environmentVariables;
[DataMember(Name = "Defaults", EmitDefaultValue = false)]
private List<TemplateToken> m_defaults;
[DataMember(Name = "FileTable", EmitDefaultValue = false)] [DataMember(Name = "FileTable", EmitDefaultValue = false)]
private List<String> m_fileTable; private List<String> m_fileTable;

View File

@@ -14,7 +14,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public const String Clean = "clean"; public const String Clean = "clean";
public const String Container = "container"; public const String Container = "container";
public const String ContinueOnError = "continue-on-error"; public const String ContinueOnError = "continue-on-error";
public const String Defaults = "defaults";
public const String Env = "env"; public const String Env = "env";
public const String Event = "event"; public const String Event = "event";
public const String EventPattern = "github.event"; public const String EventPattern = "github.event";
@@ -30,10 +29,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
public const String Include = "include"; public const String Include = "include";
public const String Inputs = "inputs"; public const String Inputs = "inputs";
public const String Job = "job"; public const String Job = "job";
public const String JobDefaultsRun = "job-defaults-run";
public const String JobOutputs = "job-outputs";
public const String Jobs = "jobs"; public const String Jobs = "jobs";
public const String Labels = "labels";
public const String Lfs = "lfs"; public const String Lfs = "lfs";
public const String Matrix = "matrix"; public const String Matrix = "matrix";
public const String MaxParallel = "max-parallel"; public const String MaxParallel = "max-parallel";

View File

@@ -231,78 +231,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
return result; return result;
} }
public Dictionary<String, String> EvaluateJobOutput(
TemplateToken token,
DictionaryContextData contextData)
{
var result = default(Dictionary<String, String>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobOutputs, token, 0, null, omitHeader: true);
context.Errors.Check();
result = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
var mapping = token.AssertMapping("outputs");
foreach (var pair in mapping)
{
// Literal key
var key = pair.Key.AssertString("output key");
// Literal value
var value = pair.Value.AssertString("output value");
result[key.Value] = value.Value;
}
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public Dictionary<String, String> EvaluateJobDefaultsRun(
TemplateToken token,
DictionaryContextData contextData)
{
var result = default(Dictionary<String, String>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(contextData);
try
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true);
context.Errors.Check();
result = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
var mapping = token.AssertMapping("defaults run");
foreach (var pair in mapping)
{
// Literal key
var key = pair.Key.AssertString("defaults run key");
// Literal value
var value = pair.Value.AssertString("defaults run value");
result[key.Value] = value.Value;
}
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers( public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
TemplateToken token, TemplateToken token,
DictionaryContextData contextData) DictionaryContextData contextData)
@@ -436,7 +364,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
PipelineTemplateConstants.GitHub, PipelineTemplateConstants.GitHub,
PipelineTemplateConstants.Strategy, PipelineTemplateConstants.Strategy,
PipelineTemplateConstants.Matrix, PipelineTemplateConstants.Matrix,
PipelineTemplateConstants.Needs,
PipelineTemplateConstants.Secrets, PipelineTemplateConstants.Secrets,
PipelineTemplateConstants.Steps, PipelineTemplateConstants.Steps,
PipelineTemplateConstants.Inputs, PipelineTemplateConstants.Inputs,

View File

@@ -9,7 +9,6 @@
"properties": { "properties": {
"on": "any", "on": "any",
"name": "string", "name": "string",
"defaults": "workflow-defaults",
"env": "workflow-env", "env": "workflow-env",
"jobs": "jobs" "jobs": "jobs"
} }
@@ -39,7 +38,6 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"needs",
"matrix", "matrix",
"secrets", "secrets",
"steps", "steps",
@@ -68,7 +66,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"inputs", "inputs",
@@ -92,8 +89,7 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"one-of": [ "one-of": [
"string", "string",
@@ -116,7 +112,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -126,23 +121,6 @@
"string": {} "string": {}
}, },
"workflow-defaults": {
"mapping": {
"properties": {
"run": "workflow-defaults-run"
}
}
},
"workflow-defaults-run": {
"mapping": {
"properties": {
"shell": "non-empty-string",
"working-directory": "non-empty-string"
}
}
},
"workflow-env": { "workflow-env": {
"context": [ "context": [
"github", "github",
@@ -165,21 +143,16 @@
"mapping": { "mapping": {
"properties": { "properties": {
"needs": "needs", "needs": "needs",
"if": "job-if", "if": "string",
"strategy": "strategy", "strategy": "strategy",
"name": "string-strategy-context", "name": "string-strategy-context",
"runs-on": { "runs-on": "runs-on",
"type": "runs-on",
"required": true
},
"timeout-minutes": "number-strategy-context", "timeout-minutes": "number-strategy-context",
"cancel-timeout-minutes": "number-strategy-context", "cancel-timeout-minutes": "number-strategy-context",
"continue-on-error": "boolean-strategy-context", "continue-on-error": "boolean",
"container": "container", "container": "container",
"services": "services", "services": "services",
"env": "job-env", "env": "job-env",
"outputs": "job-outputs",
"defaults": "job-defaults",
"steps": "steps" "steps": "steps"
} }
} }
@@ -192,22 +165,9 @@
] ]
}, },
"job-if": {
"context": [
"github",
"needs",
"always(0,0)",
"failure(0,MAX)",
"cancelled(0,0)",
"success(0,MAX)"
],
"string": {}
},
"strategy": { "strategy": {
"context": [ "context": [
"github", "github"
"needs"
], ],
"mapping": { "mapping": {
"properties": { "properties": {
@@ -273,23 +233,24 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"one-of": [ "one-of": [
"non-empty-string", "runs-on-string",
"sequence-of-non-empty-string",
"runs-on-mapping" "runs-on-mapping"
] ]
}, },
"runs-on-string": {
"string": {
"require-non-empty": true
}
},
"runs-on-mapping": { "runs-on-mapping": {
"mapping": { "mapping": {
"properties": { "properties": {
"pool": { "pool": "non-empty-string"
"type": "non-empty-string",
"required": true
}
} }
} }
}, },
@@ -299,8 +260,7 @@
"github", "github",
"secrets", "secrets",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"mapping": { "mapping": {
"loose-key-type": "non-empty-string", "loose-key-type": "non-empty-string",
@@ -308,37 +268,6 @@
} }
}, },
"job-defaults": {
"mapping": {
"properties": {
"run": "job-defaults-run"
}
}
},
"job-defaults-run": {
"context": [
"github",
"strategy",
"matrix",
"needs",
"env"
],
"mapping": {
"properties": {
"shell": "non-empty-string",
"working-directory": "non-empty-string"
}
}
},
"job-outputs": {
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "string-runner-context"
}
},
"steps": { "steps": {
"sequence": { "sequence": {
"item-type": "steps-item" "item-type": "steps-item"
@@ -372,12 +301,9 @@
"properties": { "properties": {
"name": "string-steps-context", "name": "string-steps-context",
"id": "non-empty-string", "id": "non-empty-string",
"if": "step-if", "if": "string",
"timeout-minutes": "number-steps-context", "timeout-minutes": "number-steps-context",
"run": { "run": "string-steps-context",
"type": "string-steps-context",
"required": true
},
"continue-on-error": "boolean-steps-context", "continue-on-error": "boolean-steps-context",
"env": "step-env", "env": "step-env",
"working-directory": "string-steps-context", "working-directory": "string-steps-context",
@@ -391,12 +317,9 @@
"properties": { "properties": {
"name": "string-steps-context-in-template", "name": "string-steps-context-in-template",
"id": "non-empty-string", "id": "non-empty-string",
"if": "step-if-in-template", "if": "string",
"timeout-minutes": "number-steps-context-in-template", "timeout-minutes": "number-steps-context-in-template",
"run": { "run": "string-steps-context-in-template",
"type": "string-steps-context-in-template",
"required": true
},
"continue-on-error": "boolean-steps-context-in-template", "continue-on-error": "boolean-steps-context-in-template",
"env": "step-env-in-template", "env": "step-env-in-template",
"working-directory": "string-steps-context-in-template", "working-directory": "string-steps-context-in-template",
@@ -410,13 +333,10 @@
"properties": { "properties": {
"name": "string-steps-context", "name": "string-steps-context",
"id": "non-empty-string", "id": "non-empty-string",
"if": "step-if", "if": "string",
"continue-on-error": "boolean-steps-context", "continue-on-error": "boolean-steps-context",
"timeout-minutes": "number-steps-context", "timeout-minutes": "number-steps-context",
"uses": { "uses": "non-empty-string",
"type": "non-empty-string",
"required": true
},
"with": "step-with", "with": "step-with",
"env": "step-env" "env": "step-env"
} }
@@ -428,56 +348,16 @@
"properties": { "properties": {
"name": "string-steps-context-in-template", "name": "string-steps-context-in-template",
"id": "non-empty-string", "id": "non-empty-string",
"if": "step-if-in-template", "if": "string",
"continue-on-error": "boolean-steps-context-in-template", "continue-on-error": "boolean-steps-context-in-template",
"timeout-minutes": "number-steps-context-in-template", "timeout-minutes": "number-steps-context-in-template",
"uses": { "uses": "non-empty-string",
"type": "non-empty-string",
"required": true
},
"with": "step-with-in-template", "with": "step-with-in-template",
"env": "step-env-in-template" "env": "step-env-in-template"
} }
} }
}, },
"step-if": {
"context": [
"github",
"strategy",
"matrix",
"needs",
"steps",
"job",
"runner",
"env",
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)"
],
"string": {}
},
"step-if-in-template": {
"context": [
"github",
"strategy",
"matrix",
"needs",
"steps",
"inputs",
"job",
"runner",
"env",
"always(0,0)",
"failure(0,0)",
"cancelled(0,0)",
"success(0,0)"
],
"string": {}
},
"steps-template-reference": { "steps-template-reference": {
"mapping": { "mapping": {
"properties": { "properties": {
@@ -503,7 +383,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -521,7 +400,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"inputs", "inputs",
@@ -540,7 +418,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -558,7 +435,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"inputs", "inputs",
@@ -577,7 +453,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -594,8 +469,7 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"one-of": [ "one-of": [
"string", "string",
@@ -619,8 +493,7 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"mapping": { "mapping": {
"loose-key-type": "non-empty-string", "loose-key-type": "non-empty-string",
@@ -632,8 +505,7 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"one-of": [ "one-of": [
"non-empty-string", "non-empty-string",
@@ -649,24 +521,23 @@
}, },
"step-with-in-template": { "step-with-in-template": {
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs", "secrets",
"secrets", "steps",
"steps", "inputs",
"inputs", "job",
"job", "runner",
"runner", "env"
"env" ],
], "mapping": {
"mapping": { "loose-key-type": "non-empty-string",
"loose-key-type": "non-empty-string", "loose-value-type": "string"
"loose-value-type": "string" }
} },
},
"non-empty-string": { "non-empty-string": {
"string": { "string": {
"require-non-empty": true "require-non-empty": true
@@ -679,22 +550,11 @@
} }
}, },
"boolean-strategy-context": {
"context": [
"github",
"strategy",
"matrix",
"needs"
],
"boolean": {}
},
"number-strategy-context": { "number-strategy-context": {
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"number": {} "number": {}
}, },
@@ -703,8 +563,7 @@
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix"
"needs"
], ],
"string": {} "string": {}
}, },
@@ -714,7 +573,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -729,7 +587,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"inputs", "inputs",
@@ -745,7 +602,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -760,7 +616,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"inputs", "inputs",
@@ -771,27 +626,11 @@
"number": {} "number": {}
}, },
"string-runner-context": {
"context": [
"github",
"strategy",
"matrix",
"needs",
"secrets",
"steps",
"job",
"runner",
"env"
],
"string": {}
},
"string-steps-context": { "string-steps-context": {
"context": [ "context": [
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"job", "job",
@@ -806,7 +645,6 @@
"github", "github",
"strategy", "strategy",
"matrix", "matrix",
"needs",
"secrets", "secrets",
"steps", "steps",
"inputs", "inputs",

View File

@@ -31,7 +31,7 @@ namespace GitHub.DistributedTask.WebApi
} }
protected JobEvent( protected JobEvent(
String name, String name,
Guid jobId) Guid jobId)
{ {
this.Name = name; this.Name = name;
@@ -123,12 +123,11 @@ namespace GitHub.DistributedTask.WebApi
Int64 requestId, Int64 requestId,
Guid jobId, Guid jobId,
TaskResult result, TaskResult result,
Dictionary<String, VariableValue> outputs) IDictionary<String, VariableValue> outputVariables)
: base(JobEventTypes.JobCompleted, jobId) : base(JobEventTypes.JobCompleted, jobId)
{ {
this.RequestId = requestId; this.RequestId = requestId;
this.Result = result; this.Result = result;
this.Outputs = outputs;
} }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
@@ -144,13 +143,6 @@ namespace GitHub.DistributedTask.WebApi
get; get;
set; set;
} }
[DataMember(EmitDefaultValue = false)]
public IDictionary<String, VariableValue> Outputs
{
get;
set;
}
} }
[DataContract] [DataContract]
@@ -161,9 +153,9 @@ namespace GitHub.DistributedTask.WebApi
} }
protected TaskEvent( protected TaskEvent(
string name, string name,
Guid jobId, Guid jobId,
Guid taskId) Guid taskId)
: base(name, jobId) : base(name, jobId)
{ {
TaskId = taskId; TaskId = taskId;
@@ -193,9 +185,9 @@ namespace GitHub.DistributedTask.WebApi
} }
public override Object ReadJson( public override Object ReadJson(
JsonReader reader, JsonReader reader,
Type objectType, Type objectType,
Object existingValue, Object existingValue,
JsonSerializer serializer) JsonSerializer serializer)
{ {
var eventObject = JObject.Load(reader); var eventObject = JObject.Load(reader);

View File

@@ -260,8 +260,5 @@ namespace GitHub.DistributedTask.WebApi
public static readonly Guid CheckpointResourcesLocationId = new Guid(CheckpointResourcesLocationIdString); public static readonly Guid CheckpointResourcesLocationId = new Guid(CheckpointResourcesLocationIdString);
public const String CheckpointResourcesResource = "references"; public const String CheckpointResourcesResource = "references";
public static readonly Guid RunnerAuthUrl = new Guid("{A82A119C-1E46-44B6-8D75-C82A79CF975B}");
public const string RunnerAuthUrlResource = "authurl";
} }
} }

View File

@@ -33,7 +33,7 @@ namespace GitHub.Runner.Common.Tests.Listener
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = null; TimelineReference timeline = null;
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
return result; return result;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ namespace GitHub.Runner.Common.Tests.Listener
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = null; TimelineReference timeline = null;
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
} }
private JobCancelMessage CreateJobCancelMessage() private JobCancelMessage CreateJobCancelMessage()

View File

@@ -8,7 +8,6 @@ using Xunit;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using System.Threading.Channels; using System.Threading.Channels;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using System.Linq;
namespace GitHub.Runner.Common.Tests namespace GitHub.Runner.Common.Tests
{ {
@@ -82,102 +81,6 @@ namespace GitHub.Runner.Common.Tests
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task SetCIEnv()
{
using (TestHostContext hc = new TestHostContext(this))
{
var existingCI = Environment.GetEnvironmentVariable("CI");
try
{
// Clear out CI and make sure process invoker sets it.
Environment.SetEnvironmentVariable("CI", null);
Tracing trace = hc.GetTrace();
Int32 exitCode = -1;
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
var stdout = new List<string>();
var stderr = new List<string>();
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stdout.Add(e.Data);
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stderr.Add(e.Data);
};
#if OS_WINDOWS
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %CI%\"", null, CancellationToken.None);
#else
exitCode = await processInvoker.ExecuteAsync("", "bash", "-c \"echo $CI\"", null, CancellationToken.None);
#endif
trace.Info("Exit Code: {0}", exitCode);
Assert.Equal(0, exitCode);
Assert.Equal("true", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
}
finally
{
Environment.SetEnvironmentVariable("CI", existingCI);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task KeepExistingCIEnv()
{
using (TestHostContext hc = new TestHostContext(this))
{
var existingCI = Environment.GetEnvironmentVariable("CI");
try
{
// Clear out CI and make sure process invoker sets it.
Environment.SetEnvironmentVariable("CI", null);
Tracing trace = hc.GetTrace();
Int32 exitCode = -1;
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
var stdout = new List<string>();
var stderr = new List<string>();
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stdout.Add(e.Data);
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stderr.Add(e.Data);
};
#if OS_WINDOWS
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %CI%\"", new Dictionary<string, string>() { { "CI", "false" } }, CancellationToken.None);
#else
exitCode = await processInvoker.ExecuteAsync("", "bash", "-c \"echo $CI\"", new Dictionary<string, string>() { { "CI", "false" } }, CancellationToken.None);
#endif
trace.Info("Exit Code: {0}", exitCode);
Assert.Equal(0, exitCode);
Assert.Equal("false", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
}
finally
{
Environment.SetEnvironmentVariable("CI", existingCI);
}
}
}
#if !OS_WINDOWS #if !OS_WINDOWS
//Run a process that normally takes 20sec to finish and cancel it. //Run a process that normally takes 20sec to finish and cancel it.
[Fact] [Fact]

View File

@@ -198,7 +198,7 @@ namespace GitHub.Runner.Common.Tests
case WellKnownDirectory.Tools: case WellKnownDirectory.Tools:
path = Environment.GetEnvironmentVariable("RUNNER_TOOL_CACHE"); path = Environment.GetEnvironmentVariable("RUNNER_TOOL_CACHE");
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
path = Path.Combine( path = Path.Combine(
@@ -279,13 +279,6 @@ namespace GitHub.Runner.Common.Tests
GetDirectory(WellKnownDirectory.Root), GetDirectory(WellKnownDirectory.Root),
".options"); ".options");
break; break;
case WellKnownConfigFile.SetupInfo:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".setup_info");
break;
default: default:
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'"); throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
} }

View File

@@ -150,7 +150,7 @@ namespace GitHub.Runner.Common.Tests.Worker
TimelineReference timeline = new TimelineReference(); TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
string jobName = "some job name"; string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{ {
Alias = Pipelines.PipelineConstants.SelfAlias, Alias = Pipelines.PipelineConstants.SelfAlias,

View File

@@ -25,7 +25,7 @@ namespace GitHub.Runner.Common.Tests.Worker
TimelineReference timeline = new TimelineReference(); TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
string jobName = "some job name"; string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{ {
Alias = Pipelines.PipelineConstants.SelfAlias, Alias = Pipelines.PipelineConstants.SelfAlias,
@@ -101,7 +101,7 @@ namespace GitHub.Runner.Common.Tests.Worker
TimelineReference timeline = new TimelineReference(); TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
string jobName = "some job name"; string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{ {
Alias = Pipelines.PipelineConstants.SelfAlias, Alias = Pipelines.PipelineConstants.SelfAlias,
@@ -152,7 +152,7 @@ namespace GitHub.Runner.Common.Tests.Worker
TimelineReference timeline = new TimelineReference(); TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
string jobName = "some job name"; string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{ {
Alias = Pipelines.PipelineConstants.SelfAlias, Alias = Pipelines.PipelineConstants.SelfAlias,

View File

@@ -100,7 +100,7 @@ namespace GitHub.Runner.Common.Tests.Worker
}; };
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
_message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), steps, null, null, null, null); _message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), steps, null, null);
GitHubContext github = new GitHubContext(); GitHubContext github = new GitHubContext();
github["repository"] = new Pipelines.ContextData.StringContextData("actions/runner"); github["repository"] = new Pipelines.ContextData.StringContextData("actions/runner");
_message.ContextData.Add("github", github); _message.ContextData.Add("github", github);

View File

@@ -63,7 +63,7 @@ namespace GitHub.Runner.Common.Tests.Worker
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new Timeline(Guid.NewGuid()); TimelineReference timeline = new Timeline(Guid.NewGuid());
Guid jobId = Guid.NewGuid(); Guid jobId = Guid.NewGuid();
_message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, testName, testName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null); _message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, testName, testName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null);
_message.Variables[Constants.Variables.System.Culture] = "en-US"; _message.Variables[Constants.Variables.System.Culture] = "en-US";
_message.Resources.Endpoints.Add(new ServiceEndpoint() _message.Resources.Endpoints.Add(new ServiceEndpoint()
{ {

View File

@@ -11,7 +11,6 @@ using Xunit;
using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.Runner.Common.Util;
namespace GitHub.Runner.Common.Tests.Worker namespace GitHub.Runner.Common.Tests.Worker
{ {
@@ -56,9 +55,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_ec.Setup(x => x.PostJobSteps).Returns(new Stack<IStep>()); _ec.Setup(x => x.PostJobSteps).Returns(new Stack<IStep>());
var trace = hc.GetTrace();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
_stepsRunner = new StepsRunner(); _stepsRunner = new StepsRunner();
_stepsRunner.Initialize(hc); _stepsRunner.Initialize(hc);
return hc; return hc;
@@ -517,78 +513,7 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact] private Mock<IActionRunner> CreateStep(TestHostContext hc, TaskResult result, string condition, Boolean continueOnError = false, MappingToken env = null, string name = "Test", bool setOutput = false)
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task StepContextOutcome()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var step1 = CreateStep(hc, TaskResult.Succeeded, "success()", contextName: "step1");
var step2 = CreateStep(hc, TaskResult.Failed, "steps.step1.outcome == 'success'", continueOnError: true, contextName: "step2");
var step3 = CreateStep(hc, TaskResult.Succeeded, "steps.step1.outcome == 'success' && steps.step2.outcome == 'failure'", contextName: "step3");
_ec.Object.Result = null;
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
// Act.
await _stepsRunner.RunAsync(jobContext: _ec.Object);
// Assert.
Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
step1.Verify(x => x.RunAsync(), Times.Once);
step2.Verify(x => x.RunAsync(), Times.Once);
step3.Verify(x => x.RunAsync(), Times.Once);
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Failed.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString(""));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task StepContextConclusion()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var step1 = CreateStep(hc, TaskResult.Succeeded, "false", contextName: "step1");
var step2 = CreateStep(hc, TaskResult.Failed, "steps.step1.conclusion == 'skipped'", continueOnError: true, contextName: "step2");
var step3 = CreateStep(hc, TaskResult.Succeeded, "steps.step1.outcome == 'skipped' && steps.step2.outcome == 'failure' && steps.step2.conclusion == 'success'", contextName: "step3");
_ec.Object.Result = null;
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
// Act.
await _stepsRunner.RunAsync(jobContext: _ec.Object);
// Assert.
Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
step1.Verify(x => x.RunAsync(), Times.Never);
step2.Verify(x => x.RunAsync(), Times.Once);
step3.Verify(x => x.RunAsync(), Times.Once);
Assert.Equal(TaskResult.Skipped.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Skipped.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Failed.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString(""));
}
}
private Mock<IActionRunner> CreateStep(TestHostContext hc, TaskResult result, string condition, Boolean continueOnError = false, MappingToken env = null, string name = "Test", bool setOutput = false, string contextName = null)
{ {
// Setup the step. // Setup the step.
var step = new Mock<IActionRunner>(); var step = new Mock<IActionRunner>();
@@ -599,8 +524,7 @@ namespace GitHub.Runner.Common.Tests.Worker
{ {
Name = name, Name = name,
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Environment = env, Environment = env
ContextName = contextName ?? "Test"
}); });
// Setup the step execution context. // Setup the step execution context.
@@ -612,7 +536,6 @@ namespace GitHub.Runner.Common.Tests.Worker
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts); stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
stepContext.Setup(x => x.JobContext).Returns(_jobContext); stepContext.Setup(x => x.JobContext).Returns(_jobContext);
stepContext.Setup(x => x.StepsContext).Returns(_stepContext); stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName);
stepContext.Setup(x => x.Complete(It.IsAny<TaskResult?>(), It.IsAny<string>(), It.IsAny<string>())) stepContext.Setup(x => x.Complete(It.IsAny<TaskResult?>(), It.IsAny<string>(), It.IsAny<string>()))
.Callback((TaskResult? r, string currentOperation, string resultCode) => .Callback((TaskResult? r, string currentOperation, string resultCode) =>
{ {
@@ -620,9 +543,6 @@ namespace GitHub.Runner.Common.Tests.Worker
{ {
stepContext.Object.Result = r; stepContext.Object.Result = r;
} }
_stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult().ToString());
_stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult().ToString());
}); });
var trace = hc.GetTrace(); var trace = hc.GetTrace();
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); }); stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });

View File

@@ -67,7 +67,7 @@ namespace GitHub.Runner.Common.Tests.Worker
new Pipelines.ContextData.DictionaryContextData() new Pipelines.ContextData.DictionaryContextData()
}, },
}; };
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List<MaskHint>(), resources, context, null, actions, null, null, null, null); var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List<MaskHint>(), resources, context, null, actions, null, null);
return jobRequest; return jobRequest;
} }