mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
26 Commits
v2.304.1
...
LogTemplat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3c99852af | ||
|
|
b697b12601 | ||
|
|
3a2817a9ae | ||
|
|
393dd9436e | ||
|
|
2be59d6c10 | ||
|
|
4fa33ad4ec | ||
|
|
d056e8fa08 | ||
|
|
7c1ea89b87 | ||
|
|
238443852a | ||
|
|
3a1376f90e | ||
|
|
50b3edff3c | ||
|
|
58f7a379a1 | ||
|
|
e13627df81 | ||
|
|
48cbee08f9 | ||
|
|
21b49c542c | ||
|
|
8db8bbe13a | ||
|
|
49b04976f4 | ||
|
|
eeb0cf6f1e | ||
|
|
f8a28c3c4e | ||
|
|
1bc14f0607 | ||
|
|
22d1938ac4 | ||
|
|
229b9b8ecc | ||
|
|
896152d78e | ||
|
|
8d74a9ead6 | ||
|
|
77b8586a03 | ||
|
|
c8c47d4f27 |
@@ -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/corefx/blob/master/Documentation/coding-guidelines/coding-style.md)
|
https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md)
|
||||||
|
|
||||||
### Format C# Code
|
### Format C# Code
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
|
|||||||
|
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_ARCH="x64"
|
ARG RUNNER_ARCH="x64"
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.3.1
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.3.2
|
||||||
ARG DOCKER_VERSION=20.10.23
|
ARG DOCKER_VERSION=20.10.23
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ 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
|
||||||
@@ -154,6 +155,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||||
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
|
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
|
||||||
|
public static readonly string LogTemplateErrorsToTraceWriter = "DistributedTask.LogTemplateErrorsToTraceWriter";
|
||||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||||
}
|
}
|
||||||
@@ -261,6 +263,7 @@ 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";
|
public static readonly string OrchestrationId = "system.orchestrationId";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -254,7 +254,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.Error(ex);
|
Trace.Verbose(ex.ToString());
|
||||||
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
|
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
|
||||||
{
|
{
|
||||||
// let's consider failure percentage
|
// let's consider failure percentage
|
||||||
|
|||||||
@@ -756,17 +756,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 != null && rec.ErrorCount > 0)
|
if (rec.ErrorCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.ErrorCount = rec.ErrorCount;
|
timelineRecord.ErrorCount = rec.ErrorCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec.WarningCount != null && rec.WarningCount > 0)
|
if (rec.WarningCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.WarningCount = rec.WarningCount;
|
timelineRecord.WarningCount = rec.WarningCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec.NoticeCount != null && rec.NoticeCount > 0)
|
if (rec.NoticeCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.NoticeCount = rec.NoticeCount;
|
timelineRecord.NoticeCount = rec.NoticeCount;
|
||||||
}
|
}
|
||||||
@@ -797,7 +797,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 && record.Issues.Count > 0)
|
if (record.Issues != null)
|
||||||
{
|
{
|
||||||
foreach (var issue in record.Issues)
|
foreach (var issue in record.Issues)
|
||||||
{
|
{
|
||||||
@@ -807,7 +807,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.Variables != null && record.Variables.Count > 0)
|
if (record.Variables != null)
|
||||||
{
|
{
|
||||||
foreach (var variable in record.Variables)
|
foreach (var variable in record.Variables)
|
||||||
{
|
{
|
||||||
|
|||||||
42
src/Runner.Common/LaunchServer.cs
Normal file
42
src/Runner.Common/LaunchServer.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,13 +131,13 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
private void InitializeWebsocketClient(string liveConsoleFeedUrl, string accessToken, TimeSpan delay, bool retryConnection = false)
|
private void InitializeWebsocketClient(string liveConsoleFeedUrl, string accessToken, TimeSpan delay, bool retryConnection = false)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(accessToken))
|
if (string.IsNullOrEmpty(accessToken))
|
||||||
{
|
{
|
||||||
Trace.Info($"No access token from server");
|
Trace.Info($"No access token from server");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
|
if (string.IsNullOrEmpty(liveConsoleFeedUrl))
|
||||||
{
|
{
|
||||||
Trace.Info($"No live console feed url from server");
|
Trace.Info($"No live console feed url from server");
|
||||||
return;
|
return;
|
||||||
@@ -222,7 +222,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
var delay = BackoffTimerHelper.GetRandomBackoff(MinDelayForWebsocketReconnect, MaxDelayForWebsocketReconnect);
|
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.Info($"Websocket is not open, let's attempt to connect back again with random backoff {delay} ms.");
|
||||||
Trace.Error(ex);
|
Trace.Verbose(ex.ToString());
|
||||||
retries++;
|
retries++;
|
||||||
InitializeWebsocketClient(_liveConsoleFeedUrl, _token, delay);
|
InitializeWebsocketClient(_liveConsoleFeedUrl, _token, delay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||||
|
|
||||||
Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken token);
|
Task CompleteJobAsync(
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@@ -52,14 +59,22 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return RetryRequest<AgentJobRequestMessage>(
|
return RetryRequest<AgentJobRequestMessage>(
|
||||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken);
|
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken,
|
||||||
|
shouldRetry: ex => ex is not TaskOrchestrationJobAlreadyAcquiredException);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken cancellationToken)
|
public Task CompleteJobAsync(
|
||||||
|
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, cancellationToken), cancellationToken);
|
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, cancellationToken), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -83,7 +83,8 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
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;
|
||||||
@@ -96,7 +97,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)
|
catch (Exception ex) when (retryCount < maxRetryAttemptsCount && (shouldRetry == null || shouldRetry(ex)))
|
||||||
{
|
{
|
||||||
Trace.Error("Catch exception during request");
|
Trace.Error("Catch exception during request");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ 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,
|
||||||
@@ -85,6 +86,7 @@ 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);
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
||||||
runnerSettings.ServerUrl = authResult.TenantUrl;
|
runnerSettings.ServerUrl = authResult.TenantUrl;
|
||||||
runnerSettings.UseV2Flow = authResult.UseV2Flow;
|
runnerSettings.UseV2Flow = authResult.UseV2Flow;
|
||||||
_term.WriteLine($"Using V2 flow: {runnerSettings.UseV2Flow}");
|
Trace.Info($"Using V2 flow: {runnerSettings.UseV2Flow}");
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
}
|
}
|
||||||
@@ -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);
|
agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate, command.NoDefaultLabels);
|
||||||
|
|
||||||
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);
|
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate, command.NoDefaultLabels);
|
||||||
|
|
||||||
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)
|
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate, bool noDefaultLabels)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(agent, nameof(agent));
|
ArgUtil.NotNull(agent, nameof(agent));
|
||||||
agent.Authorization = new TaskAgentAuthorization
|
agent.Authorization = new TaskAgentAuthorization
|
||||||
@@ -571,9 +571,16 @@ 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("self-hosted", LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, 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)
|
||||||
{
|
{
|
||||||
@@ -583,7 +590,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
|
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate, bool noDefaultLabels)
|
||||||
{
|
{
|
||||||
TaskAgent agent = new(agentName)
|
TaskAgent agent = new(agentName)
|
||||||
{
|
{
|
||||||
@@ -598,9 +605,16 @@ 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("self-hosted", LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, 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,6 +15,7 @@ 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
|
||||||
@@ -372,6 +373,8 @@ 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())
|
||||||
@@ -379,8 +382,6 @@ 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);
|
||||||
@@ -405,7 +406,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, lockToken, TaskResult.Canceled);
|
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, TaskResult.Canceled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +545,6 @@ 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);
|
await ForceFailJob(jobServer, message, detailInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,7 +567,7 @@ namespace GitHub.Runner.Listener
|
|||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// complete job request
|
// complete job request
|
||||||
await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo);
|
await CompleteJobRequestAsync(_poolId, message, systemConnection, 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, lockToken, resultOnAbandonOrCancel);
|
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, resultOnAbandonOrCancel);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -1065,7 +1065,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
|
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, ServiceEndpoint systemConnection, Guid lockToken, TaskResult result, string detailInfo = null)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
@@ -1077,7 +1077,23 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
if (this._isRunServiceJob)
|
if (this._isRunServiceJob)
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Skip FinishAgentRequest call from Listener because MessageType is {message.MessageType}");
|
var runServer = await GetRunServerAsync(systemConnection);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1117,7 +1133,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 errorMessage)
|
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
|
||||||
{
|
{
|
||||||
if (server is IJobServer jobServer)
|
if (server is IJobServer jobServer)
|
||||||
{
|
{
|
||||||
@@ -1129,34 +1145,11 @@ 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));
|
||||||
|
|
||||||
try
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||||
{
|
|
||||||
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)
|
||||||
@@ -1167,13 +1160,13 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", errorMessage);
|
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", detailInfo);
|
||||||
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)
|
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
|
||||||
{
|
{
|
||||||
if (server is IJobServer jobServer)
|
if (server is IJobServer jobServer)
|
||||||
{
|
{
|
||||||
@@ -1192,7 +1185,15 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, CancellationToken.None);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,7 +549,17 @@ 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);
|
||||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
try
|
||||||
|
{
|
||||||
|
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);
|
||||||
@@ -673,7 +683,8 @@ 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 Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
--labels string Custom labels that will be added to the runner. This option is mandatory if --no-default-labels is used.
|
||||||
|
--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 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);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
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);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ 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
|
||||||
{
|
{
|
||||||
@@ -100,7 +102,19 @@ 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");
|
||||||
var result = await PrepareActionsRecursiveAsync(executionContext, state, actions, depth, rootStepId);
|
PrepareActionsState result = new PrepareActionsState();
|
||||||
|
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)
|
||||||
@@ -302,6 +316,18 @@ namespace GitHub.Runner.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
||||||
|
{
|
||||||
|
if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
||||||
|
{
|
||||||
|
Trace.Info("Load action that will run container through container hooks.");
|
||||||
|
var containerAction = action.Reference as Pipelines.ContainerRegistryReference;
|
||||||
|
definition.Data.Execution = new ContainerActionExecutionData()
|
||||||
|
{
|
||||||
|
Image = containerAction.Image,
|
||||||
|
};
|
||||||
|
Trace.Info($"Using action container image: {containerAction.Image}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Trace.Info("Load action that reference container from registry.");
|
Trace.Info("Load action that reference container from registry.");
|
||||||
CachedActionContainers.TryGetValue(action.Id, out var container);
|
CachedActionContainers.TryGetValue(action.Id, out var container);
|
||||||
@@ -313,6 +339,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
Trace.Info($"Using action container image: {container.ContainerImage}.");
|
Trace.Info($"Using action container image: {container.ContainerImage}.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository)
|
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository)
|
||||||
{
|
{
|
||||||
string actionDirectory = null;
|
string actionDirectory = null;
|
||||||
@@ -648,13 +675,21 @@ 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 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);
|
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.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
||||||
{
|
{
|
||||||
var templateContext = CreateTemplateContext(executionContext);
|
var templateContext = CreateTemplateContext(executionContext, null, LogTemplateErrorsToTraceWriter(executionContext));
|
||||||
ActionDefinitionData actionDefinition = new();
|
ActionDefinitionData actionDefinition = new();
|
||||||
|
|
||||||
// Clean up file name real quick
|
// Clean up file name real quick
|
||||||
@@ -303,7 +303,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private TemplateContext CreateTemplateContext(
|
private TemplateContext CreateTemplateContext(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
IDictionary<string, PipelineContextData> extraExpressionValues = null)
|
IDictionary<string, PipelineContextData> extraExpressionValues = null,
|
||||||
|
bool addErrorsToTraceWriter = true)
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -315,6 +316,7 @@ namespace GitHub.Runner.Worker
|
|||||||
maxBytes: 10 * 1024 * 1024),
|
maxBytes: 10 * 1024 * 1024),
|
||||||
Schema = _actionManifestSchema,
|
Schema = _actionManifestSchema,
|
||||||
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
||||||
|
LogErrorsToTraceWriter = addErrorsToTraceWriter
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expression values from execution context
|
// Expression values from execution context
|
||||||
@@ -539,6 +541,13 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool LogTemplateErrorsToTraceWriter(IExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
if (executionContext == null || executionContext.Global == null || executionContext.Global.EnvironmentVariables == null) return true;
|
||||||
|
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Runner.Features.LogTemplateErrorsToTraceWriter, out var logErrorsAsDebug);
|
||||||
|
return StringUtil.ConvertToBoolean(logErrorsAsDebug, defaultValue: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace GitHub.Runner.Worker.Container.ContainerHooks
|
|||||||
public HookContainer(ContainerInfo container)
|
public HookContainer(ContainerInfo container)
|
||||||
{
|
{
|
||||||
Image = container.ContainerImage;
|
Image = container.ContainerImage;
|
||||||
EntryPointArgs = container.ContainerEntryPointArgs?.Split(' ').Select(arg => arg.Trim()) ?? new List<string>();
|
EntryPointArgs = container.ContainerEntryPointArgs?.Split(' ').Select(arg => arg.Trim()).Where(arg => !string.IsNullOrEmpty(arg)) ?? new List<string>();
|
||||||
EntryPoint = container.ContainerEntryPoint;
|
EntryPoint = container.ContainerEntryPoint;
|
||||||
WorkingDirectory = container.ContainerWorkDirectory;
|
WorkingDirectory = container.ContainerWorkDirectory;
|
||||||
CreateOptions = container.ContainerCreateOptions;
|
CreateOptions = container.ContainerCreateOptions;
|
||||||
|
|||||||
@@ -18,15 +18,23 @@ 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;
|
||||||
|
using constants = GitHub.Runner.Common.Constants;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
public class ExecutionContextType
|
public static class ExecutionContextType
|
||||||
{
|
{
|
||||||
public static string Job = "Job";
|
public const string Job = "Job";
|
||||||
public static string Task = "Task";
|
public const 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))]
|
||||||
@@ -92,7 +100,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, string message = null);
|
void AddIssue(Issue issue, ExecutionContextLogOptions logOptions);
|
||||||
void Progress(int percentage, string currentOperation = null);
|
void Progress(int percentage, string currentOperation = null);
|
||||||
void UpdateDetailTimelineRecord(TimelineRecord record);
|
void UpdateDetailTimelineRecord(TimelineRecord record);
|
||||||
|
|
||||||
@@ -118,7 +126,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
{
|
{
|
||||||
private const int _maxIssueCount = 10;
|
private const int _maxCountPerIssueType = 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
|
||||||
@@ -126,8 +134,10 @@ 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;
|
||||||
|
|
||||||
@@ -135,7 +145,6 @@ 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;
|
||||||
@@ -149,6 +158,29 @@ 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; }
|
||||||
@@ -161,7 +193,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; } = new ActionsStepTelemetry();
|
public ActionsStepTelemetry StepTelemetry { get; private init; }
|
||||||
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>();
|
||||||
|
|
||||||
@@ -186,7 +218,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 set; }
|
public bool IsEmbedded { get; private init; }
|
||||||
|
|
||||||
public TaskResult? Result
|
public TaskResult? Result
|
||||||
{
|
{
|
||||||
@@ -321,7 +353,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
var child = new ExecutionContext();
|
var child = new ExecutionContext(this, isEmbedded);
|
||||||
child.Initialize(HostContext);
|
child.Initialize(HostContext);
|
||||||
child.Global = Global;
|
child.Global = Global;
|
||||||
child.ScopeName = scopeName;
|
child.ScopeName = scopeName;
|
||||||
@@ -346,7 +378,6 @@ 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)
|
||||||
@@ -367,11 +398,9 @@ 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.IsEmbedded = isEmbedded;
|
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName();
|
||||||
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName(); ;
|
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
@@ -413,13 +442,24 @@ 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 = DateTime.UtcNow;
|
_record.FinishTime = now;
|
||||||
_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.
|
||||||
@@ -427,7 +467,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
foreach (var record in _detailRecords)
|
foreach (var record in _detailRecords)
|
||||||
{
|
{
|
||||||
record.Value.FinishTime = record.Value.FinishTime ?? DateTime.UtcNow;
|
record.Value.FinishTime = record.Value.FinishTime ?? now;
|
||||||
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;
|
||||||
@@ -438,14 +478,26 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
PublishStepTelemetry();
|
PublishStepTelemetry();
|
||||||
|
|
||||||
var stepResult = new StepResult();
|
var stepResult = new StepResult
|
||||||
stepResult.ExternalID = _record.Id;
|
{
|
||||||
stepResult.Conclusion = _record.Result ?? TaskResult.Succeeded;
|
ExternalID = _record.Id,
|
||||||
stepResult.Status = _record.State;
|
Conclusion = _record.Result ?? TaskResult.Succeeded,
|
||||||
stepResult.Number = _record.Order;
|
Status = _record.State,
|
||||||
stepResult.Name = _record.Name;
|
Number = _record.Order,
|
||||||
stepResult.StartedAt = _record.StartTime;
|
Name = _record.Name,
|
||||||
stepResult.CompletedAt = _record.FinishTime;
|
StartedAt = _record.StartTime,
|
||||||
|
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);
|
||||||
|
|
||||||
@@ -559,14 +611,10 @@ 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, string logMessage = null)
|
public void AddIssue(Issue issue, ExecutionContextLogOptions logOptions)
|
||||||
{
|
{
|
||||||
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)
|
||||||
@@ -581,54 +629,65 @@ namespace GitHub.Runner.Worker
|
|||||||
issue.Data["stepNumber"] = _record.Order.ToString();
|
issue.Data["stepNumber"] = _record.Order.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (issue.Type == IssueType.Error)
|
string wellKnownTag = null;
|
||||||
|
Int32 previousCountForIssueType = 0;
|
||||||
|
Action incrementIssueTypeCount = NoOp;
|
||||||
|
switch (issue.Type)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(logMessage))
|
case IssueType.Error:
|
||||||
{
|
wellKnownTag = WellKnownTags.Error;
|
||||||
long logLineNumber = Write(WellKnownTags.Error, logMessage);
|
previousCountForIssueType = _record.ErrorCount;
|
||||||
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
incrementIssueTypeCount = () => { _record.ErrorCount++; };
|
||||||
|
break;
|
||||||
|
case IssueType.Warning:
|
||||||
|
wellKnownTag = WellKnownTags.Warning;
|
||||||
|
previousCountForIssueType = _record.WarningCount;
|
||||||
|
incrementIssueTypeCount = () => { _record.WarningCount++; };
|
||||||
|
break;
|
||||||
|
case IssueType.Notice:
|
||||||
|
wellKnownTag = WellKnownTags.Notice;
|
||||||
|
previousCountForIssueType = _record.NoticeCount;
|
||||||
|
incrementIssueTypeCount = () => { _record.NoticeCount++; };
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_record.ErrorCount < _maxIssueCount)
|
if (!string.IsNullOrEmpty(wellKnownTag))
|
||||||
{
|
{
|
||||||
|
if (!this.IsEmbedded && previousCountForIssueType < _maxCountPerIssueType)
|
||||||
|
{
|
||||||
|
incrementIssueTypeCount();
|
||||||
_record.Issues.Add(issue);
|
_record.Issues.Add(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
_record.ErrorCount++;
|
if (logOptions.WriteToLog)
|
||||||
}
|
|
||||||
else if (issue.Type == IssueType.Warning)
|
|
||||||
{
|
{
|
||||||
|
string logMessage = issue.Message;
|
||||||
|
if (!string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
|
{
|
||||||
|
logMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(logMessage))
|
if (!string.IsNullOrEmpty(logMessage))
|
||||||
{
|
{
|
||||||
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
|
// Note that ::Write() has its own secret-masking logic.
|
||||||
|
long logLineNumber = Write(wellKnownTag, logMessage);
|
||||||
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_record.WarningCount < _maxIssueCount)
|
// Embedded ExecutionContexts (a.k.a. Composite actions) should never upload a timeline record to the server.
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
_record.Issues.Add(issue);
|
_embeddedIssueCollector.Add(issue);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
_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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateDetailTimelineRecord(TimelineRecord record)
|
public void UpdateDetailTimelineRecord(TimelineRecord record)
|
||||||
{
|
{
|
||||||
@@ -725,6 +784,9 @@ 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);
|
||||||
|
|
||||||
@@ -741,6 +803,9 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -995,8 +1060,7 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -1162,6 +1226,11 @@ 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.
|
||||||
@@ -1186,19 +1255,22 @@ 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)
|
||||||
{
|
{
|
||||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
var issue = 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)
|
||||||
{
|
{
|
||||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true });
|
var issue = 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)
|
||||||
{
|
{
|
||||||
context.AddIssue(new Issue() { Type = IssueType.Warning, Message = message });
|
var issue = 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().
|
||||||
@@ -1354,4 +1426,5 @@ namespace GitHub.Runner.Worker
|
|||||||
public static readonly string Notice = "##[notice]";
|
public static readonly string Notice = "##[notice]";
|
||||||
public static readonly string Debug = "##[debug]";
|
public static readonly string Debug = "##[debug]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -18,6 +19,7 @@ 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);
|
||||||
step.ExecutionContext.Complete(TaskResult.Failed);
|
SetStepConclusion(step, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Callback
|
// Register Callback
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
if (issue != null)
|
if (issue != null)
|
||||||
{
|
{
|
||||||
// Log issue
|
// Log issue
|
||||||
_executionContext.AddIssue(issue, stripped);
|
var logOptions = new ExecutionContextLogOptions(true, stripped);
|
||||||
|
_executionContext.AddIssue(issue, logOptions);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,14 +431,6 @@ 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.
|
||||||
@@ -683,7 +675,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);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,18 @@ namespace GitHub.Runner.Worker
|
|||||||
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 = HostContext.GetService<IJobServerQueue>();
|
||||||
_jobServerQueue.Start(message, resultServiceOnly: true);
|
_jobServerQueue.Start(message, resultServiceOnly: true);
|
||||||
}
|
}
|
||||||
@@ -108,7 +120,8 @@ namespace GitHub.Runner.Worker
|
|||||||
default:
|
default:
|
||||||
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
|
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
|
||||||
}
|
}
|
||||||
jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage });
|
var issue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
|
jobContext.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate directory permissions.
|
// Validate directory permissions.
|
||||||
@@ -137,6 +150,11 @@ namespace GitHub.Runner.Worker
|
|||||||
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||||
jobContext.SetRunnerContext("name", _runnerSettings.AgentName);
|
jobContext.SetRunnerContext("name", _runnerSettings.AgentName);
|
||||||
|
|
||||||
|
if (jobContext.Global.Variables.TryGetValue(WellKnownDistributedTaskVariables.RunnerEnvironment, out var runnerEnvironment))
|
||||||
|
{
|
||||||
|
jobContext.SetRunnerContext("environment", runnerEnvironment);
|
||||||
|
}
|
||||||
|
|
||||||
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
|
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
|
||||||
Directory.CreateDirectory(toolsDirectory);
|
Directory.CreateDirectory(toolsDirectory);
|
||||||
jobContext.SetRunnerContext("tool_cache", toolsDirectory);
|
jobContext.SetRunnerContext("tool_cache", toolsDirectory);
|
||||||
@@ -272,7 +290,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, default);
|
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, default);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -34,6 +34,11 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
m_errors = value;
|
m_errors = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Because TraceWriter has access to the ExecutionContext and is logging template errors, duplicated issues are reported.
|
||||||
|
/// By setting LogErrorsToTraceWriter = false TemplateContext will add errors only to TemplateValidationErrors
|
||||||
|
/// </summary>
|
||||||
|
internal bool LogErrorsToTraceWriter = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Available functions within expression contexts
|
/// Available functions within expression contexts
|
||||||
@@ -120,12 +125,6 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Error(TemplateValidationError error)
|
|
||||||
{
|
|
||||||
Errors.Add(error);
|
|
||||||
TraceWriter.Error(error.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Error(
|
internal void Error(
|
||||||
TemplateToken value,
|
TemplateToken value,
|
||||||
Exception ex)
|
Exception ex)
|
||||||
@@ -141,8 +140,12 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
{
|
{
|
||||||
var prefix = GetErrorPrefix(fileId, line, column);
|
var prefix = GetErrorPrefix(fileId, line, column);
|
||||||
Errors.Add(prefix, ex);
|
Errors.Add(prefix, ex);
|
||||||
|
|
||||||
|
if (LogErrorsToTraceWriter)
|
||||||
|
{
|
||||||
TraceWriter.Error(prefix, ex);
|
TraceWriter.Error(prefix, ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void Error(
|
internal void Error(
|
||||||
TemplateToken value,
|
TemplateToken value,
|
||||||
@@ -158,13 +161,13 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
String message)
|
String message)
|
||||||
{
|
{
|
||||||
var prefix = GetErrorPrefix(fileId, line, column);
|
var prefix = GetErrorPrefix(fileId, line, column);
|
||||||
if (!String.IsNullOrEmpty(prefix))
|
var fullMessage = !String.IsNullOrEmpty(prefix) ? $"{prefix} {message}" : message;
|
||||||
{
|
|
||||||
message = $"{prefix} {message}";
|
|
||||||
}
|
|
||||||
|
|
||||||
Errors.Add(message);
|
Errors.Add(fullMessage);
|
||||||
TraceWriter.Error(message);
|
if (LogErrorsToTraceWriter)
|
||||||
|
{
|
||||||
|
TraceWriter.Error(fullMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal INamedValueInfo[] GetExpressionNamedValues()
|
internal INamedValueInfo[] GetExpressionNamedValues()
|
||||||
|
|||||||
@@ -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,6 +1519,26 @@ 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,8 +1,7 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -10,11 +9,15 @@ 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();
|
||||||
|
|
||||||
|
if (recordToBeCloned != null)
|
||||||
{
|
{
|
||||||
this.Attempt = recordToBeCloned.Attempt;
|
this.Attempt = recordToBeCloned.Attempt;
|
||||||
this.ChangeId = recordToBeCloned.ChangeId;
|
this.ChangeId = recordToBeCloned.ChangeId;
|
||||||
@@ -67,12 +70,17 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
|
|
||||||
if (recordToBeCloned.m_previousAttempts?.Count > 0)
|
if (recordToBeCloned.m_previousAttempts?.Count > 0)
|
||||||
{
|
{
|
||||||
this.PreviousAttempts.AddRange(recordToBeCloned.PreviousAttempts);
|
this.m_previousAttempts.AddRange(recordToBeCloned.m_previousAttempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.m_variables?.Count > 0)
|
if (recordToBeCloned.m_variables?.Count > 0)
|
||||||
{
|
{
|
||||||
this.m_variables = recordToBeCloned.Variables.ToDictionary(k => k.Key, v => v.Value.Clone());
|
// Don't pave over the case-insensitive Dictionary we initialized above.
|
||||||
|
foreach (var kvp in recordToBeCloned.m_variables)
|
||||||
|
{
|
||||||
|
m_variables[kvp.Key] = kvp.Value.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,14 +106,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;
|
||||||
@@ -126,7 +134,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 7)]
|
[DataMember(Order = 7)]
|
||||||
public String CurrentOperation
|
public string CurrentOperation
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -154,7 +162,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 11)]
|
[DataMember(Order = 11)]
|
||||||
public String ResultCode
|
public string ResultCode
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -175,7 +183,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 14)]
|
[DataMember(Order = 14)]
|
||||||
public String WorkerName
|
public string WorkerName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -189,7 +197,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;
|
||||||
@@ -209,35 +217,46 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 40)]
|
public Int32 ErrorCount
|
||||||
public Int32? ErrorCount
|
|
||||||
{
|
{
|
||||||
get;
|
get
|
||||||
set;
|
{
|
||||||
|
return m_errorCount.GetValueOrDefault(0);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_errorCount = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 50)]
|
public Int32 WarningCount
|
||||||
public Int32? WarningCount
|
|
||||||
{
|
{
|
||||||
get;
|
get
|
||||||
set;
|
{
|
||||||
|
return m_warningCount.GetValueOrDefault(0);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_warningCount = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 55)]
|
public Int32 NoticeCount
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +276,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 131)]
|
[DataMember(Order = 131)]
|
||||||
public String Identifier
|
public string Identifier
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -274,22 +293,14 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,13 +310,53 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
return new TimelineRecord(this);
|
return new TimelineRecord(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Name = "Issues", EmitDefaultValue = false, Order = 60)]
|
[OnDeserialized]
|
||||||
|
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 = "Variables", EmitDefaultValue = false, Order = 80)]
|
[DataMember(Name = nameof(Variables), EmitDefaultValue = false, Order = 80)]
|
||||||
private Dictionary<String, VariableValue> m_variables;
|
private Dictionary<string, VariableValue> m_variables;
|
||||||
|
|
||||||
[DataMember(Name = "PreviousAttempts", EmitDefaultValue = false, Order = 120)]
|
[DataMember(Name = nameof(PreviousAttempts), EmitDefaultValue = false, Order = 120)]
|
||||||
private List<TimelineAttempt> m_previousAttempts;
|
private List<TimelineAttempt> m_previousAttempts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
{
|
{
|
||||||
@@ -6,5 +6,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
public static readonly String JobId = "system.jobId";
|
public static readonly String JobId = "system.jobId";
|
||||||
public static readonly String RunnerLowDiskspaceThreshold = "system.runner.lowdiskspacethreshold";
|
public static readonly String RunnerLowDiskspaceThreshold = "system.runner.lowdiskspacethreshold";
|
||||||
|
public static readonly String RunnerEnvironment = "system.runnerEnvironment";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,5 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
[DataMember(Name = "jobMessageId", EmitDefaultValue = false)]
|
[DataMember(Name = "jobMessageId", EmitDefaultValue = false)]
|
||||||
public string JobMessageId { get; set; }
|
public string JobMessageId { get; set; }
|
||||||
|
|
||||||
// This field will be removed in an upcoming Runner release.
|
|
||||||
// It's left here temporarily to facilitate the transition to the new field name, JobMessageId.
|
|
||||||
[DataMember(Name = "streamId", EmitDefaultValue = false)]
|
|
||||||
public string StreamId { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/Sdk/RSWebApi/Contracts/Annotation.cs
Normal file
35
src/Sdk/RSWebApi/Contracts/Annotation.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Sdk/RSWebApi/Contracts/AnnotationLevel.cs
Normal file
20
src/Sdk/RSWebApi/Contracts/AnnotationLevel.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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,6 +2,7 @@ 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
|
||||||
{
|
{
|
||||||
@@ -22,5 +23,8 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
|
|
||||||
[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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
91
src/Sdk/RSWebApi/Contracts/IssueExtensions.cs
Normal file
91
src/Sdk/RSWebApi/Contracts/IssueExtensions.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Sdk/RSWebApi/Contracts/IssueKeys.cs
Normal file
13
src/Sdk/RSWebApi/Contracts/IssueKeys.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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,7 +1,9 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -34,5 +36,8 @@ 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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,6 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
var payload = new AcquireJobRequest
|
var payload = new AcquireJobRequest
|
||||||
{
|
{
|
||||||
JobMessageId = messageId,
|
JobMessageId = messageId,
|
||||||
StreamId = messageId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "acquirejob");
|
requestUri = new Uri(requestUri, "acquirejob");
|
||||||
@@ -86,6 +85,8 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
case HttpStatusCode.NotFound:
|
case HttpStatusCode.NotFound:
|
||||||
throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}");
|
throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}");
|
||||||
|
case HttpStatusCode.Conflict:
|
||||||
|
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired: {messageId}");
|
||||||
default:
|
default:
|
||||||
throw new Exception($"Failed to get job message: {result.Error}");
|
throw new Exception($"Failed to get job message: {result.Error}");
|
||||||
}
|
}
|
||||||
@@ -98,6 +99,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
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");
|
||||||
@@ -107,7 +109,8 @@ 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");
|
||||||
|
|||||||
@@ -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>7.3</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
70
src/Sdk/WebApi/WebApi/LaunchContracts.cs
Normal file
70
src/Sdk/WebApi/WebApi/LaunchContracts.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/Sdk/WebApi/WebApi/LaunchHttpClient.cs
Normal file
115
src/Sdk/WebApi/WebApi/LaunchHttpClient.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
270
src/Test/L0/DistributedTask/WebApi/TimelineRecordL0.cs
Normal file
270
src/Test/L0/DistributedTask/WebApi/TimelineRecordL0.cs
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
#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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -206,6 +206,107 @@ 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")]
|
||||||
|
|||||||
275
src/Test/L0/Sdk/DTObjectTemplating/TemplateContextL0.cs
Normal file
275
src/Test/L0/Sdk/DTObjectTemplating/TemplateContextL0.cs
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.ObjectTemplating.Tests
|
||||||
|
{
|
||||||
|
public sealed class TemplateContextL0
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithTokenAndException()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("(Line: 1, Col: 1): Exception of type 'System.Exception' was thrown.") };
|
||||||
|
|
||||||
|
context.Error(new StringToken(1, 1, 1, "some-token"), new System.Exception());
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithNullTokenAndException()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("System.Exception: Exception of type 'System.Exception' was thrown.") };
|
||||||
|
|
||||||
|
context.Error(null, new System.Exception());
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithTokenAndMessage()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("(Line: 1, Col: 1): message") };
|
||||||
|
|
||||||
|
context.Error(new StringToken(1, 1, 1, "some-token"), "message");
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithNullTokenAndMessage()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("message") };
|
||||||
|
|
||||||
|
context.Error(null, "message");
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithException()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("(Line: 2, Col: 3): Fatal template exception") };
|
||||||
|
List<string> expectedTracewriterErrors = new List<string> { "(Line: 2, Col: 3):" };
|
||||||
|
|
||||||
|
context.Error(1, 2, 3, new Exception("Fatal template exception"));
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
ListTraceWriter listTraceWriter = (ListTraceWriter)context.TraceWriter;
|
||||||
|
List<string> tracewriterErrors = listTraceWriter.GetErrors();
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
Assert.True(expectedTracewriterErrors.SequenceEqual(tracewriterErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithExceptionAndInnerException()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("(Line: 2, Col: 3): Fatal template exception"),
|
||||||
|
new TemplateValidationError("(Line: 2, Col: 3): Inner Exception") };
|
||||||
|
List<string> expectedTracewriterErrors = new List<string> { "(Line: 2, Col: 3):" };
|
||||||
|
|
||||||
|
Exception e = new Exception("Fatal template exception", new Exception("Inner Exception"));
|
||||||
|
|
||||||
|
context.Error(1, 2, 3, e);
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
ListTraceWriter listTraceWriter = (ListTraceWriter)context.TraceWriter;
|
||||||
|
List<string> tracewriterErrors = listTraceWriter.GetErrors();
|
||||||
|
|
||||||
|
Assert.Equal(2, templateValidationErrors.Count);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
Assert.True(expectedTracewriterErrors.SequenceEqual(tracewriterErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithLineNumbersAndMessage()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("(Line: 2, Col: 3): Fatal template exception") };
|
||||||
|
List<string> expectedTracewriterErrors = new List<string> { "(Line: 2, Col: 3): Fatal template exception" };
|
||||||
|
|
||||||
|
context.Error(1, 2, 3, "Fatal template exception");
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
ListTraceWriter listTraceWriter = (ListTraceWriter)context.TraceWriter;
|
||||||
|
List<string> tracewriterErrors = listTraceWriter.GetErrors();
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
Assert.True(expectedTracewriterErrors.SequenceEqual(tracewriterErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithNullLineNumbersAndMessage()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("Fatal template exception") };
|
||||||
|
List<string> expectedTracewriterErrors = new List<string> { "Fatal template exception" };
|
||||||
|
|
||||||
|
context.Error(null, null, null, "Fatal template exception");
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
ListTraceWriter listTraceWriter = (ListTraceWriter)context.TraceWriter;
|
||||||
|
List<string> tracewriterErrors = listTraceWriter.GetErrors();
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
Assert.True(expectedTracewriterErrors.SequenceEqual(tracewriterErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithLineNumbersAndException()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("(Line: 2, Col: 3): Fatal template exception") };
|
||||||
|
List<string> expectedTracewriterErrors = new List<string> { "(Line: 2, Col: 3):" };
|
||||||
|
|
||||||
|
context.Error(1, 2, 3, new Exception("Fatal template exception"));
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
ListTraceWriter listTraceWriter = (ListTraceWriter)context.TraceWriter;
|
||||||
|
List<string> tracewriterErrors = listTraceWriter.GetErrors();
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
Assert.True(expectedTracewriterErrors.SequenceEqual(tracewriterErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void VerifyErrorWithNullLineNumbersAndException()
|
||||||
|
{
|
||||||
|
TemplateContext context = buildContext();
|
||||||
|
|
||||||
|
List<TemplateValidationError> expectedErrors = new List<TemplateValidationError> { new TemplateValidationError("System.Exception: Fatal template exception") };
|
||||||
|
List<string> expectedTracewriterErrors = new List<string> { "" };
|
||||||
|
|
||||||
|
context.Error(null, null, null, new Exception("Fatal template exception"));
|
||||||
|
|
||||||
|
List<TemplateValidationError> templateValidationErrors = toList(context.Errors.GetEnumerator());
|
||||||
|
ListTraceWriter listTraceWriter = (ListTraceWriter)context.TraceWriter;
|
||||||
|
List<string> tracewriterErrors = listTraceWriter.GetErrors();
|
||||||
|
|
||||||
|
Assert.Single(templateValidationErrors);
|
||||||
|
Assert.True(areEqual(expectedErrors, templateValidationErrors));
|
||||||
|
Assert.True(expectedTracewriterErrors.SequenceEqual(tracewriterErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TemplateContext buildContext()
|
||||||
|
{
|
||||||
|
return new TemplateContext
|
||||||
|
{
|
||||||
|
// CancellationToken = CancellationToken.None,
|
||||||
|
Errors = new TemplateValidationErrors(10, int.MaxValue), // Don't truncate error messages otherwise we might not scrub secrets correctly
|
||||||
|
Memory = new TemplateMemory(
|
||||||
|
maxDepth: 100,
|
||||||
|
maxEvents: 1000000,
|
||||||
|
maxBytes: 10 * 1024 * 1024),
|
||||||
|
Schema = null,
|
||||||
|
TraceWriter = new ListTraceWriter(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TemplateValidationError> toList(IEnumerator<TemplateValidationError> enumerator)
|
||||||
|
{
|
||||||
|
List<TemplateValidationError> result = new();
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
TemplateValidationError err = enumerator.Current;
|
||||||
|
result.Add(err);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool areEqual(List<TemplateValidationError> l1, List<TemplateValidationError> l2)
|
||||||
|
{
|
||||||
|
if (l1.Count != l2.Count) return false;
|
||||||
|
|
||||||
|
var twoLists = l1.Zip(l2, (l1Error, l2Error) => new { Elem1 = l1Error, Elem2 = l2Error });
|
||||||
|
|
||||||
|
foreach (var elem in twoLists)
|
||||||
|
{
|
||||||
|
if (elem.Elem1.Message != elem.Elem2.Message) return false;
|
||||||
|
if (elem.Elem1.Code != elem.Elem2.Code) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ListTraceWriter : ITraceWriter
|
||||||
|
{
|
||||||
|
|
||||||
|
private List<string> errors = new();
|
||||||
|
private List<string> infoMessages = new();
|
||||||
|
private List<string> verboseMessages = new();
|
||||||
|
public void Error(string format, params object[] args)
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(System.Globalization.CultureInfo.CurrentCulture, $"{format}", args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string format, params object[] args)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string format, params object[] args)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetErrors()
|
||||||
|
{
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,11 +14,9 @@ public sealed class AcquireJobRequestL0
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public void VerifySerialization()
|
public void VerifySerialization()
|
||||||
{
|
{
|
||||||
var jobMessageId = "1526919030369-33";
|
|
||||||
var request = new AcquireJobRequest
|
var request = new AcquireJobRequest
|
||||||
{
|
{
|
||||||
JobMessageId = jobMessageId,
|
JobMessageId = "1526919030369-33"
|
||||||
StreamId = jobMessageId
|
|
||||||
};
|
};
|
||||||
var serializer = new DataContractJsonSerializer(typeof(AcquireJobRequest));
|
var serializer = new DataContractJsonSerializer(typeof(AcquireJobRequest));
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
@@ -27,7 +25,7 @@ public sealed class AcquireJobRequestL0
|
|||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||||
string json = reader.ReadToEnd();
|
string json = reader.ReadToEnd();
|
||||||
string expected = DoubleQuotify(string.Format("{{'jobMessageId':'{0}','streamId':'{0}'}}", request.JobMessageId));
|
string expected = DoubleQuotify(string.Format("{{'jobMessageId':'{0}'}}", request.JobMessageId));
|
||||||
Assert.Equal(expected, json);
|
Assert.Equal(expected, json);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
70
src/Test/L0/Sdk/RSWebApi/AnnotationsL0.cs
Normal file
70
src/Test/L0/Sdk/RSWebApi/AnnotationsL0.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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 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<string>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((Issue issue, string message) =>
|
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? 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<string>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((Issue issue, string message) =>
|
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? 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<string>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((Issue issue, string message) =>
|
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ 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;
|
||||||
@@ -2147,7 +2148,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<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? 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.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>();
|
||||||
@@ -2175,6 +2176,25 @@ 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" });
|
||||||
|
|
||||||
@@ -2183,6 +2203,7 @@ 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());
|
||||||
|
|||||||
@@ -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<string>()), 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<ExecutionContextLogOptions>()), 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<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? 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}"); });
|
||||||
}
|
}
|
||||||
|
|
||||||
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<string>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<ExecutionContextLogOptions>()), 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<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? 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}"); });
|
||||||
|
|
||||||
_hc.SetSingleton<IActionManager>(_actionManager.Object);
|
_hc.SetSingleton<IActionManager>(_actionManager.Object);
|
||||||
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
|
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
|
||||||
|
|||||||
@@ -247,12 +247,16 @@ 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<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_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) =>
|
||||||
|
|||||||
@@ -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,42 +52,43 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
// Act.
|
// Act.
|
||||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
// 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" }, 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.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
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.ErrorCount > 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.WarningCount > 0)), Times.AtLeast(10));
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -190,9 +191,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
bigMessage += "a";
|
bigMessage += "a";
|
||||||
}
|
}
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage });
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -203,6 +204,61 @@ 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")]
|
||||||
@@ -242,13 +298,15 @@ 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" });
|
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.Warning, Message = "warning 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.Notice, Message = "notice 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);
|
||||||
|
|
||||||
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);
|
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.Warning).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.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.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.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,12 +684,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" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, 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.Notice, Message = "notice" });
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, 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" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -692,9 +750,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" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
embeddedStep.PublishStepTelemetry();
|
embeddedStep.PublishStepTelemetry();
|
||||||
|
|
||||||
@@ -756,9 +814,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" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
|
|||||||
@@ -984,10 +984,15 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_onMatcherChanged = handler;
|
_onMatcherChanged = handler;
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
|
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,12 +413,16 @@ 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<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_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,12 +411,16 @@ 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<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_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,12 +413,16 @@ 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<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_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) =>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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 DistributedTask.Pipelines.ActionStep()
|
.Returns(new GitHub.DistributedTask.Pipelines.ActionStep()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
|
|||||||
70
src/Test/TestData/timelinerecord_json_samples.yml
Normal file
70
src/Test/TestData/timelinerecord_json_samples.yml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user