mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
4 Commits
DontAddTem
...
users/jww3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afcca9bfa4 | ||
|
|
157e03616e | ||
|
|
30f686b9c2 | ||
|
|
ec5d72810f |
@@ -157,7 +157,7 @@ cat (Runner/Worker)_TIMESTAMP.log # view your log file
|
|||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
We use the .NET Foundation and CoreCLR style guidelines [located here](
|
We use the .NET Foundation and CoreCLR style guidelines [located here](
|
||||||
https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md)
|
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md)
|
||||||
|
|
||||||
### Format C# Code
|
### Format C# Code
|
||||||
|
|
||||||
@@ -165,4 +165,4 @@ To format both staged and unstaged .cs files
|
|||||||
```
|
```
|
||||||
cd ./src
|
cd ./src
|
||||||
./dev.(cmd|sh) format
|
./dev.(cmd|sh) format
|
||||||
```
|
```
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
## Features
|
## Features
|
||||||
- Runner changes for communication with Results service (#2510, #2531, #2535, #2516)
|
- Support matrix context in output keys (#2477)
|
||||||
- Add `*.ghe.localhost` domains to hosted server check (#2536)
|
- Add update certificates to `./run.sh` if `RUNNER_UPDATE_CA_CERTS` env is set (#2471)
|
||||||
- Add `OrchestrationId` to user-agent for better telemetry correlation. (#2568)
|
- Bypass all proxies for all hosts if `no_proxy='*'` is set (#2395)
|
||||||
|
- Change runner image to make user/folder align with `ubuntu-latest` hosted runner. (#2469)
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Fix JIT configurations on Windows (#2497)
|
- Exit on runner version deprecation error (#2299)
|
||||||
- Guard against NullReference while creating HostContext (#2343)
|
- Runner service exit after consecutive re-try exits (#2426)
|
||||||
- Handles broken symlink in `Which` (#2150, #2196)
|
|
||||||
- Adding curl retry for external tool downloads (#2552, #2557)
|
|
||||||
- Limit the time we wait for waiting websocket to connect. (#2554)
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Bump container hooks version to 0.3.1 in runner image (#2496)
|
- Replace deprecated command with environment file (#2429)
|
||||||
- Runner changes to communicate with vNext services (#2487, #2500, #2505, #2541, #2547)
|
- Make requests to `Run` service to renew job request (#2461)
|
||||||
|
- Add job/step log upload to Result service (#2447, #2439)
|
||||||
|
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||||
|
|||||||
@@ -55,23 +55,12 @@ function acquireExternalTool() {
|
|||||||
# Download from source to the partial file.
|
# Download from source to the partial file.
|
||||||
echo "Downloading $download_source"
|
echo "Downloading $download_source"
|
||||||
mkdir -p "$(dirname "$download_target")" || checkRC 'mkdir'
|
mkdir -p "$(dirname "$download_target")" || checkRC 'mkdir'
|
||||||
|
|
||||||
CURL_VERSION=$(curl --version | awk 'NR==1{print $2}')
|
|
||||||
echo "Curl version: $CURL_VERSION"
|
|
||||||
|
|
||||||
# curl -f Fail silently (no output at all) on HTTP errors (H)
|
# curl -f Fail silently (no output at all) on HTTP errors (H)
|
||||||
# -k Allow connections to SSL sites without certs (H)
|
# -k Allow connections to SSL sites without certs (H)
|
||||||
# -S Show error. With -s, make curl show errors when they occur
|
# -S Show error. With -s, make curl show errors when they occur
|
||||||
# -L Follow redirects (H)
|
# -L Follow redirects (H)
|
||||||
# -o FILE Write to FILE instead of stdout
|
# -o FILE Write to FILE instead of stdout
|
||||||
# --retry 3 Retries transient errors 3 times (timeouts, 5xx)
|
curl -fkSL -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
|
||||||
if [[ "$(printf '%s\n' "7.71.0" "$CURL_VERSION" | sort -V | head -n1)" != "7.71.0" ]]; then
|
|
||||||
# Curl version is less than or equal to 7.71.0, skipping retry-all-errors flag
|
|
||||||
curl -fkSL --retry 3 -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
|
|
||||||
else
|
|
||||||
# Curl version is greater than 7.71.0, running curl with --retry-all-errors flag
|
|
||||||
curl -fkSL --retry 3 --retry-all-errors -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Move the partial file to the download target.
|
# Move the partial file to the download target.
|
||||||
mv "$partial_target" "$download_target" || checkRC 'mv'
|
mv "$partial_target" "$download_target" || checkRC 'mv'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -132,7 +132,6 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string GenerateServiceConfig = "generateServiceConfig";
|
public static readonly string GenerateServiceConfig = "generateServiceConfig";
|
||||||
public static readonly string Help = "help";
|
public static readonly string Help = "help";
|
||||||
public static readonly string Local = "local";
|
public static readonly string Local = "local";
|
||||||
public static readonly string NoDefaultLabels = "no-default-labels";
|
|
||||||
public static readonly string Replace = "replace";
|
public static readonly string Replace = "replace";
|
||||||
public static readonly string DisableUpdate = "disableupdate";
|
public static readonly string DisableUpdate = "disableupdate";
|
||||||
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
||||||
@@ -262,8 +261,6 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string AccessToken = "system.accessToken";
|
public static readonly string AccessToken = "system.accessToken";
|
||||||
public static readonly string Culture = "system.culture";
|
public static readonly string Culture = "system.culture";
|
||||||
public static readonly string PhaseDisplayName = "system.phaseDisplayName";
|
public static readonly string PhaseDisplayName = "system.phaseDisplayName";
|
||||||
public static readonly string JobRequestType = "system.jobRequestType";
|
|
||||||
public static readonly string OrchestrationId = "system.orchestrationId";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace GitHub.Runner.Common
|
|||||||
private static int[] _vssHttpCredentialEventIds = new int[] { 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 27, 29 };
|
private static int[] _vssHttpCredentialEventIds = new int[] { 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 27, 29 };
|
||||||
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new();
|
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new();
|
||||||
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new();
|
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new();
|
||||||
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
private readonly ISecretMasker _secretMasker;
|
||||||
private readonly List<ProductInfoHeaderValue> _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
private readonly List<ProductInfoHeaderValue> _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||||
private CancellationTokenSource _runnerShutdownTokenSource = new();
|
private CancellationTokenSource _runnerShutdownTokenSource = new();
|
||||||
private object _perfLock = new();
|
private object _perfLock = new();
|
||||||
@@ -82,17 +82,20 @@ namespace GitHub.Runner.Common
|
|||||||
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(HostContext).GetTypeInfo().Assembly);
|
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(HostContext).GetTypeInfo().Assembly);
|
||||||
_loadContext.Unloading += LoadContext_Unloading;
|
_loadContext.Unloading += LoadContext_Unloading;
|
||||||
|
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.Base64StringEscape);
|
var masks = new List<ValueEncoder>()
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.Base64StringEscapeShift1);
|
{
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.Base64StringEscapeShift2);
|
ValueEncoders.EnumerateBase64Variations,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.CommandLineArgumentEscape);
|
ValueEncoders.CommandLineArgumentEscape,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.ExpressionStringEscape);
|
ValueEncoders.ExpressionStringEscape,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape);
|
ValueEncoders.JsonStringEscape,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
ValueEncoders.UriDataEscape,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
ValueEncoders.XmlDataEscape,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
|
ValueEncoders.TrimDoubleQuotes,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPreAmpersandEscape);
|
ValueEncoders.PowerShellPreAmpersandEscape,
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPostAmpersandEscape);
|
ValueEncoders.PowerShellPostAmpersandEscape
|
||||||
|
};
|
||||||
|
_secretMasker = new SecretMasker(masks);
|
||||||
|
|
||||||
|
|
||||||
// Create StdoutTraceListener if ENV is set
|
// Create StdoutTraceListener if ENV is set
|
||||||
StdoutTraceListener stdoutTraceListener = null;
|
StdoutTraceListener stdoutTraceListener = null;
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ using System.Threading.Tasks;
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
|
||||||
using GitHub.Services.Results.Client;
|
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using GitHub.Services.WebApi.Utilities.Internal;
|
using GitHub.Services.WebApi.Utilities.Internal;
|
||||||
|
using GitHub.Services.Results.Client;
|
||||||
|
using GitHub.Services.OAuth;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -199,15 +199,13 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Trace.Info($"Attempting to start websocket client with delay {delay}.");
|
Trace.Info($"Attempting to start websocket client with delay {delay}.");
|
||||||
await Task.Delay(delay);
|
await Task.Delay(delay);
|
||||||
using var connectTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), default(CancellationToken));
|
||||||
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), connectTimeoutTokenSource.Token);
|
|
||||||
Trace.Info($"Successfully started websocket client.");
|
Trace.Info($"Successfully started websocket client.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Info("Exception caught during websocket client connect, fallback of HTTP would be used now instead of websocket.");
|
Trace.Info("Exception caught during websocket client connect, fallback of HTTP would be used now instead of websocket.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
this._websocketClient = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +252,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
failedAttemptsToPostBatchedLinesByWebsocket++;
|
failedAttemptsToPostBatchedLinesByWebsocket++;
|
||||||
Trace.Info($"Caught exception during append web console line to websocket, let's fallback to sending via non-websocket call (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}, websocket state: {this._websocketClient?.State}).");
|
Trace.Info($"Caught exception during append web console line to websocket, let's fallback to sending via non-websocket call (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}, websocket state: {this._websocketClient?.State}).");
|
||||||
Trace.Verbose(ex.ToString());
|
Trace.Error(ex);
|
||||||
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
|
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
|
||||||
{
|
{
|
||||||
// let's consider failure percentage
|
// let's consider failure percentage
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace GitHub.Runner.Common
|
|||||||
TaskCompletionSource<int> JobRecordUpdated { get; }
|
TaskCompletionSource<int> JobRecordUpdated { get; }
|
||||||
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||||
Task ShutdownAsync();
|
Task ShutdownAsync();
|
||||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultServiceOnly = false);
|
void Start(Pipelines.AgentJobRequestMessage jobRequest);
|
||||||
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
||||||
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
||||||
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
||||||
@@ -70,7 +70,6 @@ namespace GitHub.Runner.Common
|
|||||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
||||||
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
||||||
private bool _queueInProcess = false;
|
private bool _queueInProcess = false;
|
||||||
private bool _resultsServiceOnly = false;
|
|
||||||
|
|
||||||
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
||||||
|
|
||||||
@@ -96,17 +95,13 @@ namespace GitHub.Runner.Common
|
|||||||
_resultsServer = hostContext.GetService<IResultsServer>();
|
_resultsServer = hostContext.GetService<IResultsServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultServiceOnly = false)
|
public void Start(Pipelines.AgentJobRequestMessage jobRequest)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
_resultsServiceOnly = resultServiceOnly;
|
|
||||||
|
|
||||||
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (!resultServiceOnly)
|
_jobServer.InitializeWebsocketClient(serviceEndPoint);
|
||||||
{
|
|
||||||
_jobServer.InitializeWebsocketClient(serviceEndPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code is usually wrapped by an instance of IExecutionContext which isn't available here.
|
// This code is usually wrapped by an instance of IExecutionContext which isn't available here.
|
||||||
jobRequest.Variables.TryGetValue("system.github.results_endpoint", out VariableValue resultsEndpointVariable);
|
jobRequest.Variables.TryGetValue("system.github.results_endpoint", out VariableValue resultsEndpointVariable);
|
||||||
@@ -117,16 +112,8 @@ namespace GitHub.Runner.Common
|
|||||||
!string.IsNullOrEmpty(accessToken) &&
|
!string.IsNullOrEmpty(accessToken) &&
|
||||||
!string.IsNullOrEmpty(resultsReceiverEndpoint))
|
!string.IsNullOrEmpty(resultsReceiverEndpoint))
|
||||||
{
|
{
|
||||||
string liveConsoleFeedUrl = null;
|
|
||||||
Trace.Info("Initializing results client");
|
Trace.Info("Initializing results client");
|
||||||
if (resultServiceOnly
|
_resultsServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), accessToken);
|
||||||
&& serviceEndPoint.Data.TryGetValue("FeedStreamUrl", out var feedStreamUrl)
|
|
||||||
&& !string.IsNullOrEmpty(feedStreamUrl))
|
|
||||||
{
|
|
||||||
liveConsoleFeedUrl = feedStreamUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
_resultsServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), liveConsoleFeedUrl, accessToken);
|
|
||||||
_resultsClientInitiated = true;
|
_resultsClientInitiated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,9 +194,6 @@ namespace GitHub.Runner.Common
|
|||||||
Trace.Info($"Disposing job server ...");
|
Trace.Info($"Disposing job server ...");
|
||||||
await _jobServer.DisposeAsync();
|
await _jobServer.DisposeAsync();
|
||||||
|
|
||||||
Trace.Info($"Disposing results server ...");
|
|
||||||
await _resultsServer.DisposeAsync();
|
|
||||||
|
|
||||||
Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
|
Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,14 +372,7 @@ namespace GitHub.Runner.Common
|
|||||||
// Give at most 60s for each request.
|
// Give at most 60s for each request.
|
||||||
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)))
|
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)))
|
||||||
{
|
{
|
||||||
if (_resultsServiceOnly)
|
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, timeoutTokenSource.Token);
|
||||||
{
|
|
||||||
await _resultsServer.AppendLiveConsoleFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, timeoutTokenSource.Token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, timeoutTokenSource.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_firstConsoleOutputs)
|
if (_firstConsoleOutputs)
|
||||||
@@ -622,7 +599,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
foreach (var detailTimeline in update.PendingRecords.Where(r => r.Details != null))
|
foreach (var detailTimeline in update.PendingRecords.Where(r => r.Details != null))
|
||||||
{
|
{
|
||||||
if (!_resultsServiceOnly && !_allTimelines.Contains(detailTimeline.Details.Id))
|
if (!_allTimelines.Contains(detailTimeline.Details.Id))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -644,11 +621,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_resultsServiceOnly)
|
await _jobServer.UpdateTimelineRecordsAsync(_scopeIdentifier, _hubName, _planId, update.TimelineId, update.PendingRecords, default(CancellationToken));
|
||||||
{
|
|
||||||
await _jobServer.UpdateTimelineRecordsAsync(_scopeIdentifier, _hubName, _planId, update.TimelineId, update.PendingRecords, default(CancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_resultsClientInitiated)
|
if (_resultsClientInitiated)
|
||||||
@@ -756,17 +729,17 @@ namespace GitHub.Runner.Common
|
|||||||
timelineRecord.State = rec.State ?? timelineRecord.State;
|
timelineRecord.State = rec.State ?? timelineRecord.State;
|
||||||
timelineRecord.WorkerName = rec.WorkerName ?? timelineRecord.WorkerName;
|
timelineRecord.WorkerName = rec.WorkerName ?? timelineRecord.WorkerName;
|
||||||
|
|
||||||
if (rec.ErrorCount > 0)
|
if (rec.ErrorCount != null && rec.ErrorCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.ErrorCount = rec.ErrorCount;
|
timelineRecord.ErrorCount = rec.ErrorCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec.WarningCount > 0)
|
if (rec.WarningCount != null && rec.WarningCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.WarningCount = rec.WarningCount;
|
timelineRecord.WarningCount = rec.WarningCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec.NoticeCount > 0)
|
if (rec.NoticeCount != null && rec.NoticeCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.NoticeCount = rec.NoticeCount;
|
timelineRecord.NoticeCount = rec.NoticeCount;
|
||||||
}
|
}
|
||||||
@@ -797,7 +770,7 @@ namespace GitHub.Runner.Common
|
|||||||
foreach (var record in mergedRecords)
|
foreach (var record in mergedRecords)
|
||||||
{
|
{
|
||||||
Trace.Verbose($" Record: t={record.RecordType}, n={record.Name}, s={record.State}, st={record.StartTime}, {record.PercentComplete}%, ft={record.FinishTime}, r={record.Result}: {record.CurrentOperation}");
|
Trace.Verbose($" Record: t={record.RecordType}, n={record.Name}, s={record.State}, st={record.StartTime}, {record.PercentComplete}%, ft={record.FinishTime}, r={record.Result}: {record.CurrentOperation}");
|
||||||
if (record.Issues != null)
|
if (record.Issues != null && record.Issues.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var issue in record.Issues)
|
foreach (var issue in record.Issues)
|
||||||
{
|
{
|
||||||
@@ -807,7 +780,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.Variables != null)
|
if (record.Variables != null && record.Variables.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var variable in record.Variables)
|
foreach (var variable in record.Variables)
|
||||||
{
|
{
|
||||||
@@ -818,35 +791,33 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
return mergedRecords;
|
return mergedRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UploadFile(UploadFileInfo file)
|
private async Task UploadFile(UploadFileInfo file)
|
||||||
{
|
{
|
||||||
bool uploadSucceed = false;
|
bool uploadSucceed = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_resultsServiceOnly)
|
if (String.Equals(file.Type, CoreAttachmentType.Log, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (String.Equals(file.Type, CoreAttachmentType.Log, StringComparison.OrdinalIgnoreCase))
|
// Create the log
|
||||||
|
var taskLog = await _jobServer.CreateLogAsync(_scopeIdentifier, _hubName, _planId, new TaskLog(String.Format(@"logs\{0:D}", file.TimelineRecordId)), default(CancellationToken));
|
||||||
|
|
||||||
|
// Upload the contents
|
||||||
|
using (FileStream fs = File.Open(file.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
{
|
{
|
||||||
// Create the log
|
var logUploaded = await _jobServer.AppendLogContentAsync(_scopeIdentifier, _hubName, _planId, taskLog.Id, fs, default(CancellationToken));
|
||||||
var taskLog = await _jobServer.CreateLogAsync(_scopeIdentifier, _hubName, _planId, new TaskLog(String.Format(@"logs\{0:D}", file.TimelineRecordId)), default(CancellationToken));
|
|
||||||
|
|
||||||
// Upload the contents
|
|
||||||
using (FileStream fs = File.Open(file.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
|
||||||
{
|
|
||||||
var logUploaded = await _jobServer.AppendLogContentAsync(_scopeIdentifier, _hubName, _planId, taskLog.Id, fs, default(CancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new record and only set the Log field
|
|
||||||
var attachmentUpdataRecord = new TimelineRecord() { Id = file.TimelineRecordId, Log = taskLog };
|
|
||||||
QueueTimelineRecordUpdate(file.TimelineId, attachmentUpdataRecord);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// Create a new record and only set the Log field
|
||||||
|
var attachmentUpdataRecord = new TimelineRecord() { Id = file.TimelineRecordId, Log = taskLog };
|
||||||
|
QueueTimelineRecordUpdate(file.TimelineId, attachmentUpdataRecord);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create attachment
|
||||||
|
using (FileStream fs = File.Open(file.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
{
|
{
|
||||||
// Create attachment
|
var result = await _jobServer.CreateAttachmentAsync(_scopeIdentifier, _hubName, _planId, file.TimelineId, file.TimelineRecordId, file.Type, file.Name, fs, default(CancellationToken));
|
||||||
using (FileStream fs = File.Open(file.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
|
||||||
{
|
|
||||||
var result = await _jobServer.CreateAttachmentAsync(_scopeIdentifier, _hubName, _planId, file.TimelineId, file.TimelineRecordId, file.Type, file.Name, fs, default(CancellationToken));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.Launch.Client;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
|
||||||
{
|
|
||||||
[ServiceLocator(Default = typeof(LaunchServer))]
|
|
||||||
public interface ILaunchServer : IRunnerService
|
|
||||||
{
|
|
||||||
void InitializeLaunchClient(Uri uri, string token);
|
|
||||||
|
|
||||||
Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class LaunchServer : RunnerService, ILaunchServer
|
|
||||||
{
|
|
||||||
private LaunchHttpClient _launchClient;
|
|
||||||
|
|
||||||
public void InitializeLaunchClient(Uri uri, string token)
|
|
||||||
{
|
|
||||||
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
|
||||||
this._launchClient = new LaunchHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_launchClient != null)
|
|
||||||
{
|
|
||||||
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException("Launch client is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Security;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Services.Results.Client;
|
using GitHub.Services.Results.Client;
|
||||||
using GitHub.Services.WebApi.Utilities.Internal;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
[ServiceLocator(Default = typeof(ResultServer))]
|
[ServiceLocator(Default = typeof(ResultServer))]
|
||||||
public interface IResultsServer : IRunnerService, IAsyncDisposable
|
public interface IResultsServer : IRunnerService
|
||||||
{
|
{
|
||||||
void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token);
|
void InitializeResultsClient(Uri uri, string token);
|
||||||
|
|
||||||
Task<bool> AppendLiveConsoleFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
// logging and console
|
// logging and console
|
||||||
Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
||||||
@@ -40,26 +31,10 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
private ResultsHttpClient _resultsClient;
|
private ResultsHttpClient _resultsClient;
|
||||||
|
|
||||||
private ClientWebSocket _websocketClient;
|
public void InitializeResultsClient(Uri uri, string token)
|
||||||
private DateTime? _lastConnectionFailure;
|
|
||||||
|
|
||||||
private static readonly TimeSpan MinDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(100);
|
|
||||||
private static readonly TimeSpan MaxDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(500);
|
|
||||||
|
|
||||||
private Task _websocketConnectTask;
|
|
||||||
private String _liveConsoleFeedUrl;
|
|
||||||
private string _token;
|
|
||||||
|
|
||||||
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
|
|
||||||
{
|
{
|
||||||
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
||||||
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
||||||
_token = token;
|
|
||||||
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
|
|
||||||
{
|
|
||||||
_liveConsoleFeedUrl = liveConsoleFeedUrl;
|
|
||||||
InitializeWebsocketClient(liveConsoleFeedUrl, token, TimeSpan.Zero, retryConnection: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
||||||
@@ -119,144 +94,5 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
throw new InvalidOperationException("Results client is not initialized.");
|
throw new InvalidOperationException("Results client is not initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
|
|
||||||
return ValueTask.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeWebsocketClient(string liveConsoleFeedUrl, string accessToken, TimeSpan delay, bool retryConnection = false)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(accessToken))
|
|
||||||
{
|
|
||||||
Trace.Info($"No access token from server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(liveConsoleFeedUrl))
|
|
||||||
{
|
|
||||||
Trace.Info($"No live console feed url from server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Creating websocket client ..." + liveConsoleFeedUrl);
|
|
||||||
this._websocketClient = new ClientWebSocket();
|
|
||||||
this._websocketClient.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}");
|
|
||||||
var userAgentValues = new List<ProductInfoHeaderValue>();
|
|
||||||
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
|
|
||||||
userAgentValues.AddRange(HostContext.UserAgents);
|
|
||||||
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x => x.ToString())));
|
|
||||||
|
|
||||||
// during initialization, retry upto 3 times to setup connection
|
|
||||||
this._websocketConnectTask = ConnectWebSocketClient(liveConsoleFeedUrl, delay, retryConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ConnectWebSocketClient(string feedStreamUrl, TimeSpan delay, bool retryConnection = false)
|
|
||||||
{
|
|
||||||
bool connected = false;
|
|
||||||
int retries = 0;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Trace.Info($"Attempting to start websocket client with delay {delay}.");
|
|
||||||
await Task.Delay(delay);
|
|
||||||
using var connectTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
|
||||||
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), connectTimeoutTokenSource.Token);
|
|
||||||
Trace.Info($"Successfully started websocket client.");
|
|
||||||
connected = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info("Exception caught during websocket client connect, retry connection.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
retries++;
|
|
||||||
this._websocketClient = null;
|
|
||||||
_lastConnectionFailure = DateTime.Now;
|
|
||||||
}
|
|
||||||
} while (retryConnection && !connected && retries < 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> AppendLiveConsoleFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_websocketConnectTask != null)
|
|
||||||
{
|
|
||||||
await _websocketConnectTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool delivered = false;
|
|
||||||
int retries = 0;
|
|
||||||
|
|
||||||
// "_websocketClient != null" implies either: We have a successful connection OR we have to attempt sending again and then reconnect
|
|
||||||
// ...in other words, if websocket client is null, we will skip sending to websocket
|
|
||||||
if (_websocketClient != null)
|
|
||||||
{
|
|
||||||
var linesWrapper = startLine.HasValue
|
|
||||||
? new TimelineRecordFeedLinesWrapper(stepId, lines, startLine.Value)
|
|
||||||
: new TimelineRecordFeedLinesWrapper(stepId, lines);
|
|
||||||
var jsonData = StringUtil.ConvertToJson(linesWrapper);
|
|
||||||
var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
|
|
||||||
// break the message into chunks of 1024 bytes
|
|
||||||
for (var i = 0; i < jsonDataBytes.Length; i += 1 * 1024)
|
|
||||||
{
|
|
||||||
var lastChunk = i + (1 * 1024) >= jsonDataBytes.Length;
|
|
||||||
var chunk = new ArraySegment<byte>(jsonDataBytes, i, Math.Min(1 * 1024, jsonDataBytes.Length - i));
|
|
||||||
|
|
||||||
delivered = false;
|
|
||||||
while (!delivered && retries < 3)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_websocketClient != null)
|
|
||||||
{
|
|
||||||
await _websocketClient.SendAsync(chunk, WebSocketMessageType.Text, endOfMessage: lastChunk, cancellationToken);
|
|
||||||
delivered = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var delay = BackoffTimerHelper.GetRandomBackoff(MinDelayForWebsocketReconnect, MaxDelayForWebsocketReconnect);
|
|
||||||
Trace.Info($"Websocket is not open, let's attempt to connect back again with random backoff {delay} ms.");
|
|
||||||
Trace.Verbose(ex.ToString());
|
|
||||||
retries++;
|
|
||||||
InitializeWebsocketClient(_liveConsoleFeedUrl, _token, delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!delivered)
|
|
||||||
{
|
|
||||||
// Giving up for now, so next invocation of this method won't attempt to reconnect
|
|
||||||
_websocketClient = null;
|
|
||||||
|
|
||||||
// however if 10 minutes have already passed, let's try reestablish connection again
|
|
||||||
if (_lastConnectionFailure.HasValue && DateTime.Now > _lastConnectionFailure.Value.AddMinutes(10))
|
|
||||||
{
|
|
||||||
// Some minutes passed since we retried last time, try connection again
|
|
||||||
InitializeWebsocketClient(_liveConsoleFeedUrl, _token, TimeSpan.Zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return delivered;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CloseWebSocket(WebSocketCloseStatus closeStatus, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_websocketClient?.CloseOutputAsync(closeStatus, "Closing websocket", cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception websocketEx)
|
|
||||||
{
|
|
||||||
// In some cases this might be okay since the websocket might be open yet, so just close and don't trace exceptions
|
|
||||||
Trace.Info($"Failed to close websocket gracefully {websocketEx.GetType().Name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||||
|
|
||||||
Task CompleteJobAsync(
|
Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken token);
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
TaskResult result,
|
|
||||||
Dictionary<String, VariableValue> outputs,
|
|
||||||
IList<StepResult> stepResults,
|
|
||||||
IList<Annotation> jobAnnotations,
|
|
||||||
CancellationToken token);
|
|
||||||
|
|
||||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||||
}
|
}
|
||||||
@@ -58,30 +51,34 @@ namespace GitHub.Runner.Common
|
|||||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return RetryRequest<AgentJobRequestMessage>(
|
var jobMessage = RetryRequest<AgentJobRequestMessage>(
|
||||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken,
|
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken);
|
||||||
shouldRetry: ex => ex is not TaskOrchestrationJobAlreadyAcquiredException);
|
if (jobMessage == null)
|
||||||
|
{
|
||||||
|
throw new TaskOrchestrationJobNotFoundException(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CompleteJobAsync(
|
public Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken cancellationToken)
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
TaskResult result,
|
|
||||||
Dictionary<String, VariableValue> outputs,
|
|
||||||
IList<StepResult> stepResults,
|
|
||||||
IList<Annotation> jobAnnotations,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return RetryRequest(
|
return RetryRequest(
|
||||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, cancellationToken), cancellationToken);
|
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, cancellationToken), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return RetryRequest<RenewJobResponse>(
|
var renewJobResponse = RetryRequest<RenewJobResponse>(
|
||||||
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken);
|
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken);
|
||||||
|
if (renewJobResponse == null)
|
||||||
|
{
|
||||||
|
throw new TaskOrchestrationJobNotFoundException(jobId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return renewJobResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
@@ -80,11 +80,10 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
|
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
int maxRetryAttemptsCount = 5,
|
int maxRetryAttemptsCount = 5
|
||||||
Func<Exception, bool> shouldRetry = null
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var retryCount = 0;
|
var retryCount = 0;
|
||||||
@@ -97,7 +96,7 @@ namespace GitHub.Runner.Common
|
|||||||
return await func();
|
return await func();
|
||||||
}
|
}
|
||||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount && (shouldRetry == null || shouldRetry(ex)))
|
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
||||||
{
|
{
|
||||||
Trace.Error("Catch exception during request");
|
Trace.Error("Catch exception during request");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ namespace GitHub.Runner.Listener
|
|||||||
private readonly Dictionary<string, string[]> validOptions = new()
|
private readonly Dictionary<string, string[]> validOptions = new()
|
||||||
{
|
{
|
||||||
// Valid configure flags and args
|
// Valid configure flags and args
|
||||||
[Constants.Runner.CommandLine.Commands.Configure] =
|
[Constants.Runner.CommandLine.Commands.Configure] =
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
||||||
Constants.Runner.CommandLine.Flags.Ephemeral,
|
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||||
@@ -38,7 +38,6 @@ namespace GitHub.Runner.Listener
|
|||||||
Constants.Runner.CommandLine.Flags.Replace,
|
Constants.Runner.CommandLine.Flags.Replace,
|
||||||
Constants.Runner.CommandLine.Flags.RunAsService,
|
Constants.Runner.CommandLine.Flags.RunAsService,
|
||||||
Constants.Runner.CommandLine.Flags.Unattended,
|
Constants.Runner.CommandLine.Flags.Unattended,
|
||||||
Constants.Runner.CommandLine.Flags.NoDefaultLabels,
|
|
||||||
Constants.Runner.CommandLine.Args.Auth,
|
Constants.Runner.CommandLine.Args.Auth,
|
||||||
Constants.Runner.CommandLine.Args.Labels,
|
Constants.Runner.CommandLine.Args.Labels,
|
||||||
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
||||||
@@ -86,7 +85,6 @@ namespace GitHub.Runner.Listener
|
|||||||
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
||||||
public bool GenerateServiceConfig => TestFlag(Constants.Runner.CommandLine.Flags.GenerateServiceConfig);
|
public bool GenerateServiceConfig => TestFlag(Constants.Runner.CommandLine.Flags.GenerateServiceConfig);
|
||||||
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
||||||
public bool NoDefaultLabels => TestFlag(Constants.Runner.CommandLine.Flags.NoDefaultLabels);
|
|
||||||
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
||||||
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
||||||
public bool RemoveLocalConfig => TestFlag(Constants.Runner.CommandLine.Flags.Local);
|
public bool RemoveLocalConfig => TestFlag(Constants.Runner.CommandLine.Flags.Local);
|
||||||
@@ -184,7 +182,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
command = Constants.Runner.CommandLine.Commands.Warmup;
|
command = Constants.Runner.CommandLine.Commands.Warmup;
|
||||||
}
|
}
|
||||||
|
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (command.GetReplace())
|
if (command.GetReplace())
|
||||||
{
|
{
|
||||||
// Update existing agent with new PublicKey, agent version.
|
// Update existing agent with new PublicKey, agent version.
|
||||||
agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate, command.NoDefaultLabels);
|
agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -293,7 +293,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a new agent.
|
// Create a new agent.
|
||||||
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate, command.NoDefaultLabels);
|
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -554,7 +554,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate, bool noDefaultLabels)
|
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(agent, nameof(agent));
|
ArgUtil.NotNull(agent, nameof(agent));
|
||||||
agent.Authorization = new TaskAgentAuthorization
|
agent.Authorization = new TaskAgentAuthorization
|
||||||
@@ -571,16 +571,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
agent.Labels.Clear();
|
agent.Labels.Clear();
|
||||||
|
|
||||||
if (!noDefaultLabels)
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
{
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
|
||||||
}
|
|
||||||
else if (userLabels.Count == 0)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Disabling default labels via --no-default-labels without specifying --labels is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var userLabel in userLabels)
|
foreach (var userLabel in userLabels)
|
||||||
{
|
{
|
||||||
@@ -590,7 +583,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate, bool noDefaultLabels)
|
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
|
||||||
{
|
{
|
||||||
TaskAgent agent = new(agentName)
|
TaskAgent agent = new(agentName)
|
||||||
{
|
{
|
||||||
@@ -605,16 +598,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
DisableUpdate = disableUpdate
|
DisableUpdate = disableUpdate
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!noDefaultLabels)
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
{
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
|
||||||
}
|
|
||||||
else if (userLabels.Count == 0)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Disabling default labels via --no-default-labels without specifying --labels is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var userLabel in userLabels)
|
foreach (var userLabel in userLabels)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using GitHub.Runner.Sdk;
|
|||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using GitHub.Services.WebApi.Jwt;
|
using GitHub.Services.WebApi.Jwt;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
@@ -373,8 +372,6 @@ namespace GitHub.Runner.Listener
|
|||||||
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
var notification = HostContext.GetService<IJobNotification>();
|
var notification = HostContext.GetService<IJobNotification>();
|
||||||
|
|
||||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
// lock renew cancellation token.
|
// lock renew cancellation token.
|
||||||
using (var lockRenewalTokenSource = new CancellationTokenSource())
|
using (var lockRenewalTokenSource = new CancellationTokenSource())
|
||||||
using (var workerProcessCancelTokenSource = new CancellationTokenSource())
|
using (var workerProcessCancelTokenSource = new CancellationTokenSource())
|
||||||
@@ -382,6 +379,8 @@ namespace GitHub.Runner.Listener
|
|||||||
long requestId = message.RequestId;
|
long requestId = message.RequestId;
|
||||||
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
||||||
|
|
||||||
|
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
// start renew job request
|
// start renew job request
|
||||||
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
||||||
Task renewJobRequest = RenewJobRequestAsync(message, systemConnection, _poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
Task renewJobRequest = RenewJobRequestAsync(message, systemConnection, _poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
||||||
@@ -406,7 +405,7 @@ namespace GitHub.Runner.Listener
|
|||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// complete job request with result Cancelled
|
// complete job request with result Cancelled
|
||||||
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, TaskResult.Canceled);
|
await CompleteJobRequestAsync(_poolId, message, lockToken, TaskResult.Canceled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,6 +544,7 @@ namespace GitHub.Runner.Listener
|
|||||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||||
|
|
||||||
|
|
||||||
var jobServer = await InitializeJobServerAsync(systemConnection);
|
var jobServer = await InitializeJobServerAsync(systemConnection);
|
||||||
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||||
|
|
||||||
@@ -552,7 +552,7 @@ namespace GitHub.Runner.Listener
|
|||||||
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
||||||
await ForceFailJob(jobServer, message, detailInfo);
|
await ForceFailJob(jobServer, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,7 +567,7 @@ namespace GitHub.Runner.Listener
|
|||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// complete job request
|
// complete job request
|
||||||
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, result, detailInfo);
|
await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo);
|
||||||
|
|
||||||
// print out unhandled exception happened in worker after we complete job request.
|
// 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.
|
// when we run out of disk space, report back to server has higher priority.
|
||||||
@@ -664,7 +664,7 @@ namespace GitHub.Runner.Listener
|
|||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// complete job request
|
// complete job request
|
||||||
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, resultOnAbandonOrCancel);
|
await CompleteJobRequestAsync(_poolId, message, lockToken, resultOnAbandonOrCancel);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -1065,7 +1065,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, ServiceEndpoint systemConnection, Guid lockToken, TaskResult result, string detailInfo = null)
|
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
@@ -1077,23 +1077,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
if (this._isRunServiceJob)
|
if (this._isRunServiceJob)
|
||||||
{
|
{
|
||||||
var runServer = await GetRunServerAsync(systemConnection);
|
Trace.Verbose($"Skip FinishAgentRequest call from Listener because MessageType is {message.MessageType}");
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
|
||||||
var unhandledAnnotation = unhandledExceptionIssue.ToAnnotation();
|
|
||||||
var jobAnnotations = new List<Annotation>();
|
|
||||||
if (unhandledAnnotation.HasValue)
|
|
||||||
{
|
|
||||||
jobAnnotations.Add(unhandledAnnotation.Value);
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error("Fail to raise job completion back to service.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,7 +1117,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
// log an error issue to job level timeline record
|
// log an error issue to job level timeline record
|
||||||
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
|
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
||||||
{
|
{
|
||||||
if (server is IJobServer jobServer)
|
if (server is IJobServer jobServer)
|
||||||
{
|
{
|
||||||
@@ -1145,11 +1129,34 @@ namespace GitHub.Runner.Listener
|
|||||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||||
|
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(errorMessage) &&
|
||||||
|
message.Variables.TryGetValue("DistributedTask.EnableRunnerIPCDebug", out var enableRunnerIPCDebug) &&
|
||||||
|
StringUtil.ConvertToBoolean(enableRunnerIPCDebug.Value))
|
||||||
|
{
|
||||||
|
// the trace should be best effort and not affect any job result
|
||||||
|
var match = _invalidJsonRegex.Match(errorMessage);
|
||||||
|
if (match.Success &&
|
||||||
|
match.Groups.Count == 2)
|
||||||
|
{
|
||||||
|
var jsonPosition = int.Parse(match.Groups[1].Value);
|
||||||
|
var serializedJobMessage = JsonUtility.ToString(message);
|
||||||
|
var originalJson = serializedJobMessage.Substring(jsonPosition - 10, 20);
|
||||||
|
errorMessage = $"Runner sent Json at position '{jsonPosition}': {originalJson} ({Convert.ToBase64String(Encoding.UTF8.GetBytes(originalJson))})\n{errorMessage}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error(ex);
|
||||||
|
errorMessage = $"Fail to check json IPC error: {ex.Message}\n{errorMessage}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||||
jobRecord.ErrorCount++;
|
jobRecord.ErrorCount++;
|
||||||
jobRecord.Issues.Add(unhandledExceptionIssue);
|
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||||
|
|
||||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1160,13 +1167,13 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", detailInfo);
|
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// raise job completed event to fail the job.
|
// raise job completed event to fail the job.
|
||||||
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
|
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message)
|
||||||
{
|
{
|
||||||
if (server is IJobServer jobServer)
|
if (server is IJobServer jobServer)
|
||||||
{
|
{
|
||||||
@@ -1185,15 +1192,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, CancellationToken.None);
|
||||||
var unhandledAnnotation = unhandledExceptionIssue.ToAnnotation();
|
|
||||||
var jobAnnotations = new List<Annotation>();
|
|
||||||
if (unhandledAnnotation.HasValue)
|
|
||||||
{
|
|
||||||
jobAnnotations.Add(unhandledAnnotation.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, CancellationToken.None);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -549,17 +549,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
var runServer = HostContext.CreateService<IRunServer>();
|
var runServer = HostContext.CreateService<IRunServer>();
|
||||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||||
try
|
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
{
|
|
||||||
jobRequestMessage =
|
|
||||||
await runServer.GetJobMessageAsync(messageRef.RunnerRequestId,
|
|
||||||
messageQueueLoopTokenSource.Token);
|
|
||||||
}
|
|
||||||
catch (TaskOrchestrationJobAlreadyAcquiredException)
|
|
||||||
{
|
|
||||||
Trace.Info("Job is already acquired, skip this message.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jobDispatcher.Run(jobRequestMessage, runOnce);
|
jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||||
@@ -683,8 +673,7 @@ Config Options:
|
|||||||
--token string Registration token. Required if unattended
|
--token string Registration token. Required if unattended
|
||||||
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
||||||
--runnergroup string Name of the runner group to add this runner to (defaults to the default runner group)
|
--runnergroup string Name of the runner group to add this runner to (defaults to the default runner group)
|
||||||
--labels string Custom labels that will be added to the runner. This option is mandatory if --no-default-labels is used.
|
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
||||||
--no-default-labels Disables adding the default labels: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
|
||||||
--local Removes the runner config files from your local machine. Used as an option to the remove command
|
--local Removes the runner config files from your local machine. Used as an option to the remove command
|
||||||
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
||||||
--replace Replace any existing runner with the same name (default false)
|
--replace Replace any existing runner with the same name (default false)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace GitHub.Runner.Sdk
|
namespace GitHub.Runner.Sdk
|
||||||
{
|
{
|
||||||
@@ -11,11 +11,10 @@ namespace GitHub.Runner.Sdk
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase) ||
|
||||||
gitHubUrl.Host.EndsWith(".ghe.localhost", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
gitHubUrl.Host.EndsWith(".ghe.com", StringComparison.OrdinalIgnoreCase);
|
gitHubUrl.Host.EndsWith(".ghe.com", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
trace?.Verbose(ex.ToString());
|
trace?.Verbose(ex.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
|
if (matches != null && matches.Length > 0)
|
||||||
{
|
{
|
||||||
trace?.Info($"Location: '{matches.First()}'");
|
trace?.Info($"Location: '{matches.First()}'");
|
||||||
return matches.First();
|
return matches.First();
|
||||||
@@ -86,7 +86,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
for (int i = 0; i < pathExtSegments.Length; i++)
|
for (int i = 0; i < pathExtSegments.Length; i++)
|
||||||
{
|
{
|
||||||
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
|
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
|
||||||
if (matches.Any(p => p.Equals(fullPath, StringComparison.OrdinalIgnoreCase)) && IsPathValid(fullPath, trace))
|
if (matches.Any(p => p.Equals(fullPath, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
trace?.Info($"Location: '{fullPath}'");
|
trace?.Info($"Location: '{fullPath}'");
|
||||||
return fullPath;
|
return fullPath;
|
||||||
@@ -105,7 +105,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
trace?.Verbose(ex.ToString());
|
trace?.Verbose(ex.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
|
if (matches != null && matches.Length > 0)
|
||||||
{
|
{
|
||||||
trace?.Info($"Location: '{matches.First()}'");
|
trace?.Info($"Location: '{matches.First()}'");
|
||||||
return matches.First();
|
return matches.First();
|
||||||
@@ -128,15 +128,5 @@ namespace GitHub.Runner.Sdk
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if the file is a symlink and if the symlink`s target exists.
|
|
||||||
private static bool IsPathValid(string path, ITraceWriter trace = null)
|
|
||||||
{
|
|
||||||
var fileInfo = new FileInfo(path);
|
|
||||||
var linkTargetFullPath = fileInfo.Directory?.FullName + Path.DirectorySeparatorChar + fileInfo.LinkTarget;
|
|
||||||
if(fileInfo.LinkTarget == null || File.Exists(linkTargetFullPath) || File.Exists(fileInfo.LinkTarget)) return true;
|
|
||||||
trace?.Info($"the target '{fileInfo.LinkTarget}' of the symbolic link '{path}', does not exist");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using System;
|
using System;
|
||||||
@@ -276,7 +276,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Message = $"Can't update {blocked} environment variable using ::set-env:: command."
|
Message = $"Can't update {blocked} environment variable using ::set-env:: command."
|
||||||
};
|
};
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = $"{Constants.Runner.UnsupportedCommand}_{envName}";
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = $"{Constants.Runner.UnsupportedCommand}_{envName}";
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
||||||
};
|
};
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
||||||
@@ -350,7 +350,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
||||||
};
|
};
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
||||||
@@ -666,7 +666,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using WebApi = GitHub.DistributedTask.WebApi;
|
using WebApi = GitHub.DistributedTask.WebApi;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -102,19 +100,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
||||||
executionContext.Output("Prepare all required actions");
|
executionContext.Output("Prepare all required actions");
|
||||||
PrepareActionsState result = new PrepareActionsState();
|
var result = await PrepareActionsRecursiveAsync(executionContext, state, actions, depth, rootStepId);
|
||||||
try
|
|
||||||
{
|
|
||||||
result = await PrepareActionsRecursiveAsync(executionContext, state, actions, depth, rootStepId);
|
|
||||||
}
|
|
||||||
catch (FailedToResolveActionDownloadInfoException ex)
|
|
||||||
{
|
|
||||||
// Log the error and fail the PrepareActionsAsync Initialization.
|
|
||||||
Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}");
|
|
||||||
executionContext.InfrastructureError(ex.Message);
|
|
||||||
executionContext.Result = TaskResult.Failed;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
if (!FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
if (!FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
||||||
{
|
{
|
||||||
if (state.ImagesToPull.Count > 0)
|
if (state.ImagesToPull.Count > 0)
|
||||||
@@ -662,21 +648,13 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve download info
|
// Resolve download info
|
||||||
var launchServer = HostContext.GetService<ILaunchServer>();
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
var actionDownloadInfos = default(WebApi.ActionDownloadInfoCollection);
|
var actionDownloadInfos = default(WebApi.ActionDownloadInfoCollection);
|
||||||
for (var attempt = 1; attempt <= 3; attempt++)
|
for (var attempt = 1; attempt <= 3; attempt++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (MessageUtil.IsRunServiceJob(executionContext.Global.Variables.Get(Constants.Variables.System.JobRequestType)))
|
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||||
{
|
|
||||||
actionDownloadInfos = await launchServer.ResolveActionsDownloadInfoAsync(executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is cancelled.
|
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is cancelled.
|
||||||
|
|||||||
@@ -18,22 +18,15 @@ using GitHub.Runner.Sdk;
|
|||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using GitHub.Runner.Worker.Handlers;
|
using GitHub.Runner.Worker.Handlers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
public static class ExecutionContextType
|
public class ExecutionContextType
|
||||||
{
|
{
|
||||||
public const string Job = "Job";
|
public static string Job = "Job";
|
||||||
public const string Task = "Task";
|
public static string Task = "Task";
|
||||||
}
|
|
||||||
|
|
||||||
public record ExecutionContextLogOptions(bool WriteToLog, string LogMessageOverride)
|
|
||||||
{
|
|
||||||
public static readonly ExecutionContextLogOptions None = new(false, null);
|
|
||||||
public static readonly ExecutionContextLogOptions Default = new(true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ExecutionContext))]
|
[ServiceLocator(Default = typeof(ExecutionContext))]
|
||||||
@@ -99,7 +92,7 @@ namespace GitHub.Runner.Worker
|
|||||||
void SetGitHubContext(string name, string value);
|
void SetGitHubContext(string name, string value);
|
||||||
void SetOutput(string name, string value, out string reference);
|
void SetOutput(string name, string value, out string reference);
|
||||||
void SetTimeout(TimeSpan? timeout);
|
void SetTimeout(TimeSpan? timeout);
|
||||||
void AddIssue(Issue issue, ExecutionContextLogOptions logOptions);
|
void AddIssue(Issue issue, string message = null);
|
||||||
void Progress(int percentage, string currentOperation = null);
|
void Progress(int percentage, string currentOperation = null);
|
||||||
void UpdateDetailTimelineRecord(TimelineRecord record);
|
void UpdateDetailTimelineRecord(TimelineRecord record);
|
||||||
|
|
||||||
@@ -125,7 +118,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
{
|
{
|
||||||
private const int _maxCountPerIssueType = 10;
|
private const int _maxIssueCount = 10;
|
||||||
private const int _throttlingDelayReportThreshold = 10 * 1000; // Don't report throttling with less than 10 seconds delay
|
private const int _throttlingDelayReportThreshold = 10 * 1000; // Don't report throttling with less than 10 seconds delay
|
||||||
private const int _maxIssueMessageLength = 4096; // Don't send issue with huge message since we can't forward them from actions to check annotation.
|
private const int _maxIssueMessageLength = 4096; // Don't send issue with huge message since we can't forward them from actions to check annotation.
|
||||||
private const int _maxIssueCountInTelemetry = 3; // Only send the first 3 issues to telemetry
|
private const int _maxIssueCountInTelemetry = 3; // Only send the first 3 issues to telemetry
|
||||||
@@ -133,10 +126,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private readonly TimelineRecord _record = new();
|
private readonly TimelineRecord _record = new();
|
||||||
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
|
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
|
||||||
private readonly List<Issue> _embeddedIssueCollector;
|
|
||||||
private readonly object _loggerLock = new();
|
private readonly object _loggerLock = new();
|
||||||
private readonly object _matchersLock = new();
|
private readonly object _matchersLock = new();
|
||||||
private readonly ExecutionContext _parentExecutionContext;
|
|
||||||
|
|
||||||
private event OnMatcherChanged _onMatcherChanged;
|
private event OnMatcherChanged _onMatcherChanged;
|
||||||
|
|
||||||
@@ -144,6 +135,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private IPagingLogger _logger;
|
private IPagingLogger _logger;
|
||||||
private IJobServerQueue _jobServerQueue;
|
private IJobServerQueue _jobServerQueue;
|
||||||
|
private ExecutionContext _parentExecutionContext;
|
||||||
|
|
||||||
private Guid _mainTimelineId;
|
private Guid _mainTimelineId;
|
||||||
private Guid _detailTimelineId;
|
private Guid _detailTimelineId;
|
||||||
@@ -157,29 +149,6 @@ namespace GitHub.Runner.Worker
|
|||||||
private long _totalThrottlingDelayInMilliseconds = 0;
|
private long _totalThrottlingDelayInMilliseconds = 0;
|
||||||
private bool _stepTelemetryPublished = false;
|
private bool _stepTelemetryPublished = false;
|
||||||
|
|
||||||
public ExecutionContext()
|
|
||||||
: this(parent: null, embedded: false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExecutionContext(ExecutionContext parent, bool embedded)
|
|
||||||
{
|
|
||||||
if (embedded)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(parent, nameof(parent));
|
|
||||||
}
|
|
||||||
|
|
||||||
_parentExecutionContext = parent;
|
|
||||||
this.IsEmbedded = embedded;
|
|
||||||
this.StepTelemetry = new ActionsStepTelemetry
|
|
||||||
{
|
|
||||||
IsEmbedded = embedded
|
|
||||||
};
|
|
||||||
|
|
||||||
//Embedded Execution Contexts pseudo-inherit their parent's embeddedIssueCollector.
|
|
||||||
_embeddedIssueCollector = embedded ? parent._embeddedIssueCollector : new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Guid Id => _record.Id;
|
public Guid Id => _record.Id;
|
||||||
public Guid EmbeddedId { get; private set; }
|
public Guid EmbeddedId { get; private set; }
|
||||||
public string ScopeName { get; private set; }
|
public string ScopeName { get; private set; }
|
||||||
@@ -192,7 +161,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
||||||
|
|
||||||
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
|
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
|
||||||
public ActionsStepTelemetry StepTelemetry { get; private init; }
|
public ActionsStepTelemetry StepTelemetry { get; } = new ActionsStepTelemetry();
|
||||||
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
||||||
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
||||||
|
|
||||||
@@ -217,7 +186,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// An embedded execution context shares the same record ID, record name, and logger
|
// An embedded execution context shares the same record ID, record name, and logger
|
||||||
// as its enclosing execution context.
|
// as its enclosing execution context.
|
||||||
public bool IsEmbedded { get; private init; }
|
public bool IsEmbedded { get; private set; }
|
||||||
|
|
||||||
public TaskResult? Result
|
public TaskResult? Result
|
||||||
{
|
{
|
||||||
@@ -352,7 +321,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
var child = new ExecutionContext(this, isEmbedded);
|
var child = new ExecutionContext();
|
||||||
child.Initialize(HostContext);
|
child.Initialize(HostContext);
|
||||||
child.Global = Global;
|
child.Global = Global;
|
||||||
child.ScopeName = scopeName;
|
child.ScopeName = scopeName;
|
||||||
@@ -377,6 +346,7 @@ namespace GitHub.Runner.Worker
|
|||||||
child.ExpressionFunctions.Add(item);
|
child.ExpressionFunctions.Add(item);
|
||||||
}
|
}
|
||||||
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
|
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
|
||||||
|
child._parentExecutionContext = this;
|
||||||
child.EchoOnActionCommand = EchoOnActionCommand;
|
child.EchoOnActionCommand = EchoOnActionCommand;
|
||||||
|
|
||||||
if (recordOrder != null)
|
if (recordOrder != null)
|
||||||
@@ -397,9 +367,11 @@ namespace GitHub.Runner.Worker
|
|||||||
child._logger.Setup(_mainTimelineId, recordId);
|
child._logger.Setup(_mainTimelineId, recordId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
child.IsEmbedded = isEmbedded;
|
||||||
child.StepTelemetry.StepId = recordId;
|
child.StepTelemetry.StepId = recordId;
|
||||||
child.StepTelemetry.Stage = stage.ToString();
|
child.StepTelemetry.Stage = stage.ToString();
|
||||||
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName();
|
child.StepTelemetry.IsEmbedded = isEmbedded;
|
||||||
|
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName(); ;
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
@@ -441,24 +413,13 @@ namespace GitHub.Runner.Worker
|
|||||||
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
|
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime now = DateTime.UtcNow;
|
|
||||||
_record.CurrentOperation = currentOperation ?? _record.CurrentOperation;
|
_record.CurrentOperation = currentOperation ?? _record.CurrentOperation;
|
||||||
_record.ResultCode = resultCode ?? _record.ResultCode;
|
_record.ResultCode = resultCode ?? _record.ResultCode;
|
||||||
_record.FinishTime = now;
|
_record.FinishTime = DateTime.UtcNow;
|
||||||
_record.PercentComplete = 100;
|
_record.PercentComplete = 100;
|
||||||
_record.Result = _record.Result ?? TaskResult.Succeeded;
|
_record.Result = _record.Result ?? TaskResult.Succeeded;
|
||||||
_record.State = TimelineRecordState.Completed;
|
_record.State = TimelineRecordState.Completed;
|
||||||
|
|
||||||
// Before our main timeline's final QueueTimelineRecordUpdate,
|
|
||||||
// inject any issues collected by embedded ExecutionContexts.
|
|
||||||
if (!this.IsEmbedded)
|
|
||||||
{
|
|
||||||
foreach (var issue in _embeddedIssueCollector)
|
|
||||||
{
|
|
||||||
AddIssue(issue, ExecutionContextLogOptions.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
||||||
|
|
||||||
// complete all detail timeline records.
|
// complete all detail timeline records.
|
||||||
@@ -466,7 +427,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
foreach (var record in _detailRecords)
|
foreach (var record in _detailRecords)
|
||||||
{
|
{
|
||||||
record.Value.FinishTime = record.Value.FinishTime ?? now;
|
record.Value.FinishTime = record.Value.FinishTime ?? DateTime.UtcNow;
|
||||||
record.Value.PercentComplete = record.Value.PercentComplete ?? 100;
|
record.Value.PercentComplete = record.Value.PercentComplete ?? 100;
|
||||||
record.Value.Result = record.Value.Result ?? TaskResult.Succeeded;
|
record.Value.Result = record.Value.Result ?? TaskResult.Succeeded;
|
||||||
record.Value.State = TimelineRecordState.Completed;
|
record.Value.State = TimelineRecordState.Completed;
|
||||||
@@ -477,26 +438,14 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
PublishStepTelemetry();
|
PublishStepTelemetry();
|
||||||
|
|
||||||
var stepResult = new StepResult
|
var stepResult = new StepResult();
|
||||||
{
|
stepResult.ExternalID = _record.Id;
|
||||||
ExternalID = _record.Id,
|
stepResult.Conclusion = _record.Result ?? TaskResult.Succeeded;
|
||||||
Conclusion = _record.Result ?? TaskResult.Succeeded,
|
stepResult.Status = _record.State;
|
||||||
Status = _record.State,
|
stepResult.Number = _record.Order;
|
||||||
Number = _record.Order,
|
stepResult.Name = _record.Name;
|
||||||
Name = _record.Name,
|
stepResult.StartedAt = _record.StartTime;
|
||||||
StartedAt = _record.StartTime,
|
stepResult.CompletedAt = _record.FinishTime;
|
||||||
CompletedAt = _record.FinishTime,
|
|
||||||
Annotations = new List<Annotation>()
|
|
||||||
};
|
|
||||||
|
|
||||||
_record.Issues?.ForEach(issue =>
|
|
||||||
{
|
|
||||||
var annotation = issue.ToAnnotation();
|
|
||||||
if (annotation != null)
|
|
||||||
{
|
|
||||||
stepResult.Annotations.Add(annotation.Value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Global.StepsResult.Add(stepResult);
|
Global.StepsResult.Add(stepResult);
|
||||||
|
|
||||||
@@ -610,10 +559,14 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is not thread safe, the caller need to take lock before calling issue()
|
// This is not thread safe, the caller need to take lock before calling issue()
|
||||||
public void AddIssue(Issue issue, ExecutionContextLogOptions logOptions)
|
public void AddIssue(Issue issue, string logMessage = null)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(issue, nameof(issue));
|
ArgUtil.NotNull(issue, nameof(issue));
|
||||||
ArgUtil.NotNull(logOptions, nameof(logOptions));
|
|
||||||
|
if (string.IsNullOrEmpty(logMessage))
|
||||||
|
{
|
||||||
|
logMessage = issue.Message;
|
||||||
|
}
|
||||||
|
|
||||||
issue.Message = HostContext.SecretMasker.MaskSecrets(issue.Message);
|
issue.Message = HostContext.SecretMasker.MaskSecrets(issue.Message);
|
||||||
if (issue.Message.Length > _maxIssueMessageLength)
|
if (issue.Message.Length > _maxIssueMessageLength)
|
||||||
@@ -628,64 +581,53 @@ namespace GitHub.Runner.Worker
|
|||||||
issue.Data["stepNumber"] = _record.Order.ToString();
|
issue.Data["stepNumber"] = _record.Order.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
string wellKnownTag = null;
|
if (issue.Type == IssueType.Error)
|
||||||
Int32 previousCountForIssueType = 0;
|
|
||||||
Action incrementIssueTypeCount = NoOp;
|
|
||||||
switch (issue.Type)
|
|
||||||
{
|
{
|
||||||
case IssueType.Error:
|
if (!string.IsNullOrEmpty(logMessage))
|
||||||
wellKnownTag = WellKnownTags.Error;
|
{
|
||||||
previousCountForIssueType = _record.ErrorCount;
|
long logLineNumber = Write(WellKnownTags.Error, logMessage);
|
||||||
incrementIssueTypeCount = () => { _record.ErrorCount++; };
|
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
||||||
break;
|
}
|
||||||
case IssueType.Warning:
|
|
||||||
wellKnownTag = WellKnownTags.Warning;
|
if (_record.ErrorCount < _maxIssueCount)
|
||||||
previousCountForIssueType = _record.WarningCount;
|
|
||||||
incrementIssueTypeCount = () => { _record.WarningCount++; };
|
|
||||||
break;
|
|
||||||
case IssueType.Notice:
|
|
||||||
wellKnownTag = WellKnownTags.Notice;
|
|
||||||
previousCountForIssueType = _record.NoticeCount;
|
|
||||||
incrementIssueTypeCount = () => { _record.NoticeCount++; };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(wellKnownTag))
|
|
||||||
{
|
|
||||||
if (!this.IsEmbedded && previousCountForIssueType < _maxCountPerIssueType)
|
|
||||||
{
|
{
|
||||||
incrementIssueTypeCount();
|
|
||||||
_record.Issues.Add(issue);
|
_record.Issues.Add(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logOptions.WriteToLog)
|
_record.ErrorCount++;
|
||||||
|
}
|
||||||
|
else if (issue.Type == IssueType.Warning)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(logMessage))
|
||||||
{
|
{
|
||||||
string logMessage = issue.Message;
|
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
|
||||||
if (!string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
||||||
{
|
|
||||||
logMessage = logOptions.LogMessageOverride;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(logMessage))
|
|
||||||
{
|
|
||||||
// Note that ::Write() has its own secret-masking logic.
|
|
||||||
long logLineNumber = Write(wellKnownTag, logMessage);
|
|
||||||
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_record.WarningCount < _maxIssueCount)
|
||||||
|
{
|
||||||
|
_record.Issues.Add(issue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_record.WarningCount++;
|
||||||
|
}
|
||||||
|
else if (issue.Type == IssueType.Notice)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(logMessage))
|
||||||
|
{
|
||||||
|
long logLineNumber = Write(WellKnownTags.Notice, logMessage);
|
||||||
|
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_record.NoticeCount < _maxIssueCount)
|
||||||
|
{
|
||||||
|
_record.Issues.Add(issue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_record.NoticeCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embedded ExecutionContexts (a.k.a. Composite actions) should never upload a timeline record to the server.
|
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
||||||
// Instead, we store processed issues on a shared (psuedo-inherited) list (belonging to the closest
|
|
||||||
// non-embedded ancestor ExecutionContext) so that they can be processed when that ancestor completes.
|
|
||||||
if (this.IsEmbedded)
|
|
||||||
{
|
|
||||||
_embeddedIssueCollector.Add(issue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateDetailTimelineRecord(TimelineRecord record)
|
public void UpdateDetailTimelineRecord(TimelineRecord record)
|
||||||
@@ -783,9 +725,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// Steps results for entire job
|
// Steps results for entire job
|
||||||
Global.StepsResult = new List<StepResult>();
|
Global.StepsResult = new List<StepResult>();
|
||||||
|
|
||||||
// Job level annotations
|
|
||||||
Global.JobAnnotations = new List<Annotation>();
|
|
||||||
|
|
||||||
// Job Outputs
|
// Job Outputs
|
||||||
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -802,9 +741,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// File table
|
// File table
|
||||||
Global.FileTable = new List<String>(message.FileTable ?? new string[0]);
|
Global.FileTable = new List<String>(message.FileTable ?? new string[0]);
|
||||||
|
|
||||||
// What type of job request is running (i.e. Run Service vs. pipelines)
|
|
||||||
Global.Variables.Set(Constants.Variables.System.JobRequestType, message.MessageType);
|
|
||||||
|
|
||||||
// Expression values
|
// Expression values
|
||||||
if (message.ContextData?.Count > 0)
|
if (message.ContextData?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -1059,7 +995,8 @@ namespace GitHub.Runner.Worker
|
|||||||
StepTelemetry.FinishTime = _record.FinishTime;
|
StepTelemetry.FinishTime = _record.FinishTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsEmbedded)
|
if (!IsEmbedded &&
|
||||||
|
_record.Issues.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var issue in _record.Issues)
|
foreach (var issue in _record.Issues)
|
||||||
{
|
{
|
||||||
@@ -1225,11 +1162,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
UpdateGlobalStepsContext();
|
UpdateGlobalStepsContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void NoOp()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
|
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
|
||||||
@@ -1254,22 +1186,19 @@ namespace GitHub.Runner.Worker
|
|||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void Error(this IExecutionContext context, string message)
|
public static void Error(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
var issue = new Issue() { Type = IssueType.Error, Message = message };
|
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void InfrastructureError(this IExecutionContext context, string message)
|
public static void InfrastructureError(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
var issue = new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true };
|
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true });
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void Warning(this IExecutionContext context, string message)
|
public static void Warning(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
var issue = new Issue() { Type = IssueType.Warning, Message = message };
|
context.AddIssue(new Issue() { Type = IssueType.Warning, Message = message });
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GitHub.Actions.RunService.WebApi;
|
using GitHub.Actions.RunService.WebApi;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -19,7 +18,6 @@ namespace GitHub.Runner.Worker
|
|||||||
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; set; }
|
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; set; }
|
||||||
public List<ActionsStepTelemetry> StepsTelemetry { get; set; }
|
public List<ActionsStepTelemetry> StepsTelemetry { get; set; }
|
||||||
public List<StepResult> StepsResult { get; set; }
|
public List<StepResult> StepsResult { get; set; }
|
||||||
public List<Annotation> JobAnnotations { get; set; }
|
|
||||||
public List<JobTelemetry> JobTelemetry { get; set; }
|
public List<JobTelemetry> JobTelemetry { get; set; }
|
||||||
public TaskOrchestrationPlanReference Plan { get; set; }
|
public TaskOrchestrationPlanReference Plan { get; set; }
|
||||||
public List<string> PrependPath { get; set; }
|
public List<string> PrependPath { get; set; }
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
// Evaluation error
|
// Evaluation error
|
||||||
Trace.Info("Caught exception from expression for embedded step.env");
|
Trace.Info("Caught exception from expression for embedded step.env");
|
||||||
step.ExecutionContext.Error(ex);
|
step.ExecutionContext.Error(ex);
|
||||||
SetStepConclusion(step, TaskResult.Failed);
|
step.ExecutionContext.Complete(TaskResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Callback
|
// Register Callback
|
||||||
|
|||||||
@@ -143,8 +143,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
if (issue != null)
|
if (issue != null)
|
||||||
{
|
{
|
||||||
// Log issue
|
// Log issue
|
||||||
var logOptions = new ExecutionContextLogOptions(true, stripped);
|
_executionContext.AddIssue(issue, stripped);
|
||||||
_executionContext.AddIssue(issue, logOptions);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,6 +431,14 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Result = TaskResult.Canceled;
|
context.Result = TaskResult.Canceled;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
catch (FailedToResolveActionDownloadInfoException ex)
|
||||||
|
{
|
||||||
|
// Log the error and fail the JobExtension Initialization.
|
||||||
|
Trace.Error($"Caught exception from JobExtenion Initialization: {ex}");
|
||||||
|
context.InfrastructureError(ex.Message);
|
||||||
|
context.Result = TaskResult.Failed;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Log the error and fail the JobExtension Initialization.
|
// Log the error and fail the JobExtension Initialization.
|
||||||
@@ -675,7 +683,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
|
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
|
||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -43,13 +42,6 @@ namespace GitHub.Runner.Worker
|
|||||||
DateTime jobStartTimeUtc = DateTime.UtcNow;
|
DateTime jobStartTimeUtc = DateTime.UtcNow;
|
||||||
IRunnerService server = null;
|
IRunnerService server = null;
|
||||||
|
|
||||||
// add orchestration id to useragent for better correlation.
|
|
||||||
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
|
||||||
!string.IsNullOrEmpty(orchestrationId.Value))
|
|
||||||
{
|
|
||||||
HostContext.UserAgents.Add(new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||||
{
|
{
|
||||||
@@ -57,21 +49,6 @@ namespace GitHub.Runner.Worker
|
|||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
await runServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
await runServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
||||||
server = runServer;
|
server = runServer;
|
||||||
|
|
||||||
message.Variables.TryGetValue("system.github.launch_endpoint", out VariableValue launchEndpointVariable);
|
|
||||||
var launchReceiverEndpoint = launchEndpointVariable?.Value;
|
|
||||||
|
|
||||||
if (systemConnection?.Authorization != null &&
|
|
||||||
systemConnection.Authorization.Parameters.TryGetValue("AccessToken", out var accessToken) &&
|
|
||||||
!string.IsNullOrEmpty(accessToken) &&
|
|
||||||
!string.IsNullOrEmpty(launchReceiverEndpoint))
|
|
||||||
{
|
|
||||||
Trace.Info("Initializing launch client");
|
|
||||||
var launchServer = HostContext.GetService<ILaunchServer>();
|
|
||||||
launchServer.InitializeLaunchClient(new Uri(launchReceiverEndpoint), accessToken);
|
|
||||||
}
|
|
||||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
|
||||||
_jobServerQueue.Start(message, resultServiceOnly: true);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -120,8 +97,7 @@ namespace GitHub.Runner.Worker
|
|||||||
default:
|
default:
|
||||||
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
|
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
|
||||||
}
|
}
|
||||||
var issue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage });
|
||||||
jobContext.AddIssue(issue, ExecutionContextLogOptions.Default);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate directory permissions.
|
// Validate directory permissions.
|
||||||
@@ -285,7 +261,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, default);
|
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, default);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions2.Sdk
|
namespace GitHub.DistributedTask.Expressions2.Sdk
|
||||||
@@ -55,7 +55,7 @@ namespace GitHub.DistributedTask.Expressions2.Sdk
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Evaluate
|
// Evaluate
|
||||||
secretMasker = secretMasker?.Clone() ?? new SecretMasker();
|
secretMasker = secretMasker?.Clone() ?? new SecretMasker(Enumerable.Empty<ValueEncoder>());
|
||||||
trace = new EvaluationTraceWriter(trace, secretMasker);
|
trace = new EvaluationTraceWriter(trace, secretMasker);
|
||||||
var context = new EvaluationContext(trace, secretMasker, state, options, this);
|
var context = new EvaluationContext(trace, secretMasker, state, options, this);
|
||||||
trace.Info($"Evaluating: {ConvertToExpression()}");
|
trace.Info($"Evaluating: {ConvertToExpression()}");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Logging
|
namespace GitHub.DistributedTask.Logging
|
||||||
@@ -8,7 +8,6 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
{
|
{
|
||||||
void AddRegex(String pattern);
|
void AddRegex(String pattern);
|
||||||
void AddValue(String value);
|
void AddValue(String value);
|
||||||
void AddValueEncoder(ValueEncoder encoder);
|
|
||||||
ISecretMasker Clone();
|
ISecretMasker Clone();
|
||||||
String MaskSecrets(String input);
|
String MaskSecrets(String input);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -10,11 +10,11 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public sealed class SecretMasker : ISecretMasker, IDisposable
|
public sealed class SecretMasker : ISecretMasker, IDisposable
|
||||||
{
|
{
|
||||||
public SecretMasker()
|
public SecretMasker(IEnumerable<ValueEncoder> encoders)
|
||||||
{
|
{
|
||||||
m_originalValueSecrets = new HashSet<ValueSecret>();
|
m_originalValueSecrets = new HashSet<ValueSecret>();
|
||||||
m_regexSecrets = new HashSet<RegexSecret>();
|
m_regexSecrets = new HashSet<RegexSecret>();
|
||||||
m_valueEncoders = new HashSet<ValueEncoder>();
|
m_valueEncoders = new HashSet<ValueEncoder>(encoders ?? Enumerable.Empty<ValueEncoder>());
|
||||||
m_valueSecrets = new HashSet<ValueSecret>();
|
m_valueSecrets = new HashSet<ValueSecret>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,15 +104,11 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the encoded values.
|
var secretVariations = valueEncoders.SelectMany(encoder => encoder(value))
|
||||||
foreach (ValueEncoder valueEncoder in valueEncoders)
|
.Where(variation => !string.IsNullOrEmpty(variation))
|
||||||
{
|
.Distinct()
|
||||||
String encodedValue = valueEncoder(value);
|
.Select(variation => new ValueSecret(variation));
|
||||||
if (!String.IsNullOrEmpty(encodedValue))
|
valueSecrets.AddRange(secretVariations);
|
||||||
{
|
|
||||||
valueSecrets.Add(new ValueSecret(encodedValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write section.
|
// Write section.
|
||||||
try
|
try
|
||||||
@@ -135,69 +131,6 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This implementation assumes no more than one thread is adding regexes, values, or encoders at any given time.
|
|
||||||
/// </summary>
|
|
||||||
public void AddValueEncoder(ValueEncoder encoder)
|
|
||||||
{
|
|
||||||
ValueSecret[] originalSecrets;
|
|
||||||
|
|
||||||
// Read section.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
m_lock.EnterReadLock();
|
|
||||||
|
|
||||||
// Test whether already added.
|
|
||||||
if (m_valueEncoders.Contains(encoder))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the original value secrets.
|
|
||||||
originalSecrets = m_originalValueSecrets.ToArray();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (m_lock.IsReadLockHeld)
|
|
||||||
{
|
|
||||||
m_lock.ExitReadLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the encoded values.
|
|
||||||
var encodedSecrets = new List<ValueSecret>();
|
|
||||||
foreach (ValueSecret originalSecret in originalSecrets)
|
|
||||||
{
|
|
||||||
String encodedValue = encoder(originalSecret.m_value);
|
|
||||||
if (!String.IsNullOrEmpty(encodedValue))
|
|
||||||
{
|
|
||||||
encodedSecrets.Add(new ValueSecret(encodedValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write section.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
m_lock.EnterWriteLock();
|
|
||||||
|
|
||||||
// Add the encoder.
|
|
||||||
m_valueEncoders.Add(encoder);
|
|
||||||
|
|
||||||
// Add the values.
|
|
||||||
foreach (ValueSecret encodedSecret in encodedSecrets)
|
|
||||||
{
|
|
||||||
m_valueSecrets.Add(encodedSecret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (m_lock.IsWriteLockHeld)
|
|
||||||
{
|
|
||||||
m_lock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISecretMasker Clone() => new SecretMasker(this);
|
public ISecretMasker Clone() => new SecretMasker(this);
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -1,73 +1,97 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Logging
|
namespace GitHub.DistributedTask.Logging
|
||||||
{
|
{
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public delegate String ValueEncoder(String value);
|
public delegate IEnumerable<string> ValueEncoder(string value);
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public static class ValueEncoders
|
public static class ValueEncoders
|
||||||
{
|
{
|
||||||
public static String Base64StringEscape(String value)
|
|
||||||
{
|
|
||||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base64 is 6 bits -> char
|
public static IEnumerable<string> EnumerateBase64Variations(string value)
|
||||||
// A byte is 8 bits
|
|
||||||
// When end user doing somthing like base64(user:password)
|
|
||||||
// The length of the leading content will cause different base64 encoding result on the password
|
|
||||||
// So we add base64(value shifted 1 and two bytes) as secret as well.
|
|
||||||
// B1 B2 B3 B4 B5 B6 B7
|
|
||||||
// 000000|00 0000|0000 00|000000| 000000|00 0000|0000 00|000000|
|
|
||||||
// Char1 Char2 Char3 Char4
|
|
||||||
// See the above, the first byte has a character beginning at index 0, the second byte has a character beginning at index 4, the third byte has a character beginning at index 2 and then the pattern repeats
|
|
||||||
// We register byte offsets for all these possible values
|
|
||||||
public static String Base64StringEscapeShift1(String value)
|
|
||||||
{
|
{
|
||||||
return Base64StringEscapeShift(value, 1);
|
if (!string.IsNullOrEmpty(value))
|
||||||
}
|
{
|
||||||
|
// A byte is 8 bits. A Base64 "digit" can hold a maximum of 6 bits (2^64 - 1, or values 0 to 63).
|
||||||
|
// As a result, many Unicode characters (including single-byte letters) cannot be represented using a single Base64 digit.
|
||||||
|
// Furthermore, on average a Base64 string will be about 33% longer than the original text.
|
||||||
|
// This is because it generally requires 4 Base64 digits to represent 3 Unicode bytes. (4 / 3 ~ 1.33)
|
||||||
|
//
|
||||||
|
// Because of this 4:3 ratio (or, more precisely, 8 bits : 6 bits ratio), there's a cyclical pattern
|
||||||
|
// to when a byte boundary aligns with a Base64 digit boundary.
|
||||||
|
// The pattern repeats every 24 bits (the lowest common multiple of 8 and 6).
|
||||||
|
//
|
||||||
|
// |-----------24 bits-------------|-----------24 bits------------|
|
||||||
|
// Base64 Digits: |digit 0|digit 1|digit 2|digit 3|digit 4|digit 5|digit 6|digit7|
|
||||||
|
// Allocated Bits: aaaaaa aaBBBB BBBBcc cccccc DDDDDD DDeeee eeeeFF FFFFFF
|
||||||
|
// Unicode chars: |0th char |1st char |2nd char |3rd char |4th char |5th char |
|
||||||
|
|
||||||
public static String Base64StringEscapeShift2(String value)
|
// Depending on alignment, the Base64-encoded secret can take any of 3 basic forms.
|
||||||
{
|
// For example, the Base64 digits representing "abc" could appear as any of the following:
|
||||||
return Base64StringEscapeShift(value, 2);
|
// "YWJj" when aligned
|
||||||
|
// ".!FiYw==" when preceded by 3x + 1 bytes
|
||||||
|
// "..!hYmM=" when preceded by 3x + 2 bytes
|
||||||
|
// (where . represents an unrelated Base64 digit, ! represents a Base64 digit that should be masked, and x represents any non-negative integer)
|
||||||
|
|
||||||
|
var rawBytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
|
||||||
|
for (var offset = 0; offset <= 2; offset++)
|
||||||
|
{
|
||||||
|
var prunedBytes = rawBytes.Skip(offset).ToArray();
|
||||||
|
if (prunedBytes.Length > 0)
|
||||||
|
{
|
||||||
|
// Don't include Base64 padding characters (=) in Base64 representations of the secret.
|
||||||
|
// They don't represent anything interesting, so they don't need to be masked.
|
||||||
|
// (Some clients omit the padding, so we want to be sure we recognize the secret regardless of whether the padding is present or not.)
|
||||||
|
var buffer = new StringBuilder(Convert.ToBase64String(prunedBytes).TrimEnd(BASE64_PADDING_SUFFIX));
|
||||||
|
yield return buffer.ToString();
|
||||||
|
|
||||||
|
// Also, yield the RFC4648-equivalent RegEx.
|
||||||
|
buffer.Replace('+', '-');
|
||||||
|
buffer.Replace('/', '_');
|
||||||
|
yield return buffer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used when we pass environment variables to docker to escape " with \"
|
// Used when we pass environment variables to docker to escape " with \"
|
||||||
public static String CommandLineArgumentEscape(String value)
|
public static IEnumerable<string> CommandLineArgumentEscape(string value)
|
||||||
{
|
{
|
||||||
return value.Replace("\"", "\\\"");
|
yield return value.Replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String ExpressionStringEscape(String value)
|
public static IEnumerable<string> ExpressionStringEscape(string value)
|
||||||
{
|
{
|
||||||
return Expressions2.Sdk.ExpressionUtility.StringEscape(value);
|
yield return Expressions2.Sdk.ExpressionUtility.StringEscape(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String JsonStringEscape(String value)
|
public static IEnumerable<string> JsonStringEscape(string value)
|
||||||
{
|
{
|
||||||
// Convert to a JSON string and then remove the leading/trailing double-quote.
|
// Convert to a JSON string and then remove the leading/trailing double-quote.
|
||||||
String jsonString = JsonConvert.ToString(value);
|
String jsonString = JsonConvert.ToString(value);
|
||||||
String jsonEscapedValue = jsonString.Substring(startIndex: 1, length: jsonString.Length - 2);
|
String jsonEscapedValue = jsonString.Substring(startIndex: 1, length: jsonString.Length - 2);
|
||||||
return jsonEscapedValue;
|
yield return jsonEscapedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String UriDataEscape(String value)
|
public static IEnumerable<string> UriDataEscape(string value)
|
||||||
{
|
{
|
||||||
return UriDataEscape(value, 65519);
|
yield return UriDataEscape(value, 65519);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String XmlDataEscape(String value)
|
public static IEnumerable<string> XmlDataEscape(string value)
|
||||||
{
|
{
|
||||||
return SecurityElement.Escape(value);
|
yield return SecurityElement.Escape(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String TrimDoubleQuotes(String value)
|
public static IEnumerable<string> TrimDoubleQuotes(string value)
|
||||||
{
|
{
|
||||||
var trimmed = string.Empty;
|
var trimmed = string.Empty;
|
||||||
if (!string.IsNullOrEmpty(value) &&
|
if (!string.IsNullOrEmpty(value) &&
|
||||||
@@ -78,17 +102,17 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
trimmed = value.Substring(1, value.Length - 2);
|
trimmed = value.Substring(1, value.Length - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimmed;
|
yield return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String PowerShellPreAmpersandEscape(String value)
|
public static IEnumerable<string> PowerShellPreAmpersandEscape(string value)
|
||||||
{
|
{
|
||||||
// if the secret is passed to PS as a command and it causes an error, sections of it can be surrounded by color codes
|
// if the secret is passed to PS as a command and it causes an error, sections of it can be surrounded by color codes
|
||||||
// or printed individually.
|
// or printed individually.
|
||||||
|
|
||||||
// The secret secretpart1&secretpart2&secretpart3 would be split into 2 sections:
|
// The secret secretpart1&secretpart2&secretpart3 would be split into 2 sections:
|
||||||
// 'secretpart1&secretpart2&' and 'secretpart3'. This method masks for the first section.
|
// 'secretpart1&secretpart2&' and 'secretpart3'. This method masks for the first section.
|
||||||
|
|
||||||
// The secret secretpart1&+secretpart2&secretpart3 would be split into 2 sections:
|
// The secret secretpart1&+secretpart2&secretpart3 would be split into 2 sections:
|
||||||
// 'secretpart1&+' and (no 's') 'ecretpart2&secretpart3'. This method masks for the first section.
|
// 'secretpart1&+' and (no 's') 'ecretpart2&secretpart3'. This method masks for the first section.
|
||||||
|
|
||||||
@@ -112,10 +136,10 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimmed;
|
yield return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String PowerShellPostAmpersandEscape(String value)
|
public static IEnumerable<string> PowerShellPostAmpersandEscape(string value)
|
||||||
{
|
{
|
||||||
var trimmed = string.Empty;
|
var trimmed = string.Empty;
|
||||||
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
||||||
@@ -137,27 +161,10 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimmed;
|
yield return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Base64StringEscapeShift(String value, int shift)
|
private static string UriDataEscape(string value, Int32 maxSegmentSize)
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(value);
|
|
||||||
if (bytes.Length > shift)
|
|
||||||
{
|
|
||||||
var shiftArray = new byte[bytes.Length - shift];
|
|
||||||
Array.Copy(bytes, shift, shiftArray, 0, bytes.Length - shift);
|
|
||||||
return Convert.ToBase64String(shiftArray);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Convert.ToBase64String(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String UriDataEscape(
|
|
||||||
String value,
|
|
||||||
Int32 maxSegmentSize)
|
|
||||||
{
|
{
|
||||||
if (value.Length <= maxSegmentSize)
|
if (value.Length <= maxSegmentSize)
|
||||||
{
|
{
|
||||||
@@ -183,5 +190,7 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
|
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const char BASE64_PADDING_SUFFIX = '=';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -163,6 +163,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
message = $"{prefix} {message}";
|
message = $"{prefix} {message}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Errors.Add(message);
|
||||||
TraceWriter.Error(message);
|
TraceWriter.Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
|
|
||||||
@@ -1519,26 +1519,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationJobAlreadyAcquiredException", "GitHub.DistributedTask.WebApi.TaskOrchestrationJobAlreadyAcquiredException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
|
||||||
public sealed class TaskOrchestrationJobAlreadyAcquiredException : DistributedTaskException
|
|
||||||
{
|
|
||||||
public TaskOrchestrationJobAlreadyAcquiredException(String message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskOrchestrationJobAlreadyAcquiredException(String message, Exception innerException)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private TaskOrchestrationJobAlreadyAcquiredException(SerializationInfo info, StreamingContext context)
|
|
||||||
: base(info, context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationPlanSecurityException", "GitHub.DistributedTask.WebApi.TaskOrchestrationPlanSecurityException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationPlanSecurityException", "GitHub.DistributedTask.WebApi.TaskOrchestrationPlanSecurityException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||||
public sealed class TaskOrchestrationPlanSecurityException : DistributedTaskException
|
public sealed class TaskOrchestrationPlanSecurityException : DistributedTaskException
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
{
|
{
|
||||||
@@ -9,78 +10,69 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public sealed class TimelineRecord
|
public sealed class TimelineRecord
|
||||||
{
|
{
|
||||||
public TimelineRecord()
|
public TimelineRecord()
|
||||||
: this(null)
|
|
||||||
{
|
{
|
||||||
|
this.Attempt = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimelineRecord(TimelineRecord recordToBeCloned)
|
private TimelineRecord(TimelineRecord recordToBeCloned)
|
||||||
{
|
{
|
||||||
this.EnsureInitialized();
|
this.Attempt = recordToBeCloned.Attempt;
|
||||||
|
this.ChangeId = recordToBeCloned.ChangeId;
|
||||||
|
this.CurrentOperation = recordToBeCloned.CurrentOperation;
|
||||||
|
this.FinishTime = recordToBeCloned.FinishTime;
|
||||||
|
this.Id = recordToBeCloned.Id;
|
||||||
|
this.Identifier = recordToBeCloned.Identifier;
|
||||||
|
this.LastModified = recordToBeCloned.LastModified;
|
||||||
|
this.Location = recordToBeCloned.Location;
|
||||||
|
this.Name = recordToBeCloned.Name;
|
||||||
|
this.Order = recordToBeCloned.Order;
|
||||||
|
this.ParentId = recordToBeCloned.ParentId;
|
||||||
|
this.PercentComplete = recordToBeCloned.PercentComplete;
|
||||||
|
this.RecordType = recordToBeCloned.RecordType;
|
||||||
|
this.Result = recordToBeCloned.Result;
|
||||||
|
this.ResultCode = recordToBeCloned.ResultCode;
|
||||||
|
this.StartTime = recordToBeCloned.StartTime;
|
||||||
|
this.State = recordToBeCloned.State;
|
||||||
|
this.TimelineId = recordToBeCloned.TimelineId;
|
||||||
|
this.WorkerName = recordToBeCloned.WorkerName;
|
||||||
|
this.RefName = recordToBeCloned.RefName;
|
||||||
|
this.ErrorCount = recordToBeCloned.ErrorCount;
|
||||||
|
this.WarningCount = recordToBeCloned.WarningCount;
|
||||||
|
this.NoticeCount = recordToBeCloned.NoticeCount;
|
||||||
|
this.AgentPlatform = recordToBeCloned.AgentPlatform;
|
||||||
|
|
||||||
if (recordToBeCloned != null)
|
if (recordToBeCloned.Log != null)
|
||||||
{
|
{
|
||||||
this.Attempt = recordToBeCloned.Attempt;
|
this.Log = new TaskLogReference
|
||||||
this.ChangeId = recordToBeCloned.ChangeId;
|
|
||||||
this.CurrentOperation = recordToBeCloned.CurrentOperation;
|
|
||||||
this.FinishTime = recordToBeCloned.FinishTime;
|
|
||||||
this.Id = recordToBeCloned.Id;
|
|
||||||
this.Identifier = recordToBeCloned.Identifier;
|
|
||||||
this.LastModified = recordToBeCloned.LastModified;
|
|
||||||
this.Location = recordToBeCloned.Location;
|
|
||||||
this.Name = recordToBeCloned.Name;
|
|
||||||
this.Order = recordToBeCloned.Order;
|
|
||||||
this.ParentId = recordToBeCloned.ParentId;
|
|
||||||
this.PercentComplete = recordToBeCloned.PercentComplete;
|
|
||||||
this.RecordType = recordToBeCloned.RecordType;
|
|
||||||
this.Result = recordToBeCloned.Result;
|
|
||||||
this.ResultCode = recordToBeCloned.ResultCode;
|
|
||||||
this.StartTime = recordToBeCloned.StartTime;
|
|
||||||
this.State = recordToBeCloned.State;
|
|
||||||
this.TimelineId = recordToBeCloned.TimelineId;
|
|
||||||
this.WorkerName = recordToBeCloned.WorkerName;
|
|
||||||
this.RefName = recordToBeCloned.RefName;
|
|
||||||
this.ErrorCount = recordToBeCloned.ErrorCount;
|
|
||||||
this.WarningCount = recordToBeCloned.WarningCount;
|
|
||||||
this.NoticeCount = recordToBeCloned.NoticeCount;
|
|
||||||
this.AgentPlatform = recordToBeCloned.AgentPlatform;
|
|
||||||
|
|
||||||
if (recordToBeCloned.Log != null)
|
|
||||||
{
|
{
|
||||||
this.Log = new TaskLogReference
|
Id = recordToBeCloned.Log.Id,
|
||||||
{
|
Location = recordToBeCloned.Log.Location,
|
||||||
Id = recordToBeCloned.Log.Id,
|
};
|
||||||
Location = recordToBeCloned.Log.Location,
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recordToBeCloned.Details != null)
|
if (recordToBeCloned.Details != null)
|
||||||
|
{
|
||||||
|
this.Details = new TimelineReference
|
||||||
{
|
{
|
||||||
this.Details = new TimelineReference
|
ChangeId = recordToBeCloned.Details.ChangeId,
|
||||||
{
|
Id = recordToBeCloned.Details.Id,
|
||||||
ChangeId = recordToBeCloned.Details.ChangeId,
|
Location = recordToBeCloned.Details.Location,
|
||||||
Id = recordToBeCloned.Details.Id,
|
};
|
||||||
Location = recordToBeCloned.Details.Location,
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recordToBeCloned.m_issues?.Count > 0)
|
if (recordToBeCloned.m_issues?.Count> 0)
|
||||||
{
|
{
|
||||||
this.Issues.AddRange(recordToBeCloned.Issues.Select(i => i.Clone()));
|
this.Issues.AddRange(recordToBeCloned.Issues.Select(i => i.Clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.m_previousAttempts?.Count > 0)
|
if (recordToBeCloned.m_previousAttempts?.Count > 0)
|
||||||
{
|
{
|
||||||
this.m_previousAttempts.AddRange(recordToBeCloned.m_previousAttempts);
|
this.PreviousAttempts.AddRange(recordToBeCloned.PreviousAttempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.m_variables?.Count > 0)
|
if (recordToBeCloned.m_variables?.Count > 0)
|
||||||
{
|
{
|
||||||
// Don't pave over the case-insensitive Dictionary we initialized above.
|
this.m_variables = recordToBeCloned.Variables.ToDictionary(k => k.Key, v => v.Value.Clone());
|
||||||
foreach (var kvp in recordToBeCloned.m_variables)
|
|
||||||
{
|
|
||||||
m_variables[kvp.Key] = kvp.Value.Clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,14 +98,14 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Name = "Type", Order = 3)]
|
[DataMember(Name = "Type", Order = 3)]
|
||||||
public string RecordType
|
public String RecordType
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 4)]
|
[DataMember(Order = 4)]
|
||||||
public string Name
|
public String Name
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -134,7 +126,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 7)]
|
[DataMember(Order = 7)]
|
||||||
public string CurrentOperation
|
public String CurrentOperation
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -162,7 +154,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 11)]
|
[DataMember(Order = 11)]
|
||||||
public string ResultCode
|
public String ResultCode
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -183,7 +175,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 14)]
|
[DataMember(Order = 14)]
|
||||||
public string WorkerName
|
public String WorkerName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -197,7 +189,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 16, EmitDefaultValue = false)]
|
[DataMember(Order = 16, EmitDefaultValue = false)]
|
||||||
public string RefName
|
public String RefName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -217,46 +209,35 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int32 ErrorCount
|
[DataMember(Order = 40)]
|
||||||
|
public Int32? ErrorCount
|
||||||
{
|
{
|
||||||
get
|
get;
|
||||||
{
|
set;
|
||||||
return m_errorCount.GetValueOrDefault(0);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_errorCount = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int32 WarningCount
|
[DataMember(Order = 50)]
|
||||||
|
public Int32? WarningCount
|
||||||
{
|
{
|
||||||
get
|
get;
|
||||||
{
|
set;
|
||||||
return m_warningCount.GetValueOrDefault(0);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_warningCount = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int32 NoticeCount
|
[DataMember(Order = 55)]
|
||||||
|
public Int32? NoticeCount
|
||||||
{
|
{
|
||||||
get
|
get;
|
||||||
{
|
set;
|
||||||
return m_noticeCount.GetValueOrDefault(0);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_noticeCount = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Issue> Issues
|
public List<Issue> Issues
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (m_issues == null)
|
||||||
|
{
|
||||||
|
m_issues = new List<Issue>();
|
||||||
|
}
|
||||||
return m_issues;
|
return m_issues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +257,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 131)]
|
[DataMember(Order = 131)]
|
||||||
public string Identifier
|
public String Identifier
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -293,14 +274,22 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (m_previousAttempts == null)
|
||||||
|
{
|
||||||
|
m_previousAttempts = new List<TimelineAttempt>();
|
||||||
|
}
|
||||||
return m_previousAttempts;
|
return m_previousAttempts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDictionary<string, VariableValue> Variables
|
public IDictionary<String, VariableValue> Variables
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (m_variables == null)
|
||||||
|
{
|
||||||
|
m_variables = new Dictionary<String, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
return m_variables;
|
return m_variables;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,53 +299,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
return new TimelineRecord(this);
|
return new TimelineRecord(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[OnDeserialized]
|
[DataMember(Name = "Issues", EmitDefaultValue = false, Order = 60)]
|
||||||
private void OnDeserialized(StreamingContext context)
|
|
||||||
{
|
|
||||||
this.EnsureInitialized();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DataContractSerializer bypasses all constructor logic and inline initialization!
|
|
||||||
/// This method takes the place of a workhorse constructor for baseline initialization.
|
|
||||||
/// The expectation is for this logic to be accessible to constructors and also to the OnDeserialized helper.
|
|
||||||
/// </summary>
|
|
||||||
private void EnsureInitialized()
|
|
||||||
{
|
|
||||||
// Note that ?? is a short-circuiting operator. (??= would be preferable, but it's not supported in the .NET Framework version currently used by actions/runner.)
|
|
||||||
|
|
||||||
// De-nullify the following historically-nullable ints.
|
|
||||||
// (After several weeks in production, it may be possible to eliminate these nullable backing fields.)
|
|
||||||
m_errorCount = m_errorCount ?? 0;
|
|
||||||
m_warningCount = m_warningCount ?? 0;
|
|
||||||
m_noticeCount = m_noticeCount ?? 0;
|
|
||||||
|
|
||||||
m_issues = m_issues ?? new List<Issue>();
|
|
||||||
m_previousAttempts = m_previousAttempts ?? new List<TimelineAttempt>();
|
|
||||||
this.Attempt = Math.Max(this.Attempt, 1);
|
|
||||||
|
|
||||||
// Ensure whatever content may have been deserialized for m_variables is backed by a case-insensitive Dictionary.
|
|
||||||
var empty = Enumerable.Empty<KeyValuePair<string, VariableValue>>();
|
|
||||||
m_variables = new Dictionary<string, VariableValue>(m_variables ?? empty, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = nameof(ErrorCount), Order = 40)]
|
|
||||||
private Int32? m_errorCount;
|
|
||||||
|
|
||||||
[DataMember(Name = nameof(WarningCount), Order = 50)]
|
|
||||||
private Int32? m_warningCount;
|
|
||||||
|
|
||||||
[DataMember(Name = nameof(NoticeCount), Order = 55)]
|
|
||||||
private Int32? m_noticeCount;
|
|
||||||
|
|
||||||
[DataMember(Name = nameof(Issues), EmitDefaultValue = false, Order = 60)]
|
|
||||||
private List<Issue> m_issues;
|
private List<Issue> m_issues;
|
||||||
|
|
||||||
[DataMember(Name = nameof(Variables), EmitDefaultValue = false, Order = 80)]
|
[DataMember(Name = "Variables", EmitDefaultValue = false, Order = 80)]
|
||||||
private Dictionary<string, VariableValue> m_variables;
|
private Dictionary<String, VariableValue> m_variables;
|
||||||
|
|
||||||
[DataMember(Name = nameof(PreviousAttempts), EmitDefaultValue = false, Order = 120)]
|
[DataMember(Name = "PreviousAttempts", EmitDefaultValue = false, Order = 120)]
|
||||||
private List<TimelineAttempt> m_previousAttempts;
|
private List<TimelineAttempt> m_previousAttempts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
namespace GitHub.Actions.RunService.WebApi
|
||||||
{
|
{
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public class AcquireJobRequest
|
public class AcquireJobRequest
|
||||||
{
|
{
|
||||||
[DataMember(Name = "jobMessageId", EmitDefaultValue = false)]
|
[DataMember(Name = "streamId", EmitDefaultValue = false)]
|
||||||
public string JobMessageId { get; set; }
|
public string StreamID { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace Sdk.RSWebApi.Contracts
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public struct Annotation
|
|
||||||
{
|
|
||||||
[DataMember(Name = "level", EmitDefaultValue = false)]
|
|
||||||
public AnnotationLevel Level;
|
|
||||||
|
|
||||||
[DataMember(Name = "message", EmitDefaultValue = false)]
|
|
||||||
public string Message;
|
|
||||||
|
|
||||||
[DataMember(Name = "rawDetails", EmitDefaultValue = false)]
|
|
||||||
public string RawDetails;
|
|
||||||
|
|
||||||
[DataMember(Name = "path", EmitDefaultValue = false)]
|
|
||||||
public string Path;
|
|
||||||
|
|
||||||
[DataMember(Name = "isInfrastructureIssue", EmitDefaultValue = false)]
|
|
||||||
public bool IsInfrastructureIssue;
|
|
||||||
|
|
||||||
[DataMember(Name = "startLine", EmitDefaultValue = false)]
|
|
||||||
public long StartLine;
|
|
||||||
|
|
||||||
[DataMember(Name = "endLine", EmitDefaultValue = false)]
|
|
||||||
public long EndLine;
|
|
||||||
|
|
||||||
[DataMember(Name = "startColumn", EmitDefaultValue = false)]
|
|
||||||
public long StartColumn;
|
|
||||||
|
|
||||||
[DataMember(Name = "endColumn", EmitDefaultValue = false)]
|
|
||||||
public long EndColumn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace Sdk.RSWebApi.Contracts
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public enum AnnotationLevel
|
|
||||||
{
|
|
||||||
[EnumMember]
|
|
||||||
UNKNOWN = 0,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
NOTICE = 1,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
WARNING = 2,
|
|
||||||
|
|
||||||
[EnumMember]
|
|
||||||
FAILURE = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
namespace GitHub.Actions.RunService.WebApi
|
||||||
{
|
{
|
||||||
@@ -11,20 +10,17 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
[DataMember(Name = "planId", EmitDefaultValue = false)]
|
[DataMember(Name = "planId", EmitDefaultValue = false)]
|
||||||
public Guid PlanID { get; set; }
|
public Guid PlanID { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "jobId", EmitDefaultValue = false)]
|
[DataMember(Name = "jobId", EmitDefaultValue = false)]
|
||||||
public Guid JobID { get; set; }
|
public Guid JobID { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "conclusion")]
|
[DataMember(Name = "conclusion")]
|
||||||
public TaskResult Conclusion { get; set; }
|
public TaskResult Conclusion { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "outputs", EmitDefaultValue = false)]
|
[DataMember(Name = "outputs", EmitDefaultValue = false)]
|
||||||
public Dictionary<string, VariableValue> Outputs { get; set; }
|
public Dictionary<string, VariableValue> Outputs { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "stepResults", EmitDefaultValue = false)]
|
[DataMember(Name = "stepResults", EmitDefaultValue = false)]
|
||||||
public IList<StepResult> StepResults { get; set; }
|
public IList<StepResult> StepResults { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "annotations", EmitDefaultValue = false)]
|
|
||||||
public IList<Annotation> Annotations { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace Sdk.RSWebApi.Contracts
|
|
||||||
{
|
|
||||||
public static class IssueExtensions
|
|
||||||
{
|
|
||||||
public static Annotation? ToAnnotation(this Issue issue)
|
|
||||||
{
|
|
||||||
var issueMessage = issue.Message;
|
|
||||||
if (string.IsNullOrWhiteSpace(issueMessage))
|
|
||||||
{
|
|
||||||
if (!issue.Data.TryGetValue(RunIssueKeys.Message, out issueMessage) || string.IsNullOrWhiteSpace(issueMessage))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var annotationLevel = GetAnnotationLevel(issue.Type);
|
|
||||||
var path = GetFilePath(issue);
|
|
||||||
var lineNumber = GetAnnotationNumber(issue, RunIssueKeys.Line) ?? 0;
|
|
||||||
var endLineNumber = GetAnnotationNumber(issue, RunIssueKeys.EndLine) ?? lineNumber;
|
|
||||||
var columnNumber = GetAnnotationNumber(issue, RunIssueKeys.Col) ?? 0;
|
|
||||||
var endColumnNumber = GetAnnotationNumber(issue, RunIssueKeys.EndColumn) ?? columnNumber;
|
|
||||||
var logLineNumber = GetAnnotationNumber(issue, RunIssueKeys.LogLineNumber) ?? 0;
|
|
||||||
|
|
||||||
if (path == null && lineNumber == 0 && logLineNumber != 0)
|
|
||||||
{
|
|
||||||
lineNumber = logLineNumber;
|
|
||||||
endLineNumber = logLineNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Annotation
|
|
||||||
{
|
|
||||||
Level = annotationLevel,
|
|
||||||
Message = issueMessage,
|
|
||||||
Path = path,
|
|
||||||
StartLine = lineNumber,
|
|
||||||
EndLine = endLineNumber,
|
|
||||||
StartColumn = columnNumber,
|
|
||||||
EndColumn = endColumnNumber,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AnnotationLevel GetAnnotationLevel(IssueType issueType)
|
|
||||||
{
|
|
||||||
switch (issueType)
|
|
||||||
{
|
|
||||||
case IssueType.Error:
|
|
||||||
return AnnotationLevel.FAILURE;
|
|
||||||
case IssueType.Warning:
|
|
||||||
return AnnotationLevel.WARNING;
|
|
||||||
case IssueType.Notice:
|
|
||||||
return AnnotationLevel.NOTICE;
|
|
||||||
default:
|
|
||||||
return AnnotationLevel.UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int? GetAnnotationNumber(Issue issue, string key)
|
|
||||||
{
|
|
||||||
if (issue.Data.TryGetValue(key, out var numberString) &&
|
|
||||||
int.TryParse(numberString, out var number))
|
|
||||||
{
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetAnnotationField(Issue issue, string key)
|
|
||||||
{
|
|
||||||
if (issue.Data.TryGetValue(key, out var value))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetFilePath(Issue issue)
|
|
||||||
{
|
|
||||||
if (issue.Data.TryGetValue(RunIssueKeys.File, out var path) &&
|
|
||||||
!string.IsNullOrWhiteSpace(path))
|
|
||||||
{
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace Sdk.RSWebApi.Contracts
|
|
||||||
{
|
|
||||||
public static class RunIssueKeys
|
|
||||||
{
|
|
||||||
public const string Message = "message";
|
|
||||||
public const string File = "file";
|
|
||||||
public const string Line = "line";
|
|
||||||
public const string Col = "col";
|
|
||||||
public const string EndLine = "endLine";
|
|
||||||
public const string EndColumn = "endColumn";
|
|
||||||
public const string LogLineNumber = "logFileLineNumber";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
namespace GitHub.Actions.RunService.WebApi
|
||||||
{
|
{
|
||||||
@@ -36,8 +34,5 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
|
|
||||||
[DataMember(Name = "completed_log_lines", EmitDefaultValue = false)]
|
[DataMember(Name = "completed_log_lines", EmitDefaultValue = false)]
|
||||||
public long? CompletedLogLines { get; set; }
|
public long? CompletedLogLines { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "annotations", EmitDefaultValue = false)]
|
|
||||||
public List<Annotation> Annotations { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -56,7 +55,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AgentJobRequestMessage> GetJobMessageAsync(
|
public Task<AgentJobRequestMessage> GetJobMessageAsync(
|
||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
string messageId,
|
string messageId,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -64,42 +63,26 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
var payload = new AcquireJobRequest
|
var payload = new AcquireJobRequest
|
||||||
{
|
{
|
||||||
JobMessageId = messageId,
|
StreamID = messageId
|
||||||
};
|
};
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "acquirejob");
|
requestUri = new Uri(requestUri, "acquirejob");
|
||||||
|
|
||||||
var requestContent = new ObjectContent<AcquireJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
var requestContent = new ObjectContent<AcquireJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
||||||
var result = await SendAsync<AgentJobRequestMessage>(
|
return SendAsync<AgentJobRequestMessage>(
|
||||||
httpMethod,
|
httpMethod,
|
||||||
requestUri: requestUri,
|
requestUri: requestUri,
|
||||||
content: requestContent,
|
content: requestContent,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (result.IsSuccess)
|
|
||||||
{
|
|
||||||
return result.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (result.StatusCode)
|
|
||||||
{
|
|
||||||
case HttpStatusCode.NotFound:
|
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}");
|
|
||||||
case HttpStatusCode.Conflict:
|
|
||||||
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired: {messageId}");
|
|
||||||
default:
|
|
||||||
throw new Exception($"Failed to get job message: {result.Error}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CompleteJobAsync(
|
public Task CompleteJobAsync(
|
||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
Guid planId,
|
Guid planId,
|
||||||
Guid jobId,
|
Guid jobId,
|
||||||
TaskResult result,
|
TaskResult result,
|
||||||
Dictionary<String, VariableValue> outputs,
|
Dictionary<String, VariableValue> outputs,
|
||||||
IList<StepResult> stepResults,
|
IList<StepResult> stepResults,
|
||||||
IList<Annotation> jobAnnotations,
|
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
@@ -109,33 +92,20 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
JobID = jobId,
|
JobID = jobId,
|
||||||
Conclusion = result,
|
Conclusion = result,
|
||||||
Outputs = outputs,
|
Outputs = outputs,
|
||||||
StepResults = stepResults,
|
StepResults = stepResults
|
||||||
Annotations = jobAnnotations
|
|
||||||
};
|
};
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "completejob");
|
requestUri = new Uri(requestUri, "completejob");
|
||||||
|
|
||||||
var requestContent = new ObjectContent<CompleteJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
var requestContent = new ObjectContent<CompleteJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
||||||
var response = await SendAsync(
|
return SendAsync(
|
||||||
httpMethod,
|
httpMethod,
|
||||||
requestUri,
|
requestUri,
|
||||||
content: requestContent,
|
content: requestContent,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (response.StatusCode)
|
|
||||||
{
|
|
||||||
case HttpStatusCode.NotFound:
|
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
|
||||||
default:
|
|
||||||
throw new Exception($"Failed to complete job: {response.ReasonPhrase}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RenewJobResponse> RenewJobAsync(
|
public Task<RenewJobResponse> RenewJobAsync(
|
||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
Guid planId,
|
Guid planId,
|
||||||
Guid jobId,
|
Guid jobId,
|
||||||
@@ -151,24 +121,11 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
requestUri = new Uri(requestUri, "renewjob");
|
requestUri = new Uri(requestUri, "renewjob");
|
||||||
|
|
||||||
var requestContent = new ObjectContent<RenewJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
var requestContent = new ObjectContent<RenewJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
||||||
var result = await SendAsync<RenewJobResponse>(
|
return SendAsync<RenewJobResponse>(
|
||||||
httpMethod,
|
httpMethod,
|
||||||
requestUri,
|
requestUri,
|
||||||
content: requestContent,
|
content: requestContent,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (result.IsSuccess)
|
|
||||||
{
|
|
||||||
return result.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (result.StatusCode)
|
|
||||||
{
|
|
||||||
case HttpStatusCode.NotFound:
|
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
|
||||||
default:
|
|
||||||
throw new Exception($"Failed to renew job: {result.Error}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<NoWarn>NU1701;NU1603</NoWarn>
|
<NoWarn>NU1701;NU1603</NoWarn>
|
||||||
<Version>$(Version)</Version>
|
<Version>$(Version)</Version>
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>7.3</LangVersion>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -56,7 +55,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TaskAgentMessage> GetRunnerMessageAsync(
|
public Task<TaskAgentMessage> GetRunnerMessageAsync(
|
||||||
string runnerVersion,
|
string runnerVersion,
|
||||||
TaskAgentStatus? status,
|
TaskAgentStatus? status,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
@@ -75,18 +74,11 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
queryParams.Add("runnerVersion", runnerVersion);
|
queryParams.Add("runnerVersion", runnerVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await SendAsync<TaskAgentMessage>(
|
return SendAsync<TaskAgentMessage>(
|
||||||
new HttpMethod("GET"),
|
new HttpMethod("GET"),
|
||||||
requestUri: requestUri,
|
requestUri: requestUri,
|
||||||
queryParameters: queryParams,
|
queryParameters: queryParams,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (result.IsSuccess)
|
|
||||||
{
|
|
||||||
return result.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Failed to get job message: {result.Error}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,8 +157,6 @@ namespace GitHub.Services.Results.Contracts
|
|||||||
public string StartedAt;
|
public string StartedAt;
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public string CompletedAt;
|
public string CompletedAt;
|
||||||
[DataMember]
|
|
||||||
public Conclusion Conclusion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Status
|
public enum Status
|
||||||
@@ -169,15 +167,6 @@ namespace GitHub.Services.Results.Contracts
|
|||||||
StatusCompleted = 6
|
StatusCompleted = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Conclusion
|
|
||||||
{
|
|
||||||
ConclusionUnknown = 0,
|
|
||||||
ConclusionSuccess = 2,
|
|
||||||
ConclusionFailure = 3,
|
|
||||||
ConclusionCancelled = 4,
|
|
||||||
ConclusionSkipped = 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BlobStorageTypes
|
public static class BlobStorageTypes
|
||||||
{
|
{
|
||||||
public static readonly string AzureBlobStorage = "BLOB_STORAGE_TYPE_AZURE";
|
public static readonly string AzureBlobStorage = "BLOB_STORAGE_TYPE_AZURE";
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.Services.Launch.Contracts
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class ActionReferenceRequest
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "action")]
|
|
||||||
public string Action { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "version")]
|
|
||||||
public string Version { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "path")]
|
|
||||||
public string Path { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class ActionReferenceRequestList
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "actions")]
|
|
||||||
public IList<ActionReferenceRequest> Actions { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class ActionDownloadInfoResponse
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "authentication")]
|
|
||||||
public ActionDownloadAuthenticationResponse Authentication { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "name")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "resolved_name")]
|
|
||||||
public string ResolvedName { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "resolved_sha")]
|
|
||||||
public string ResolvedSha { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "tar_url")]
|
|
||||||
public string TarUrl { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "version")]
|
|
||||||
public string Version { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "zip_url")]
|
|
||||||
public string ZipUrl { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class ActionDownloadAuthenticationResponse
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "expires_at")]
|
|
||||||
public DateTime ExpiresAt { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "token")]
|
|
||||||
public string Token { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class ActionDownloadInfoResponseCollection
|
|
||||||
{
|
|
||||||
/// <summary>A mapping of action specifications to their download information.</summary>
|
|
||||||
/// <remarks>The key is the full name of the action plus version, e.g. "actions/checkout@v2".</remarks>
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "actions")]
|
|
||||||
public IDictionary<string, ActionDownloadInfoResponse> Actions { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Formatting;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.Launch.Contracts;
|
|
||||||
|
|
||||||
using Sdk.WebApi.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Services.Launch.Client
|
|
||||||
{
|
|
||||||
public class LaunchHttpClient : RawHttpClientBase
|
|
||||||
{
|
|
||||||
public LaunchHttpClient(
|
|
||||||
Uri baseUrl,
|
|
||||||
HttpMessageHandler pipeline,
|
|
||||||
string token,
|
|
||||||
bool disposeHandler)
|
|
||||||
: base(baseUrl, pipeline, disposeHandler)
|
|
||||||
{
|
|
||||||
m_token = token;
|
|
||||||
m_launchServiceUrl = baseUrl;
|
|
||||||
m_formatter = new JsonMediaTypeFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions");
|
|
||||||
return ToServerData(await GetLaunchSignedURLResponse<ActionReferenceRequestList, ActionDownloadInfoResponseCollection>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve Actions
|
|
||||||
private async Task<T> GetLaunchSignedURLResponse<R, T>(Uri uri, R request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
|
|
||||||
{
|
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
|
||||||
{
|
|
||||||
requestMessage.Content = content;
|
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
|
||||||
{
|
|
||||||
return await ReadJsonContentAsync<T>(response, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActionReferenceRequestList ToGitHubData(ActionReferenceList actionReferenceList)
|
|
||||||
{
|
|
||||||
return new ActionReferenceRequestList
|
|
||||||
{
|
|
||||||
Actions = actionReferenceList.Actions?.Select(ToGitHubData).ToList()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActionReferenceRequest ToGitHubData(ActionReference actionReference)
|
|
||||||
{
|
|
||||||
return new ActionReferenceRequest
|
|
||||||
{
|
|
||||||
Action = actionReference.NameWithOwner,
|
|
||||||
Version = actionReference.Ref,
|
|
||||||
Path = actionReference.Path
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActionDownloadInfoCollection ToServerData(ActionDownloadInfoResponseCollection actionDownloadInfoResponseCollection)
|
|
||||||
{
|
|
||||||
return new ActionDownloadInfoCollection
|
|
||||||
{
|
|
||||||
Actions = actionDownloadInfoResponseCollection.Actions?.ToDictionary(kvp => kvp.Key, kvp => ToServerData(kvp.Value))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActionDownloadInfo ToServerData(ActionDownloadInfoResponse actionDownloadInfoResponse)
|
|
||||||
{
|
|
||||||
return new ActionDownloadInfo
|
|
||||||
{
|
|
||||||
Authentication = ToServerData(actionDownloadInfoResponse.Authentication),
|
|
||||||
NameWithOwner = actionDownloadInfoResponse.Name,
|
|
||||||
ResolvedNameWithOwner = actionDownloadInfoResponse.ResolvedName,
|
|
||||||
ResolvedSha = actionDownloadInfoResponse.ResolvedSha,
|
|
||||||
TarballUrl = actionDownloadInfoResponse.TarUrl,
|
|
||||||
Ref = actionDownloadInfoResponse.Version,
|
|
||||||
ZipballUrl = actionDownloadInfoResponse.ZipUrl,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActionDownloadAuthentication? ToServerData(ActionDownloadAuthenticationResponse? actionDownloadAuthenticationResponse)
|
|
||||||
{
|
|
||||||
if (actionDownloadAuthenticationResponse == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ActionDownloadAuthentication
|
|
||||||
{
|
|
||||||
ExpiresAt = actionDownloadAuthenticationResponse.ExpiresAt,
|
|
||||||
Token = actionDownloadAuthenticationResponse.Token
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaTypeFormatter m_formatter;
|
|
||||||
private Uri m_launchServiceUrl;
|
|
||||||
private string m_token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ using Newtonsoft.Json.Linq;
|
|||||||
|
|
||||||
namespace Sdk.WebApi.WebApi
|
namespace Sdk.WebApi.WebApi
|
||||||
{
|
{
|
||||||
public class RawHttpClientBase : IDisposable
|
public class RawHttpClientBase: IDisposable
|
||||||
{
|
{
|
||||||
protected RawHttpClientBase(
|
protected RawHttpClientBase(
|
||||||
Uri baseUrl,
|
Uri baseUrl,
|
||||||
@@ -101,7 +101,7 @@ namespace Sdk.WebApi.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Task<RawHttpClientResult<T>> SendAsync<T>(
|
protected Task<T> SendAsync<T>(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
HttpContent content = null,
|
HttpContent content = null,
|
||||||
@@ -112,7 +112,7 @@ namespace Sdk.WebApi.WebApi
|
|||||||
return SendAsync<T>(method, null, requestUri, content, queryParameters, userState, cancellationToken);
|
return SendAsync<T>(method, null, requestUri, content, queryParameters, userState, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
protected async Task<T> SendAsync<T>(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
IEnumerable<KeyValuePair<String, String>> additionalHeaders,
|
IEnumerable<KeyValuePair<String, String>> additionalHeaders,
|
||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
@@ -128,7 +128,7 @@ namespace Sdk.WebApi.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
protected async Task<T> SendAsync<T>(
|
||||||
HttpRequestMessage message,
|
HttpRequestMessage message,
|
||||||
Object userState = null,
|
Object userState = null,
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
@@ -138,16 +138,7 @@ namespace Sdk.WebApi.WebApi
|
|||||||
//from deadlocking...
|
//from deadlocking...
|
||||||
using (HttpResponseMessage response = await this.SendAsync(message, userState, cancellationToken).ConfigureAwait(false))
|
using (HttpResponseMessage response = await this.SendAsync(message, userState, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
if (response.IsSuccessStatusCode)
|
return await ReadContentAsAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
T data = await ReadContentAsAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
|
||||||
return RawHttpClientResult<T>.Ok(data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string errorMessage = $"Error: {response.ReasonPhrase}";
|
|
||||||
return RawHttpClientResult<T>.Fail(errorMessage, response.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace Sdk.WebApi.WebApi
|
|
||||||
{
|
|
||||||
public class RawHttpClientResult
|
|
||||||
{
|
|
||||||
public bool IsSuccess { get; protected set; }
|
|
||||||
public string Error { get; protected set; }
|
|
||||||
public HttpStatusCode StatusCode { get; protected set; }
|
|
||||||
public bool IsFailure => !IsSuccess;
|
|
||||||
|
|
||||||
protected RawHttpClientResult(bool isSuccess, string error, HttpStatusCode statusCode)
|
|
||||||
{
|
|
||||||
IsSuccess = isSuccess;
|
|
||||||
Error = error;
|
|
||||||
StatusCode = statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RawHttpClientResult<T> : RawHttpClientResult
|
|
||||||
{
|
|
||||||
public T Value { get; private set; }
|
|
||||||
|
|
||||||
protected internal RawHttpClientResult(T value, bool isSuccess, string error, HttpStatusCode statusCode)
|
|
||||||
: base(isSuccess, error, statusCode)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RawHttpClientResult<T> Fail(string message, HttpStatusCode statusCode) => new RawHttpClientResult<T>(default(T), false, message, statusCode);
|
|
||||||
public static RawHttpClientResult<T> Ok(T value) => new RawHttpClientResult<T>(value, true, string.Empty, HttpStatusCode.OK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -329,8 +329,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
Name = r.Name,
|
Name = r.Name,
|
||||||
Status = ConvertStateToStatus(r.State.GetValueOrDefault()),
|
Status = ConvertStateToStatus(r.State.GetValueOrDefault()),
|
||||||
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat),
|
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat),
|
||||||
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat),
|
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat)
|
||||||
Conclusion = ConvertResultToConclusion(r.Result)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,29 +348,6 @@ namespace GitHub.Services.Results.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Conclusion ConvertResultToConclusion(TaskResult? r)
|
|
||||||
{
|
|
||||||
if (!r.HasValue)
|
|
||||||
{
|
|
||||||
return Conclusion.ConclusionUnknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (r)
|
|
||||||
{
|
|
||||||
case TaskResult.Succeeded:
|
|
||||||
case TaskResult.SucceededWithIssues:
|
|
||||||
return Conclusion.ConclusionSuccess;
|
|
||||||
case TaskResult.Canceled:
|
|
||||||
return Conclusion.ConclusionCancelled;
|
|
||||||
case TaskResult.Skipped:
|
|
||||||
return Conclusion.ConclusionSkipped;
|
|
||||||
case TaskResult.Failed:
|
|
||||||
return Conclusion.ConclusionFailure;
|
|
||||||
default:
|
|
||||||
return Conclusion.ConclusionUnknown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateWorkflowStepsAsync(Guid planId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
|
public async Task UpdateWorkflowStepsAsync(Guid planId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||||
|
|||||||
@@ -1,270 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization.Json;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using Xunit;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.DistributedTask
|
|
||||||
{
|
|
||||||
public sealed class TimelineRecordL0
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_Defaults()
|
|
||||||
{
|
|
||||||
var tr = new TimelineRecord();
|
|
||||||
|
|
||||||
Assert.Equal(0, tr.ErrorCount);
|
|
||||||
Assert.Equal(0, tr.WarningCount);
|
|
||||||
Assert.Equal(0, tr.NoticeCount);
|
|
||||||
Assert.Equal(1, tr.Attempt);
|
|
||||||
Assert.NotNull(tr.Issues);
|
|
||||||
Assert.NotNull(tr.PreviousAttempts);
|
|
||||||
Assert.NotNull(tr.Variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_Clone()
|
|
||||||
{
|
|
||||||
var original = new TimelineRecord();
|
|
||||||
original.ErrorCount = 100;
|
|
||||||
original.WarningCount = 200;
|
|
||||||
original.NoticeCount = 300;
|
|
||||||
original.Attempt = 3;
|
|
||||||
|
|
||||||
// The Variables dictionary should be a case-insensitive dictionary.
|
|
||||||
original.Variables["xxx"] = new VariableValue("first", false);
|
|
||||||
original.Variables["XXX"] = new VariableValue("second", false);
|
|
||||||
|
|
||||||
Assert.Equal(1, original.Variables.Count);
|
|
||||||
Assert.Equal("second", original.Variables.Values.First().Value);
|
|
||||||
Assert.Equal("second", original.Variables["xXx"].Value);
|
|
||||||
|
|
||||||
var clone = original.Clone();
|
|
||||||
|
|
||||||
Assert.NotSame(original, clone);
|
|
||||||
Assert.NotSame(original.Variables, clone.Variables);
|
|
||||||
Assert.Equal(100, clone.ErrorCount);
|
|
||||||
Assert.Equal(200, clone.WarningCount);
|
|
||||||
Assert.Equal(300, clone.NoticeCount);
|
|
||||||
Assert.Equal(3, clone.Attempt);
|
|
||||||
|
|
||||||
// Now, mutate the original post-clone.
|
|
||||||
original.ErrorCount++;
|
|
||||||
original.WarningCount += 10;
|
|
||||||
original.NoticeCount *= 3;
|
|
||||||
original.Attempt--;
|
|
||||||
original.Variables["a"] = new VariableValue("1", false);
|
|
||||||
|
|
||||||
// Verify that the clone was unaffected by the changes to the original.
|
|
||||||
Assert.Equal(100, clone.ErrorCount);
|
|
||||||
Assert.Equal(200, clone.WarningCount);
|
|
||||||
Assert.Equal(300, clone.NoticeCount);
|
|
||||||
Assert.Equal(3, clone.Attempt);
|
|
||||||
Assert.Equal(1, clone.Variables.Count);
|
|
||||||
Assert.Equal("second", clone.Variables.Values.First().Value);
|
|
||||||
|
|
||||||
// Verify that the clone's Variables dictionary is also case-sensitive.
|
|
||||||
clone.Variables["yyy"] = new VariableValue("third", false);
|
|
||||||
clone.Variables["YYY"] = new VariableValue("fourth", false);
|
|
||||||
|
|
||||||
Assert.Equal(2, clone.Variables.Count);
|
|
||||||
Assert.Equal("second", clone.Variables["xXx"].Value);
|
|
||||||
Assert.Equal("fourth", clone.Variables["yYy"].Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_DeserializationEdgeCase_NonNullCollections()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// Verify that missing JSON fields don't result in null values for collection properties.
|
|
||||||
var tr = Deserialize(jsonSamples["minimal"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("minimal", tr!.Name);
|
|
||||||
Assert.NotNull(tr.Issues);
|
|
||||||
Assert.NotNull(tr.PreviousAttempts);
|
|
||||||
Assert.NotNull(tr.Variables);
|
|
||||||
|
|
||||||
// Verify that explicitly-null JSON fields don't result in null values for collection properties.
|
|
||||||
// (Our deserialization logic should fix these up and instantiate an empty collection.)
|
|
||||||
tr = Deserialize(jsonSamples["explicit-null-collections"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("explicit-null-collections", tr!.Name);
|
|
||||||
Assert.NotNull(tr.Issues);
|
|
||||||
Assert.NotNull(tr.PreviousAttempts);
|
|
||||||
Assert.NotNull(tr.Variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_DeserializationEdgeCase_AttemptCannotBeLessThan1()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// Verify that 1 is the effective floor for TimelineRecord::Attempt.
|
|
||||||
var tr = Deserialize(jsonSamples["minimal"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("minimal", tr!.Name);
|
|
||||||
Assert.Equal(1, tr.Attempt);
|
|
||||||
|
|
||||||
tr = Deserialize(jsonSamples["invalid-attempt-value"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("invalid-attempt-value", tr!.Name);
|
|
||||||
Assert.Equal(1, tr.Attempt);
|
|
||||||
|
|
||||||
tr = Deserialize(jsonSamples["zero-attempt-value"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("zero-attempt-value", tr!.Name);
|
|
||||||
Assert.Equal(1, tr.Attempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_DeserializationEdgeCase_HandleLegacyNullsGracefully()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// Verify that nulls for ErrorCount, WarningCount, and NoticeCount are interpreted as 0.
|
|
||||||
var tr = Deserialize(jsonSamples["legacy-nulls"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("legacy-nulls", tr!.Name);
|
|
||||||
Assert.Equal(0, tr.ErrorCount);
|
|
||||||
Assert.Equal(0, tr.WarningCount);
|
|
||||||
Assert.Equal(0, tr.NoticeCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_DeserializationEdgeCase_HandleMissingCountsGracefully()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// Verify that nulls for ErrorCount, WarningCount, and NoticeCount are interpreted as 0.
|
|
||||||
var tr = Deserialize(jsonSamples["missing-counts"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("missing-counts", tr!.Name);
|
|
||||||
Assert.Equal(0, tr.ErrorCount);
|
|
||||||
Assert.Equal(0, tr.WarningCount);
|
|
||||||
Assert.Equal(0, tr.NoticeCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_DeserializationEdgeCase_NonZeroCounts()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// Verify that nulls for ErrorCount, WarningCount, and NoticeCount are interpreted as 0.
|
|
||||||
var tr = Deserialize(jsonSamples["non-zero-counts"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("non-zero-counts", tr!.Name);
|
|
||||||
Assert.Equal(10, tr.ErrorCount);
|
|
||||||
Assert.Equal(20, tr.WarningCount);
|
|
||||||
Assert.Equal(30, tr.NoticeCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_Deserialization_LeanTimelineRecord()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// Verify that a lean TimelineRecord can be deserialized.
|
|
||||||
var tr = Deserialize(jsonSamples["lean"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("lean", tr!.Name);
|
|
||||||
Assert.Equal(4, tr.Attempt);
|
|
||||||
Assert.Equal(1, tr.Issues.Count);
|
|
||||||
Assert.Equal(3, tr.Variables.Count);
|
|
||||||
Assert.Equal(3, tr.PreviousAttempts.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_Deserialization_VariablesDictionaryIsCaseInsensitive()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
var tr = Deserialize(jsonSamples["lean"]);
|
|
||||||
Assert.NotNull(tr);
|
|
||||||
Assert.Equal("lean", tr!.Name);
|
|
||||||
Assert.Equal(3, tr.Variables.Count);
|
|
||||||
|
|
||||||
// Verify that the Variables Dictionary is case-insensitive.
|
|
||||||
tr.Variables["X"] = new VariableValue("overwritten", false);
|
|
||||||
Assert.Equal(3, tr.Variables.Count);
|
|
||||||
|
|
||||||
tr.Variables["new"] = new VariableValue("new.1", false);
|
|
||||||
Assert.Equal(4, tr.Variables.Count);
|
|
||||||
|
|
||||||
tr.Variables["NEW"] = new VariableValue("new.2", false);
|
|
||||||
Assert.Equal(4, tr.Variables.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "DistributedTask")]
|
|
||||||
public void VerifyTimelineRecord_DeserializationEdgeCase_DuplicateVariableKeysThrowsException()
|
|
||||||
{
|
|
||||||
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
|
||||||
|
|
||||||
// We could be more forgiving in this case if we discover that it's not uncommon in Production for serialized TimelineRecords to:
|
|
||||||
// 1) get incorrectly instantiated with a case-sensitive Variables dictionary (in older versions, this was possible via TimelineRecord::Clone)
|
|
||||||
// 2) end up with case variations of the same key
|
|
||||||
// 3) make another serialization/deserialization round trip.
|
|
||||||
//
|
|
||||||
// If we wanted to grant clemency to such incorrectly-serialized TimelineRecords,
|
|
||||||
// the fix to TimelineRecord::EnsureInitialized would look something like the following:
|
|
||||||
//
|
|
||||||
// var seedVariables = m_variables ?? Enumerable.Empty<KeyValuePair<string, VariableValue>>();
|
|
||||||
// m_variables = new Dictionary<string, VariableValue>(seedVariables.Count(), StringComparer.OrdinalIgnoreCase);
|
|
||||||
// foreach (var kvp in seedVariables)
|
|
||||||
// {
|
|
||||||
// m_variables[kvp.Key] = kvp.Value;
|
|
||||||
// }
|
|
||||||
Assert.Throws<ArgumentException>(() => Deserialize(jsonSamples["duplicate-variable-keys"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static Dictionary<string, string> LoadJsonSamples(string path)
|
|
||||||
{
|
|
||||||
// Embedding independent JSON samples within YML works well because JSON generally doesn't need to be escaped or otherwise mangled.
|
|
||||||
var yamlDeserializer = new YamlDotNet.Serialization.Deserializer();
|
|
||||||
using var stream = new StreamReader(path);
|
|
||||||
return yamlDeserializer.Deserialize<Dictionary<string, string>>(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TimelineRecord? Deserialize(string rawJson)
|
|
||||||
{
|
|
||||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawJson ?? string.Empty));
|
|
||||||
return m_jsonSerializer.ReadObject(stream) as TimelineRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string JsonSamplesFilePath
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Path.Combine(TestUtil.GetTestDataPath(), "timelinerecord_json_samples.yml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly DataContractJsonSerializer m_jsonSerializer = new(typeof(TimelineRecord));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -13,6 +12,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
private HostContext _hc;
|
private HostContext _hc;
|
||||||
private CancellationTokenSource _tokenSource;
|
private CancellationTokenSource _tokenSource;
|
||||||
|
private const string EXPECTED_SECRET_MASK = "***";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
@@ -95,11 +95,11 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Pass%20word%20123%21123"));
|
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Pass%20word%20123%21123"));
|
||||||
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Pass<word>123!123"));
|
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Pass<word>123!123"));
|
||||||
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Pass''word''123!123"));
|
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Pass''word''123!123"));
|
||||||
Assert.Equal("OlBh***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($":Password123!"))));
|
Assert.Equal("OlBh***==", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($":Password123!"))));
|
||||||
Assert.Equal("YTpQ***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"a:Password123!"))));
|
Assert.Equal("YTpQ***=", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"a:Password123!"))));
|
||||||
Assert.Equal("YWI6***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"ab:Password123!"))));
|
Assert.Equal("YWI6***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"ab:Password123!"))));
|
||||||
Assert.Equal("YWJjOlBh***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abc:Password123!"))));
|
Assert.Equal("YWJjOlBh***==", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abc:Password123!"))));
|
||||||
Assert.Equal("YWJjZDpQ***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcd:Password123!"))));
|
Assert.Equal("YWJjZDpQ***=", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcd:Password123!"))));
|
||||||
Assert.Equal("YWJjZGU6***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcde:Password123!"))));
|
Assert.Equal("YWJjZGU6***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcde:Password123!"))));
|
||||||
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Password123!!123"));
|
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Password123!!123"));
|
||||||
Assert.Equal("123short123", _hc.SecretMasker.MaskSecrets("123short123"));
|
Assert.Equal("123short123", _hc.SecretMasker.MaskSecrets("123short123"));
|
||||||
@@ -112,6 +112,116 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void Base64SecretMaskers()
|
||||||
|
{
|
||||||
|
|
||||||
|
// The following are good candidate strings for Base64 encoding because they include
|
||||||
|
// both standard and RFC 4648 Base64 digits in all offset variations.
|
||||||
|
// TeLL? noboDy~ SEcreT?
|
||||||
|
// tElL~ NEVER~ neveR?
|
||||||
|
// TIGht? Tight~ guard~
|
||||||
|
// pRIVAte~ guARd? TIghT~
|
||||||
|
// KeeP~ TIgHT? tIgHT~
|
||||||
|
// LoCk? TiGhT~ TIght~
|
||||||
|
// DIvULGe~ nObODY~ noBOdy?
|
||||||
|
// foreVER~ Tight~ GUaRd?
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
_hc.SecretMasker.AddValue("TeLL? noboDy~ SEcreT?");
|
||||||
|
|
||||||
|
// The above string has the following Base64 variations based on the chop leading byte(s) method of Base64 aliasing:
|
||||||
|
var base64Variations = new[]
|
||||||
|
{
|
||||||
|
"VGVMTD8gbm9ib0R5fiBTRWNyZVQ/",
|
||||||
|
"ZUxMPyBub2JvRHl+IFNFY3JlVD8",
|
||||||
|
"TEw/IG5vYm9EeX4gU0VjcmVUPw",
|
||||||
|
|
||||||
|
// RFC 4648 (URL-safe Base64)
|
||||||
|
"VGVMTD8gbm9ib0R5fiBTRWNyZVQ_",
|
||||||
|
"ZUxMPyBub2JvRHl-IFNFY3JlVD8",
|
||||||
|
"TEw_IG5vYm9EeX4gU0VjcmVUPw"
|
||||||
|
};
|
||||||
|
|
||||||
|
var bookends = new[]
|
||||||
|
{
|
||||||
|
(string.Empty, string.Empty),
|
||||||
|
(string.Empty, "="),
|
||||||
|
(string.Empty, "=="),
|
||||||
|
(string.Empty, "==="),
|
||||||
|
("a", "z"),
|
||||||
|
("A", "Z"),
|
||||||
|
("abc", "abc"),
|
||||||
|
("ABC", "ABC"),
|
||||||
|
("0", "0"),
|
||||||
|
("00", "00"),
|
||||||
|
("000", "000"),
|
||||||
|
("123", "789"),
|
||||||
|
("`", "`"),
|
||||||
|
("'", "'"),
|
||||||
|
("\"", "\""),
|
||||||
|
("[", "]"),
|
||||||
|
("(", ")"),
|
||||||
|
("$(", ")"),
|
||||||
|
("{", "}"),
|
||||||
|
("${", "}"),
|
||||||
|
("!", "!"),
|
||||||
|
("!!", "!!"),
|
||||||
|
("%", "%"),
|
||||||
|
("%%", "%%"),
|
||||||
|
("_", "_"),
|
||||||
|
("__", "__"),
|
||||||
|
(":", ":"),
|
||||||
|
("::", "::"),
|
||||||
|
(";", ";"),
|
||||||
|
(";;", ";;"),
|
||||||
|
(":", string.Empty),
|
||||||
|
(";", string.Empty),
|
||||||
|
(string.Empty, ":"),
|
||||||
|
(string.Empty, ";"),
|
||||||
|
("VGVMTD8gbm9ib", "ZUxMPy"),
|
||||||
|
("VGVMTD8gbm9ib", "TEw/IG5vYm9EeX4"),
|
||||||
|
("ZUxMPy", "TEw/IG5vYm9EeX4"),
|
||||||
|
("VGVMTD8gbm9ib", string.Empty),
|
||||||
|
("TEw/IG5vYm9EeX4", string.Empty),
|
||||||
|
("ZUxMPy", string.Empty),
|
||||||
|
(string.Empty, "VGVMTD8gbm9ib"),
|
||||||
|
(string.Empty, "TEw/IG5vYm9EeX4"),
|
||||||
|
(string.Empty, "ZUxMPy"),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var variation in base64Variations)
|
||||||
|
{
|
||||||
|
foreach (var pair in bookends)
|
||||||
|
{
|
||||||
|
var (prefix, suffix) = pair;
|
||||||
|
var expected = string.Format("{0}{1}{2}", prefix, EXPECTED_SECRET_MASK, suffix);
|
||||||
|
var payload = string.Format("{0}{1}{2}", prefix, variation, suffix);
|
||||||
|
Assert.Equal(expected, _hc.SecretMasker.MaskSecrets(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify no masking is performed on a partial match.
|
||||||
|
for (int i = 1; i < variation.Length - 1; i++)
|
||||||
|
{
|
||||||
|
var fragment = variation[..i];
|
||||||
|
Assert.Equal(fragment, _hc.SecretMasker.MaskSecrets(fragment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup.
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("secret&secret&secret", "secret&secret&\x0033[96msecret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
[InlineData("secret&secret&secret", "secret&secret&\x0033[96msecret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
||||||
[InlineData("secret&secret+secret", "secret&\x0033[96msecret+secret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
[InlineData("secret&secret+secret", "secret&\x0033[96msecret+secret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
||||||
|
|||||||
@@ -206,107 +206,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "ConfigurationManagement")]
|
|
||||||
public async Task ConfigureErrorDefaultLabelsDisabledWithNoCustomLabels()
|
|
||||||
{
|
|
||||||
using (TestHostContext tc = CreateTestContext())
|
|
||||||
{
|
|
||||||
Tracing trace = tc.GetTrace();
|
|
||||||
|
|
||||||
trace.Info("Creating config manager");
|
|
||||||
IConfigurationManager configManager = new ConfigurationManager();
|
|
||||||
configManager.Initialize(tc);
|
|
||||||
|
|
||||||
trace.Info("Preparing command line arguments");
|
|
||||||
var command = new CommandSettings(
|
|
||||||
tc,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"configure",
|
|
||||||
"--url", _expectedServerUrl,
|
|
||||||
"--name", _expectedAgentName,
|
|
||||||
"--runnergroup", _secondRunnerGroupName,
|
|
||||||
"--work", _expectedWorkFolder,
|
|
||||||
"--auth", _expectedAuthType,
|
|
||||||
"--token", _expectedToken,
|
|
||||||
"--no-default-labels",
|
|
||||||
"--ephemeral",
|
|
||||||
"--disableupdate",
|
|
||||||
"--unattended",
|
|
||||||
});
|
|
||||||
trace.Info("Constructed.");
|
|
||||||
_store.Setup(x => x.IsConfigured()).Returns(false);
|
|
||||||
_configMgrAgentSettings = null;
|
|
||||||
|
|
||||||
trace.Info("Ensuring configure fails if default labels are disabled and no custom labels are set");
|
|
||||||
var ex = await Assert.ThrowsAsync<NotSupportedException>(() => configManager.ConfigureAsync(command));
|
|
||||||
|
|
||||||
Assert.Contains("--no-default-labels without specifying --labels is not supported", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "ConfigurationManagement")]
|
|
||||||
public async Task ConfigureDefaultLabelsDisabledWithCustomLabels()
|
|
||||||
{
|
|
||||||
using (TestHostContext tc = CreateTestContext())
|
|
||||||
{
|
|
||||||
Tracing trace = tc.GetTrace();
|
|
||||||
|
|
||||||
trace.Info("Creating config manager");
|
|
||||||
IConfigurationManager configManager = new ConfigurationManager();
|
|
||||||
configManager.Initialize(tc);
|
|
||||||
|
|
||||||
var userLabels = "userlabel1,userlabel2";
|
|
||||||
|
|
||||||
trace.Info("Preparing command line arguments");
|
|
||||||
var command = new CommandSettings(
|
|
||||||
tc,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"configure",
|
|
||||||
"--url", _expectedServerUrl,
|
|
||||||
"--name", _expectedAgentName,
|
|
||||||
"--runnergroup", _secondRunnerGroupName,
|
|
||||||
"--work", _expectedWorkFolder,
|
|
||||||
"--auth", _expectedAuthType,
|
|
||||||
"--token", _expectedToken,
|
|
||||||
"--labels", userLabels,
|
|
||||||
"--no-default-labels",
|
|
||||||
"--ephemeral",
|
|
||||||
"--disableupdate",
|
|
||||||
"--unattended",
|
|
||||||
});
|
|
||||||
trace.Info("Constructed.");
|
|
||||||
_store.Setup(x => x.IsConfigured()).Returns(false);
|
|
||||||
_configMgrAgentSettings = null;
|
|
||||||
|
|
||||||
trace.Info("Ensuring all the required parameters are available in the command line parameter");
|
|
||||||
await configManager.ConfigureAsync(command);
|
|
||||||
|
|
||||||
_store.Setup(x => x.IsConfigured()).Returns(true);
|
|
||||||
|
|
||||||
trace.Info("Configured, verifying all the parameter value");
|
|
||||||
var s = configManager.LoadSettings();
|
|
||||||
Assert.NotNull(s);
|
|
||||||
Assert.True(s.ServerUrl.Equals(_expectedServerUrl));
|
|
||||||
Assert.True(s.AgentName.Equals(_expectedAgentName));
|
|
||||||
Assert.True(s.PoolId.Equals(_secondRunnerGroupId));
|
|
||||||
Assert.True(s.WorkFolder.Equals(_expectedWorkFolder));
|
|
||||||
Assert.True(s.Ephemeral.Equals(true));
|
|
||||||
|
|
||||||
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
|
||||||
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
|
||||||
|
|
||||||
var expectedLabels = userLabels.Split(",").ToList();
|
|
||||||
|
|
||||||
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "ConfigurationManagement")]
|
[Trait("Category", "ConfigurationManagement")]
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.Serialization.Json;
|
|
||||||
using System.Text;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi.Tests;
|
|
||||||
|
|
||||||
public sealed class AcquireJobRequestL0
|
|
||||||
{
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void VerifySerialization()
|
|
||||||
{
|
|
||||||
var request = new AcquireJobRequest
|
|
||||||
{
|
|
||||||
JobMessageId = "1526919030369-33"
|
|
||||||
};
|
|
||||||
var serializer = new DataContractJsonSerializer(typeof(AcquireJobRequest));
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
serializer.WriteObject(stream, request);
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
|
||||||
string json = reader.ReadToEnd();
|
|
||||||
string expected = DoubleQuotify(string.Format("{{'jobMessageId':'{0}'}}", request.JobMessageId));
|
|
||||||
Assert.Equal(expected, json);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void VerifyDeserialization()
|
|
||||||
{
|
|
||||||
var serializer = new DataContractJsonSerializer(typeof(AcquireJobRequest));
|
|
||||||
var variations = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
["{'streamId': 'legacy', 'jobMessageId': 'new-1'}"] = "new-1",
|
|
||||||
["{'jobMessageId': 'new-2', 'streamId': 'legacy'}"] = "new-2",
|
|
||||||
["{'jobMessageId': 'new-3'}"] = "new-3"
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var (source, expected) in variations)
|
|
||||||
{
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
stream.Write(Encoding.UTF8.GetBytes(DoubleQuotify(source)));
|
|
||||||
stream.Position = 0;
|
|
||||||
var recoveredRecord = serializer.ReadObject(stream) as AcquireJobRequest;
|
|
||||||
Assert.NotNull(recoveredRecord);
|
|
||||||
Assert.Equal(expected, recoveredRecord.JobMessageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string DoubleQuotify(string text)
|
|
||||||
{
|
|
||||||
return text.Replace('\'', '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi.Tests;
|
|
||||||
|
|
||||||
public sealed class AnnotationsL0
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void ToAnnotation_ValidIssueWithMessage_ReturnsAnnotation()
|
|
||||||
{
|
|
||||||
var issue = new Issue
|
|
||||||
{
|
|
||||||
Type = IssueType.Error,
|
|
||||||
Message = "An error occurred",
|
|
||||||
IsInfrastructureIssue = true
|
|
||||||
};
|
|
||||||
|
|
||||||
issue.Data.Add(RunIssueKeys.File, "test.txt");
|
|
||||||
issue.Data.Add(RunIssueKeys.Line, "5");
|
|
||||||
issue.Data.Add(RunIssueKeys.Col, "10");
|
|
||||||
issue.Data.Add(RunIssueKeys.EndLine, "8");
|
|
||||||
issue.Data.Add(RunIssueKeys.EndColumn, "20");
|
|
||||||
issue.Data.Add(RunIssueKeys.LogLineNumber, "2");
|
|
||||||
|
|
||||||
var annotation = issue.ToAnnotation();
|
|
||||||
|
|
||||||
Assert.NotNull(annotation);
|
|
||||||
Assert.Equal(AnnotationLevel.FAILURE, annotation.Value.Level);
|
|
||||||
Assert.Equal("An error occurred", annotation.Value.Message);
|
|
||||||
Assert.Equal("test.txt", annotation.Value.Path);
|
|
||||||
Assert.Equal(5, annotation.Value.StartLine);
|
|
||||||
Assert.Equal(8, annotation.Value.EndLine);
|
|
||||||
Assert.Equal(10, annotation.Value.StartColumn);
|
|
||||||
Assert.Equal(20, annotation.Value.EndColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ToAnnotation_ValidIssueWithEmptyMessage_ReturnsNull()
|
|
||||||
{
|
|
||||||
var issue = new Issue
|
|
||||||
{
|
|
||||||
Type = IssueType.Warning,
|
|
||||||
Message = string.Empty
|
|
||||||
};
|
|
||||||
|
|
||||||
var annotation = issue.ToAnnotation();
|
|
||||||
|
|
||||||
Assert.Null(annotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ToAnnotation_ValidIssueWithMessageInData_ReturnsAnnotation()
|
|
||||||
{
|
|
||||||
var issue = new Issue
|
|
||||||
{
|
|
||||||
Type = IssueType.Warning,
|
|
||||||
Message = string.Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
issue.Data.Add(RunIssueKeys.Message, "A warning occurred");
|
|
||||||
|
|
||||||
var annotation = issue.ToAnnotation();
|
|
||||||
|
|
||||||
Assert.NotNull(annotation);
|
|
||||||
Assert.Equal(AnnotationLevel.WARNING, annotation.Value.Level);
|
|
||||||
Assert.Equal("A warning occurred", annotation.Value.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -56,9 +56,12 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
var traceListener = new HostTraceListener(TraceFileName);
|
var traceListener = new HostTraceListener(TraceFileName);
|
||||||
_secretMasker = new SecretMasker();
|
var encoders = new List<ValueEncoder>()
|
||||||
_secretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape);
|
{
|
||||||
_secretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
ValueEncoders.JsonStringEscape,
|
||||||
|
ValueEncoders.UriDataEscape
|
||||||
|
};
|
||||||
|
_secretMasker = new SecretMasker(encoders);
|
||||||
_traceManager = new TraceManager(traceListener, null, _secretMasker);
|
_traceManager = new TraceManager(traceListener, null, _secretMasker);
|
||||||
_trace = GetTrace(nameof(TestHostContext));
|
_trace = GetTrace(nameof(TestHostContext));
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -89,128 +89,5 @@ namespace GitHub.Runner.Common.Tests.Util
|
|||||||
Assert.Equal(gitPath, gitPath2);
|
Assert.Equal(gitPath, gitPath2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void WhichHandlesSymlinkToTargetFullPath()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using TestHostContext hc = new TestHostContext(this);
|
|
||||||
Tracing trace = hc.GetTrace();
|
|
||||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
|
||||||
#if OS_WINDOWS
|
|
||||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
|
||||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
|
||||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
|
||||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}.exe";
|
|
||||||
#else
|
|
||||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
|
||||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
|
||||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
|
||||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
|
||||||
|
|
||||||
|
|
||||||
using (File.Create(target))
|
|
||||||
{
|
|
||||||
File.CreateSymbolicLink(symlink, target);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
var result = WhichUtil.Which(symlinkName, require: true, trace: trace);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find symlink through: {nameof(WhichUtil.Which)}");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
File.Delete(symlink);
|
|
||||||
File.Delete(target);
|
|
||||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void WhichHandlesSymlinkToTargetRelativePath()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using TestHostContext hc = new TestHostContext(this);
|
|
||||||
Tracing trace = hc.GetTrace();
|
|
||||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
|
||||||
#if OS_WINDOWS
|
|
||||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
|
||||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
|
||||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
|
||||||
string targetName = $"target-{Guid.NewGuid()}.exe";
|
|
||||||
string target = Path.GetTempPath() + targetName;
|
|
||||||
#else
|
|
||||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
|
||||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
|
||||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
|
||||||
string targetName = $"target-{Guid.NewGuid()}";
|
|
||||||
string target = Path.GetTempPath() + targetName;
|
|
||||||
#endif
|
|
||||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
|
||||||
|
|
||||||
|
|
||||||
using (File.Create(target))
|
|
||||||
{
|
|
||||||
File.CreateSymbolicLink(symlink, targetName);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
var result = WhichUtil.Which(symlinkName, require: true, trace: trace);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find {symlinkName} through: {nameof(WhichUtil.Which)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
File.Delete(symlink);
|
|
||||||
File.Delete(target);
|
|
||||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
|
||||||
|
|
||||||
}
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void WhichThrowsWhenSymlinkBroken()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using TestHostContext hc = new TestHostContext(this);
|
|
||||||
Tracing trace = hc.GetTrace();
|
|
||||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
|
||||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
|
||||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
|
||||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}.exe";
|
|
||||||
#else
|
|
||||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
|
||||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
|
||||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
string target = "no-such-file-cf7e351f";
|
|
||||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
|
||||||
|
|
||||||
File.CreateSymbolicLink(brokenSymlink, target);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
var exception = Assert.Throws<FileNotFoundException>(()=>WhichUtil.Which(brokenSymlinkName, require: true, trace: trace));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(brokenSymlinkName, exception.FileName);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
File.Delete(brokenSymlink);
|
|
||||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -32,10 +32,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.GetTrace().Info($"{tag} {line}");
|
hc.GetTrace().Info($"{tag} {line}");
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
|
||||||
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((Issue issue, string message) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandManager.EnablePluginInternalCommand();
|
_commandManager.EnablePluginInternalCommand();
|
||||||
@@ -59,10 +59,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.GetTrace().Info($"{tag} {line}");
|
hc.GetTrace().Info($"{tag} {line}");
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
|
||||||
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((Issue issue, string message) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandManager.EnablePluginInternalCommand();
|
_commandManager.EnablePluginInternalCommand();
|
||||||
@@ -92,10 +92,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
|
||||||
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((Issue issue, string message) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
private Mock<IDockerCommandManager> _dockerManager;
|
private Mock<IDockerCommandManager> _dockerManager;
|
||||||
private Mock<IExecutionContext> _ec;
|
private Mock<IExecutionContext> _ec;
|
||||||
private Mock<IJobServer> _jobServer;
|
private Mock<IJobServer> _jobServer;
|
||||||
private Mock<ILaunchServer> _launchServer;
|
|
||||||
private Mock<IRunnerPluginManager> _pluginManager;
|
private Mock<IRunnerPluginManager> _pluginManager;
|
||||||
private TestHostContext _hc;
|
private TestHostContext _hc;
|
||||||
private ActionManager _actionManager;
|
private ActionManager _actionManager;
|
||||||
@@ -2148,7 +2147,7 @@ runs:
|
|||||||
_ec.Object.Global.FileTable = new List<String>();
|
_ec.Object.Global.FileTable = new List<String>();
|
||||||
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||||
|
|
||||||
_dockerManager = new Mock<IDockerCommandManager>();
|
_dockerManager = new Mock<IDockerCommandManager>();
|
||||||
@@ -2176,25 +2175,6 @@ runs:
|
|||||||
return Task.FromResult(result);
|
return Task.FromResult(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
_launchServer = new Mock<ILaunchServer>();
|
|
||||||
_launchServer.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>()))
|
|
||||||
.Returns((Guid planId, Guid jobId, ActionReferenceList actions, CancellationToken cancellationToken) =>
|
|
||||||
{
|
|
||||||
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
|
|
||||||
foreach (var action in actions.Actions)
|
|
||||||
{
|
|
||||||
var key = $"{action.NameWithOwner}@{action.Ref}";
|
|
||||||
result.Actions[key] = new ActionDownloadInfo
|
|
||||||
{
|
|
||||||
NameWithOwner = action.NameWithOwner,
|
|
||||||
Ref = action.Ref,
|
|
||||||
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
|
||||||
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return Task.FromResult(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
_pluginManager = new Mock<IRunnerPluginManager>();
|
_pluginManager = new Mock<IRunnerPluginManager>();
|
||||||
_pluginManager.Setup(x => x.GetPluginAction(It.IsAny<string>())).Returns(new RunnerPluginActionInfo() { PluginTypeName = "plugin.class, plugin", PostPluginTypeName = "plugin.cleanup, plugin" });
|
_pluginManager.Setup(x => x.GetPluginAction(It.IsAny<string>())).Returns(new RunnerPluginActionInfo() { PluginTypeName = "plugin.class, plugin", PostPluginTypeName = "plugin.cleanup, plugin" });
|
||||||
|
|
||||||
@@ -2203,7 +2183,6 @@ runs:
|
|||||||
|
|
||||||
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
|
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
|
||||||
_hc.SetSingleton<IJobServer>(_jobServer.Object);
|
_hc.SetSingleton<IJobServer>(_jobServer.Object);
|
||||||
_hc.SetSingleton<ILaunchServer>(_launchServer.Object);
|
|
||||||
_hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object);
|
_hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object);
|
||||||
_hc.SetSingleton<IActionManifestManager>(actionManifest);
|
_hc.SetSingleton<IActionManifestManager>(actionManifest);
|
||||||
_hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
_hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|||||||
@@ -670,7 +670,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
Teardown();
|
Teardown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
@@ -715,7 +715,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
//Assert
|
//Assert
|
||||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||||
Assert.Contains($"Fail to load {action_path}", err.Message);
|
Assert.Contains($"Fail to load {action_path}", err.Message);
|
||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<string>()), Times.Once);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -860,7 +860,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Teardown()
|
private void Teardown()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
@@ -366,7 +366,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal("invalid1", finialInputs["invalid1"]);
|
Assert.Equal("invalid1", finialInputs["invalid1"]);
|
||||||
Assert.Equal("invalid2", finialInputs["invalid2"]);
|
Assert.Equal("invalid2", finialInputs["invalid2"]);
|
||||||
|
|
||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -485,7 +485,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
_ec.Object.Global.Variables = new Variables(_hc, new Dictionary<string, VariableValue>());
|
_ec.Object.Global.Variables = new Variables(_hc, new Dictionary<string, VariableValue>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
|
|
||||||
_hc.SetSingleton<IActionManager>(_actionManager.Object);
|
_hc.SetSingleton<IActionManager>(_actionManager.Object);
|
||||||
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
|
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
|
||||||
|
|||||||
@@ -247,16 +247,12 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
Variables = _variables,
|
Variables = _variables,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
||||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
||||||
{
|
{
|
||||||
var resolvedMessage = issue.Message;
|
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
||||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
||||||
{
|
_trace.Info($"Issue '{issue.Type}': {message}");
|
||||||
resolvedMessage = logOptions.LogMessageOverride;
|
|
||||||
}
|
|
||||||
_issues.Add(new(issue, resolvedMessage));
|
|
||||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
@@ -272,4 +268,4 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return hostContext;
|
return hostContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -52,43 +52,42 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
// Act.
|
// Act.
|
||||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
// Flood the ExecutionContext with errors and warnings (past its max capacity of 10).
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.ErrorCount > 0)), Times.AtLeast(10));
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.ErrorCount == 15)), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.WarningCount > 0)), Times.AtLeast(10));
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.WarningCount == 14)), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Error).Count() == 10)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Error).Count() == 10)), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Warning).Count() == 10)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Warning).Count() == 10)), Times.AtLeastOnce);
|
||||||
}
|
}
|
||||||
@@ -191,9 +190,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
bigMessage += "a";
|
bigMessage += "a";
|
||||||
}
|
}
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage });
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -204,61 +203,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void AddIssue_OverrideLogMessage()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = CreateTestContext())
|
|
||||||
{
|
|
||||||
// Arrange: Create a job request message.
|
|
||||||
TaskOrchestrationPlanReference plan = new();
|
|
||||||
TimelineReference timeline = new();
|
|
||||||
Guid jobId = Guid.NewGuid();
|
|
||||||
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);
|
|
||||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
|
||||||
{
|
|
||||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
|
||||||
Id = "github",
|
|
||||||
Version = "sha1"
|
|
||||||
});
|
|
||||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
|
||||||
var pagingLogger = new Mock<IPagingLogger>();
|
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
|
||||||
|
|
||||||
hc.EnqueueInstance(pagingLogger.Object);
|
|
||||||
hc.SetSingleton(jobServerQueue.Object);
|
|
||||||
|
|
||||||
var ec = new Runner.Worker.ExecutionContext();
|
|
||||||
ec.Initialize(hc);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
|
||||||
|
|
||||||
var issueMessage = "Message embedded in issue.";
|
|
||||||
var overrideMessage = "Message override.";
|
|
||||||
var options = new ExecutionContextLogOptions(true, overrideMessage);
|
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = issueMessage }, options);
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = issueMessage }, options);
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = issueMessage }, options);
|
|
||||||
|
|
||||||
// Finally, add a variation that DOESN'T override the message.
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = issueMessage }, ExecutionContextLogOptions.Default);
|
|
||||||
|
|
||||||
ec.Complete();
|
|
||||||
|
|
||||||
// Assert.
|
|
||||||
jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<long?>()), Times.Exactly(4));
|
|
||||||
jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.Is<string>(text => text.EndsWith(overrideMessage)), It.IsAny<long?>()), Times.Exactly(3));
|
|
||||||
jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.Is<string>(text => text.EndsWith(issueMessage)), It.IsAny<long?>()), Times.Exactly(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -298,15 +242,13 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
||||||
embeddedStep.Start();
|
embeddedStep.Start();
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" });
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" });
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" });
|
||||||
|
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.AtLeastOnce);
|
||||||
// Verify that Error/Warning/Notice issues added to embedded steps don't get sent up to the server.
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.Never);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.Never);
|
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.Never);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,12 +626,12 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
ec.StepTelemetry.StepId = Guid.NewGuid();
|
ec.StepTelemetry.StepId = Guid.NewGuid();
|
||||||
ec.StepTelemetry.Stage = "main";
|
ec.StepTelemetry.Stage = "main";
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -750,9 +692,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
||||||
embeddedStep.StepTelemetry.Ref = "v2";
|
embeddedStep.StepTelemetry.Ref = "v2";
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
||||||
|
|
||||||
embeddedStep.PublishStepTelemetry();
|
embeddedStep.PublishStepTelemetry();
|
||||||
|
|
||||||
@@ -814,9 +756,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
||||||
embeddedStep.StepTelemetry.Ref = "v2";
|
embeddedStep.StepTelemetry.Ref = "v2";
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -985,7 +927,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
inputVarsContext["VARIABLE_2"] = new StringContextData("value2");
|
inputVarsContext["VARIABLE_2"] = new StringContextData("value2");
|
||||||
jobRequest.ContextData["vars"] = inputVarsContext;
|
jobRequest.ContextData["vars"] = inputVarsContext;
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
// Arrange: Setup the paging logger.
|
||||||
var pagingLogger1 = new Mock<IPagingLogger>();
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
hc.EnqueueInstance(pagingLogger1.Object);
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
@@ -999,7 +941,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var expected = new DictionaryContextData();
|
var expected = new DictionaryContextData();
|
||||||
expected["VARIABLE_1"] = new StringContextData("value1");
|
expected["VARIABLE_1"] = new StringContextData("value1");
|
||||||
expected["VARIABLE_2"] = new StringContextData("value1");
|
expected["VARIABLE_2"] = new StringContextData("value1");
|
||||||
|
|
||||||
Assert.True(ExpressionValuesAssertEqual(expected, jobContext.ExpressionValues["vars"] as DictionaryContextData));
|
Assert.True(ExpressionValuesAssertEqual(expected, jobContext.ExpressionValues["vars"] as DictionaryContextData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1030,7 +972,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true");
|
inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true");
|
||||||
jobRequest.ContextData["vars"] = inputVarsContext;
|
jobRequest.ContextData["vars"] = inputVarsContext;
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
// Arrange: Setup the paging logger.
|
||||||
var pagingLogger1 = new Mock<IPagingLogger>();
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
hc.EnqueueInstance(pagingLogger1.Object);
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
@@ -1041,7 +983,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
|
|
||||||
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
||||||
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
||||||
}
|
}
|
||||||
@@ -1076,7 +1018,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
jobRequest.Variables[Constants.Variables.Actions.StepDebug] = "false";
|
jobRequest.Variables[Constants.Variables.Actions.StepDebug] = "false";
|
||||||
jobRequest.Variables[Constants.Variables.Actions.RunnerDebug] = "false";
|
jobRequest.Variables[Constants.Variables.Actions.RunnerDebug] = "false";
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
// Arrange: Setup the paging logger.
|
||||||
var pagingLogger1 = new Mock<IPagingLogger>();
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
hc.EnqueueInstance(pagingLogger1.Object);
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
@@ -1087,7 +1029,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
|
|
||||||
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
||||||
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
||||||
}
|
}
|
||||||
@@ -1119,4 +1061,4 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -984,15 +984,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_onMatcherChanged = handler;
|
_onMatcherChanged = handler;
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
||||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
||||||
{
|
{
|
||||||
var resolvedMessage = issue.Message;
|
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
||||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
|
||||||
{
|
|
||||||
resolvedMessage = logOptions.LogMessageOverride;
|
|
||||||
}
|
|
||||||
_issues.Add(new(issue, resolvedMessage));
|
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
|
|||||||
@@ -413,16 +413,12 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
||||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
||||||
{
|
{
|
||||||
var resolvedMessage = issue.Message;
|
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
||||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
||||||
{
|
_trace.Info($"Issue '{issue.Type}': {message}");
|
||||||
resolvedMessage = logOptions.LogMessageOverride;
|
|
||||||
}
|
|
||||||
_issues.Add(new(issue, resolvedMessage));
|
|
||||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
|
|||||||
@@ -411,16 +411,12 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
||||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
||||||
{
|
{
|
||||||
var resolvedMessage = issue.Message;
|
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
||||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
||||||
{
|
_trace.Info($"Issue '{issue.Type}': {message}");
|
||||||
resolvedMessage = logOptions.LogMessageOverride;
|
|
||||||
}
|
|
||||||
_issues.Add(new(issue, resolvedMessage));
|
|
||||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
|
|||||||
@@ -413,16 +413,12 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
||||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
||||||
{
|
{
|
||||||
var resolvedMessage = issue.Message;
|
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
||||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
||||||
{
|
_trace.Info($"Issue '{issue.Type}': {message}");
|
||||||
resolvedMessage = logOptions.LogMessageOverride;
|
|
||||||
}
|
|
||||||
_issues.Add(new(issue, resolvedMessage));
|
|
||||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
@@ -434,8 +430,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
||||||
.Callback((string name, string value, out string reference) =>
|
.Callback((string name, string value, out string reference) =>
|
||||||
{
|
{
|
||||||
reference = value;
|
reference = value;
|
||||||
_outputs[name] = value;
|
_outputs[name] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
// SetOutputFileCommand
|
// SetOutputFileCommand
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -337,7 +337,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(2, variableSet.Step.Length);
|
Assert.Equal(2, variableSet.Step.Length);
|
||||||
variableSet.Step[0].Verify(x => x.RunAsync());
|
variableSet.Step[0].Verify(x => x.RunAsync());
|
||||||
variableSet.Step[1].Verify(x => x.RunAsync(), variableSet.Expected ? Times.Once() : Times.Never());
|
variableSet.Step[1].Verify(x => x.RunAsync(), variableSet.Expected ? Times.Once() : Times.Never());
|
||||||
@@ -590,7 +590,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
step.Setup(x => x.Condition).Returns(condition);
|
step.Setup(x => x.Condition).Returns(condition);
|
||||||
step.Setup(x => x.ContinueOnError).Returns(new BooleanToken(null, null, null, continueOnError));
|
step.Setup(x => x.ContinueOnError).Returns(new BooleanToken(null, null, null, continueOnError));
|
||||||
step.Setup(x => x.Action)
|
step.Setup(x => x.Action)
|
||||||
.Returns(new GitHub.DistributedTask.Pipelines.ActionStep()
|
.Returns(new DistributedTask.Pipelines.ActionStep()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
minimal: |
|
|
||||||
{ "Name": "minimal" }
|
|
||||||
invalid-attempt-value: |
|
|
||||||
{
|
|
||||||
"Name": "invalid-attempt-value",
|
|
||||||
"Attempt": -99
|
|
||||||
}
|
|
||||||
zero-attempt-value: |
|
|
||||||
{
|
|
||||||
"Name": "zero-attempt-value",
|
|
||||||
"Attempt": 0
|
|
||||||
}
|
|
||||||
legacy-nulls: |
|
|
||||||
{
|
|
||||||
"Name": "legacy-nulls",
|
|
||||||
"ErrorCount": null,
|
|
||||||
"WarningCount": null,
|
|
||||||
"NoticeCount": null
|
|
||||||
}
|
|
||||||
missing-counts: |
|
|
||||||
{
|
|
||||||
"Name": "missing-counts"
|
|
||||||
}
|
|
||||||
non-zero-counts: |
|
|
||||||
{
|
|
||||||
"Name": "non-zero-counts",
|
|
||||||
"ErrorCount": 10,
|
|
||||||
"WarningCount": 20,
|
|
||||||
"NoticeCount": 30
|
|
||||||
}
|
|
||||||
explicit-null-collections: |
|
|
||||||
{
|
|
||||||
"Name": "explicit-null-collections",
|
|
||||||
"Issues": null,
|
|
||||||
"PreviousAttempts": null,
|
|
||||||
"Variables": null
|
|
||||||
}
|
|
||||||
lean: |
|
|
||||||
{
|
|
||||||
"Id": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"Name": "lean",
|
|
||||||
"LastModified": "\/Date(1679073003252+0000)\/",
|
|
||||||
"Issues": [
|
|
||||||
{
|
|
||||||
"Type": 0,
|
|
||||||
"Category": null,
|
|
||||||
"Message": null,
|
|
||||||
"IsInfrastructureIssue": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Variables": [
|
|
||||||
{ "Key": "x", "Value": { "Value": "1" } },
|
|
||||||
{ "Key": "y", "Value": { "Value": "2" } },
|
|
||||||
{ "Key": "z", "Value": { "Value": "3" } }
|
|
||||||
],
|
|
||||||
"Attempt": 4,
|
|
||||||
"PreviousAttempts": [
|
|
||||||
{ "Attempt": 1 },
|
|
||||||
{ "Attempt": 2 },
|
|
||||||
{ "Attempt": 3 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
duplicate-variable-keys: |
|
|
||||||
{
|
|
||||||
"Name": "duplicate-variable-keys",
|
|
||||||
"Variables": [
|
|
||||||
{ "Key": "aaa", "Value": { "Value": "a.1" } },
|
|
||||||
{ "Key": "AAA", "Value": { "Value": "a.2" } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
2.304.0
|
2.303.0
|
||||||
|
|||||||
Reference in New Issue
Block a user