mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
10 Commits
releases/m
...
v2.316.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18803bdff6 | ||
|
|
04b07b6675 | ||
|
|
dd9fcfc5b2 | ||
|
|
5107c5efb2 | ||
|
|
1b61d78c07 | ||
|
|
2e0eb2c11f | ||
|
|
2d83e1d88f | ||
|
|
4a1e38095b | ||
|
|
f467e9e125 | ||
|
|
77e0bfbb8a |
@@ -4,7 +4,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.420"
|
||||
"version": "6.0.421"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
## What's Changed
|
||||
* fix summaries for actions results by @SrRyan in https://github.com/actions/runner/pull/3174
|
||||
* Bump runner version to match the latest patch release by @TingluoHuang in https://github.com/actions/runner/pull/3175
|
||||
* don't crash listener on getting job exceptions for run-service by @yaananth in https://github.com/actions/runner/pull/3177
|
||||
* Remove -f flag in wait when manually trap signal by @nikola-jokic in https://github.com/actions/runner/pull/3182
|
||||
* consume new pipelines service url in handlers by @patrickcarnahan in https://github.com/actions/runner/pull/3185
|
||||
* Add ability to enforce actions to run on node20 by @takost in https://github.com/actions/runner/pull/3192
|
||||
* Bump hook version to 0.6.0 by @nikola-jokic in https://github.com/actions/runner/pull/3203
|
||||
* Update dotnet sdk to latest version @6.0.420 by @github-actions in https://github.com/actions/runner/pull/3211
|
||||
* Bump docker version and docker buildx version by @nikola-jokic in https://github.com/actions/runner/pull/3208
|
||||
* Handle new non-retryable exception type by @thyeggman in https://github.com/actions/runner/pull/3191
|
||||
* Always Delete Actions Service Session by @luketomlinson in https://github.com/actions/runner/pull/3214
|
||||
* Load '_runnerSettings' in the early point of JobRunner.cs by @TingluoHuang in https://github.com/actions/runner/pull/3218
|
||||
* Add new SessionConflict return code by @eeSquared in https://github.com/actions/runner/pull/3215
|
||||
* backoff if we retried polling for more than 50 times in less than 30minutes by @aiqiaoy in https://github.com/actions/runner/pull/3232
|
||||
* Update dotnet sdk to latest version @6.0.421 by @github-actions in https://github.com/actions/runner/pull/3244
|
||||
* Cleanup enabled feature flags. by @TingluoHuang in https://github.com/actions/runner/pull/3246
|
||||
* Relax the condition to stop uploading to Results by @yacaovsnc in https://github.com/actions/runner/pull/3230
|
||||
* Cleanup enabled feature flags. by @TingluoHuang in https://github.com/actions/runner/pull/3248
|
||||
* Replace invalid file name chars in diag log name by @ericsciple in https://github.com/actions/runner/pull/3249
|
||||
|
||||
## New Contributors
|
||||
* @SrRyan made their first contribution in https://github.com/actions/runner/pull/3174
|
||||
* @patrickcarnahan made their first contribution in https://github.com/actions/runner/pull/3185
|
||||
* @eeSquared made their first contribution in https://github.com/actions/runner/pull/3215
|
||||
* @aiqiaoy made their first contribution in https://github.com/actions/runner/pull/3232
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.314.1...v2.315.0
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.313.0...v2.314.0
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.315.0...v2.316.0
|
||||
|
||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
|
||||
@@ -114,6 +114,11 @@ var runService = function () {
|
||||
);
|
||||
stopping = true;
|
||||
}
|
||||
} else if (code === 5) {
|
||||
console.log(
|
||||
"Runner listener exit with Session Conflict error, stop the service, no retry needed."
|
||||
);
|
||||
stopping = true;
|
||||
} else {
|
||||
var messagePrefix = "Runner listener exit with undefined return code";
|
||||
unknownFailureRetryCount++;
|
||||
|
||||
@@ -49,5 +49,10 @@ if %ERRORLEVEL% EQU 4 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if %ERRORLEVEL% EQU 5 (
|
||||
echo "Runner listener exit with Session Conflict error, stop the service, no retry needed."
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo "Exiting after unknown error code: %ERRORLEVEL%"
|
||||
exit /b 0
|
||||
@@ -70,6 +70,9 @@ elif [[ $returnCode == 4 ]]; then
|
||||
"$DIR"/safe_sleep.sh 1
|
||||
done
|
||||
exit 2
|
||||
elif [[ $returnCode == 5 ]]; then
|
||||
echo "Runner listener exit with Session Conflict error, stop the service, no retry needed."
|
||||
exit 0
|
||||
else
|
||||
echo "Exiting with unknown error code: ${returnCode}"
|
||||
exit 0
|
||||
|
||||
@@ -20,12 +20,12 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
private bool _hasConnection;
|
||||
private VssConnection _connection;
|
||||
private TaskAgentHttpClient _taskAgentClient;
|
||||
private ActionsRunServerHttpClient _actionsRunServerClient;
|
||||
|
||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
||||
{
|
||||
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
||||
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
||||
_actionsRunServerClient = _connection.GetClient<ActionsRunServerHttpClient>();
|
||||
_hasConnection = true;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace GitHub.Runner.Common
|
||||
CheckConnection();
|
||||
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
|
||||
{
|
||||
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
|
||||
return await _actionsRunServerClient.GetJobMessageAsync(id, cancellationToken);
|
||||
}, cancellationToken);
|
||||
|
||||
return jobMessage;
|
||||
|
||||
@@ -153,6 +153,7 @@ namespace GitHub.Runner.Common
|
||||
public const int RetryableError = 2;
|
||||
public const int RunnerUpdating = 3;
|
||||
public const int RunOnceRunnerUpdating = 4;
|
||||
public const int SessionConflict = 5;
|
||||
}
|
||||
|
||||
public static class Features
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace GitHub.Runner.Common
|
||||
TaskCompletionSource<int> JobRecordUpdated { get; }
|
||||
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||
Task ShutdownAsync();
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false);
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false);
|
||||
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
||||
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
||||
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
||||
@@ -74,6 +74,7 @@ namespace GitHub.Runner.Common
|
||||
private readonly List<JobTelemetry> _jobTelemetries = new();
|
||||
private bool _queueInProcess = false;
|
||||
private bool _resultsServiceOnly = false;
|
||||
private int _resultsServiceExceptionsCount = 0;
|
||||
private Stopwatch _resultsUploadTimer = new();
|
||||
private Stopwatch _actionsUploadTimer = new();
|
||||
|
||||
@@ -104,11 +105,10 @@ namespace GitHub.Runner.Common
|
||||
_resultsServer = hostContext.GetService<IResultsServer>();
|
||||
}
|
||||
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false)
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false)
|
||||
{
|
||||
Trace.Entering();
|
||||
_resultsServiceOnly = resultsServiceOnly;
|
||||
_enableTelemetry = enableTelemetry;
|
||||
|
||||
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -139,6 +139,12 @@ namespace GitHub.Runner.Common
|
||||
_resultsClientInitiated = true;
|
||||
}
|
||||
|
||||
// Enable telemetry if we have both results service and actions service
|
||||
if (_resultsClientInitiated && !_resultsServiceOnly)
|
||||
{
|
||||
_enableTelemetry = true;
|
||||
}
|
||||
|
||||
if (_queueInProcess)
|
||||
{
|
||||
Trace.Info("No-opt, all queue process tasks are running.");
|
||||
@@ -574,9 +580,9 @@ namespace GitHub.Runner.Common
|
||||
Trace.Info("Catch exception during file upload to results, keep going since the process is best effort.");
|
||||
Trace.Error(ex);
|
||||
errorCount++;
|
||||
|
||||
_resultsServiceExceptionsCount++;
|
||||
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results unless Results is serving logs
|
||||
if (!_resultsServiceOnly)
|
||||
if (!_resultsServiceOnly && _resultsServiceExceptionsCount > 3)
|
||||
{
|
||||
_resultsClientInitiated = false;
|
||||
SendResultsTelemetry(ex);
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace GitHub.Runner.Listener
|
||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
||||
}
|
||||
|
||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return CreateSessionResult.Success;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -123,7 +123,7 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
@@ -134,14 +134,18 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsSessionCreationExceptionRetriable(ex))
|
||||
{
|
||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||
return false;
|
||||
if (ex is TaskAgentSessionConflictException)
|
||||
{
|
||||
return CreateSessionResult.SessionConflict;
|
||||
}
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
if (!encounteringError) //print the message only on the first error
|
||||
|
||||
@@ -1155,18 +1155,13 @@ namespace GitHub.Runner.Listener
|
||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||
|
||||
|
||||
jobRecord.ErrorCount++;
|
||||
jobRecord.Issues.Add(issue);
|
||||
|
||||
if (message.Variables.TryGetValue("DistributedTask.MarkJobAsFailedOnWorkerCrash", out var markJobAsFailedOnWorkerCrash) &&
|
||||
StringUtil.ConvertToBoolean(markJobAsFailedOnWorkerCrash?.Value))
|
||||
{
|
||||
Trace.Info("Mark the job as failed since the worker crashed");
|
||||
jobRecord.Result = TaskResult.Failed;
|
||||
// mark the job as completed so service will pickup the result
|
||||
jobRecord.State = TimelineRecordState.Completed;
|
||||
}
|
||||
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,17 @@ using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
public enum CreateSessionResult
|
||||
{
|
||||
Success,
|
||||
Failure,
|
||||
SessionConflict
|
||||
}
|
||||
|
||||
[ServiceLocator(Default = typeof(MessageListener))]
|
||||
public interface IMessageListener : IRunnerService
|
||||
{
|
||||
Task<Boolean> CreateSessionAsync(CancellationToken token);
|
||||
Task<CreateSessionResult> CreateSessionAsync(CancellationToken token);
|
||||
Task DeleteSessionAsync();
|
||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||
@@ -59,7 +66,7 @@ namespace GitHub.Runner.Listener
|
||||
_brokerServer = hostContext.GetService<IBrokerServer>();
|
||||
}
|
||||
|
||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -123,7 +130,7 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return CreateSessionResult.Success;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -147,7 +154,7 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
@@ -158,14 +165,18 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsSessionCreationExceptionRetriable(ex))
|
||||
{
|
||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||
return false;
|
||||
if (ex is TaskAgentSessionConflictException)
|
||||
{
|
||||
return CreateSessionResult.SessionConflict;
|
||||
}
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
if (!encounteringError) //print the message only on the first error
|
||||
@@ -225,6 +236,7 @@ namespace GitHub.Runner.Listener
|
||||
ArgUtil.NotNull(_settings, nameof(_settings));
|
||||
bool encounteringError = false;
|
||||
int continuousError = 0;
|
||||
int continuousEmptyMessage = 0;
|
||||
string errorMessage = string.Empty;
|
||||
Stopwatch heartbeat = new();
|
||||
heartbeat.Restart();
|
||||
@@ -303,7 +315,7 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error(ex);
|
||||
|
||||
// don't retry if SkipSessionRecover = true, DT service will delete agent session to stop agent from taking more jobs.
|
||||
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && await CreateSessionAsync(token))
|
||||
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
||||
{
|
||||
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
|
||||
}
|
||||
@@ -348,16 +360,27 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
continuousEmptyMessage++;
|
||||
if (heartbeat.Elapsed > TimeSpan.FromMinutes(30))
|
||||
{
|
||||
Trace.Info($"No message retrieved from session '{_session.SessionId}' within last 30 minutes.");
|
||||
heartbeat.Restart();
|
||||
continuousEmptyMessage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Verbose($"No message retrieved from session '{_session.SessionId}'.");
|
||||
}
|
||||
|
||||
if (continuousEmptyMessage > 50)
|
||||
{
|
||||
// retried more than 50 times in less than 30mins and still getting empty message
|
||||
// something is not right on the service side, backoff for 15-30s before retry
|
||||
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30), _getNextMessageRetryInterval);
|
||||
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
|
||||
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -359,7 +359,12 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
Trace.Info(nameof(RunAsync));
|
||||
_listener = GetMesageListener(settings);
|
||||
if (!await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken))
|
||||
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
||||
if (createSessionResult == CreateSessionResult.SessionConflict)
|
||||
{
|
||||
return Constants.Runner.ReturnCode.SessionConflict;
|
||||
}
|
||||
else if (createSessionResult == CreateSessionResult.Failure)
|
||||
{
|
||||
return Constants.Runner.ReturnCode.TerminatedError;
|
||||
}
|
||||
|
||||
@@ -459,6 +459,34 @@ namespace GitHub.Runner.Sdk
|
||||
File.WriteAllText(path, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces invalid file name characters with '_'
|
||||
/// </summary>
|
||||
public static string ReplaceInvalidFileNameChars(string fileName)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
var current = 0; // Current index
|
||||
while (current < fileName?.Length)
|
||||
{
|
||||
var next = fileName.IndexOfAny(invalidChars, current);
|
||||
if (next >= 0)
|
||||
{
|
||||
result.Append(fileName.Substring(current, next - current));
|
||||
result.Append('_');
|
||||
current = next + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(fileName.Substring(current));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively enumerates a directory without following directory reparse points.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,129 +7,6 @@ namespace GitHub.Runner.Sdk
|
||||
public static class WhichUtil
|
||||
{
|
||||
public static string Which(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||
trace?.Info($"Which: '{command}'");
|
||||
if (Path.IsPathFullyQualified(command) && File.Exists(command))
|
||||
{
|
||||
trace?.Info($"Fully qualified path: '{command}'");
|
||||
return command;
|
||||
}
|
||||
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
trace?.Info("PATH environment variable not defined.");
|
||||
path = path ?? string.Empty;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(prependPath))
|
||||
{
|
||||
path = PathUtil.PrependPath(prependPath, path);
|
||||
}
|
||||
|
||||
string[] pathSegments = path.Split(new Char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < pathSegments.Length; i++)
|
||||
{
|
||||
pathSegments[i] = Environment.ExpandEnvironmentVariables(pathSegments[i]);
|
||||
}
|
||||
|
||||
foreach (string pathSegment in pathSegments)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pathSegment) && Directory.Exists(pathSegment))
|
||||
{
|
||||
string[] matches = null;
|
||||
#if OS_WINDOWS
|
||||
string pathExt = Environment.GetEnvironmentVariable("PATHEXT");
|
||||
if (string.IsNullOrEmpty(pathExt))
|
||||
{
|
||||
// XP's system default value for PATHEXT system variable
|
||||
pathExt = ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh";
|
||||
}
|
||||
|
||||
string[] pathExtSegments = pathExt.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// if command already has an extension.
|
||||
if (pathExtSegments.Any(ext => command.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
try
|
||||
{
|
||||
matches = Directory.GetFiles(pathSegment, command);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
|
||||
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
|
||||
{
|
||||
trace?.Info($"Location: '{matches.First()}'");
|
||||
return matches.First();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string searchPattern;
|
||||
searchPattern = StringUtil.Format($"{command}.*");
|
||||
try
|
||||
{
|
||||
matches = Directory.GetFiles(pathSegment, searchPattern);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
|
||||
if (matches != null && matches.Length > 0)
|
||||
{
|
||||
// add extension.
|
||||
for (int i = 0; i < pathExtSegments.Length; i++)
|
||||
{
|
||||
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
|
||||
if (matches.Any(p => p.Equals(fullPath, StringComparison.OrdinalIgnoreCase)) && IsPathValid(fullPath, trace))
|
||||
{
|
||||
trace?.Info($"Location: '{fullPath}'");
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
try
|
||||
{
|
||||
matches = Directory.GetFiles(pathSegment, command);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
|
||||
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
|
||||
{
|
||||
trace?.Info($"Location: '{matches.First()}'");
|
||||
return matches.First();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'Path' environment variable.");
|
||||
#else
|
||||
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'PATH' environment variable.");
|
||||
#endif
|
||||
if (require)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
message: $"{command}: command not found",
|
||||
fileName: command);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string Which2(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||
trace?.Info($"Which2: '{command}'");
|
||||
|
||||
@@ -483,10 +483,6 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
// Load stored Ids for later load actions
|
||||
compositeAction.Steps[i].Id = _cachedEmbeddedStepIds[action.Id][i];
|
||||
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && compositeAction.Steps[i].Reference.Type != Pipelines.ActionSourceType.Script)
|
||||
{
|
||||
throw new Exception("`uses:` keyword is not currently supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -797,8 +793,6 @@ namespace GitHub.Runner.Worker
|
||||
try
|
||||
{
|
||||
var useActionArchiveCache = false;
|
||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.UseActionArchiveCache") == true)
|
||||
{
|
||||
var hasActionArchiveCache = false;
|
||||
var actionArchiveCacheDir = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory);
|
||||
if (!string.IsNullOrEmpty(actionArchiveCacheDir) &&
|
||||
@@ -832,7 +826,6 @@ namespace GitHub.Runner.Worker
|
||||
Type = JobTelemetryType.General,
|
||||
Message = $"Action archive cache usage: {downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha} use cache {useActionArchiveCache} has cache {hasActionArchiveCache}"
|
||||
});
|
||||
}
|
||||
|
||||
if (!useActionArchiveCache)
|
||||
{
|
||||
@@ -878,18 +871,11 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
int exitCode = await processInvoker.ExecuteAsync(stagingDirectory, tar, $"-xzf \"{archiveFile}\"", null, executionContext.CancellationToken);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.DetailUntarFailure") == true)
|
||||
{
|
||||
var fileInfo = new FileInfo(archiveFile);
|
||||
var sha256hash = await IOUtil.GetFileContentSha256HashAsync(archiveFile);
|
||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile} (SHA256 '{sha256hash}', size '{fileInfo.Length}' bytes, tar outputs '{string.Join(' ', tarOutputs)}'). Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1032,13 +1018,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var step in compositeAction.Steps)
|
||||
{
|
||||
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && step.Reference.Type != Pipelines.ActionSourceType.Script)
|
||||
{
|
||||
throw new Exception("`uses:` keyword is not currently supported.");
|
||||
}
|
||||
}
|
||||
return setupInfo;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -91,13 +91,13 @@ namespace GitHub.Runner.Worker
|
||||
string phaseName = executionContext.Global.Variables.System_PhaseDisplayName ?? "UnknownPhaseName";
|
||||
|
||||
// zip the files
|
||||
string diagnosticsZipFileName = $"{buildName}-{phaseName}.zip";
|
||||
string diagnosticsZipFileName = $"{buildName}-{IOUtil.ReplaceInvalidFileNameChars(phaseName)}.zip";
|
||||
string diagnosticsZipFilePath = Path.Combine(supportRootFolder, diagnosticsZipFileName);
|
||||
ZipFile.CreateFromDirectory(supportFilesFolder, diagnosticsZipFilePath);
|
||||
|
||||
// upload the json metadata file
|
||||
executionContext.Debug("Uploading diagnostic metadata file.");
|
||||
string metadataFileName = $"diagnostics-{buildName}-{phaseName}.json";
|
||||
string metadataFileName = $"diagnostics-{buildName}-{IOUtil.ReplaceInvalidFileNameChars(phaseName)}.json";
|
||||
string metadataFilePath = Path.Combine(supportFilesFolder, metadataFileName);
|
||||
string phaseResult = GetTaskResultAsString(executionContext.Result);
|
||||
|
||||
|
||||
@@ -82,42 +82,21 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
#if OS_WINDOWS
|
||||
shellCommand = "pwsh";
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
if (string.IsNullOrEmpty(shellCommandPath))
|
||||
{
|
||||
shellCommand = "powershell";
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
shellCommand = "sh";
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2("bash", false, Trace, prependPath) ?? WhichUtil.Which2("sh", true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
}
|
||||
@@ -126,16 +105,9 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||
shellCommand = parsed.shellCommand;
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
}
|
||||
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
if (string.IsNullOrEmpty(argFormat))
|
||||
@@ -216,38 +188,17 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
shellCommand = "pwsh";
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
if (string.IsNullOrEmpty(commandPath))
|
||||
{
|
||||
shellCommand = "powershell";
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
}
|
||||
ArgUtil.NotNullOrEmpty(commandPath, "Default Shell");
|
||||
#else
|
||||
shellCommand = "sh";
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2("bash", false, Trace, prependPath) ?? WhichUtil.Which2("sh", true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
#endif
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
}
|
||||
@@ -258,14 +209,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
if (!IsActionStep && systemShells.Contains(shell))
|
||||
{
|
||||
shellCommand = shell;
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shell, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
if (shell == "bash")
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat("sh");
|
||||
@@ -280,14 +224,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||
shellCommand = parsed.shellCommand;
|
||||
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
if (string.IsNullOrEmpty(argFormat))
|
||||
{
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace GitHub.Runner.Worker
|
||||
Trace.Info("Job ID {0}", message.JobId);
|
||||
|
||||
DateTime jobStartTimeUtc = DateTime.UtcNow;
|
||||
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||
IRunnerService server = null;
|
||||
|
||||
// add orchestration id to useragent for better correlation.
|
||||
@@ -54,13 +55,6 @@ namespace GitHub.Runner.Worker
|
||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||
}
|
||||
|
||||
var jobServerQueueTelemetry = false;
|
||||
if (message.Variables.TryGetValue("DistributedTask.EnableJobServerQueueTelemetry", out VariableValue enableJobServerQueueTelemetry) &&
|
||||
!string.IsNullOrEmpty(enableJobServerQueueTelemetry?.Value))
|
||||
{
|
||||
jobServerQueueTelemetry = StringUtil.ConvertToBoolean(enableJobServerQueueTelemetry.Value);
|
||||
}
|
||||
|
||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||
{
|
||||
@@ -82,7 +76,7 @@ namespace GitHub.Runner.Worker
|
||||
launchServer.InitializeLaunchClient(new Uri(launchReceiverEndpoint), accessToken);
|
||||
}
|
||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||
_jobServerQueue.Start(message, resultsServiceOnly: true, enableTelemetry: jobServerQueueTelemetry);
|
||||
_jobServerQueue.Start(message, resultsServiceOnly: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -104,7 +98,7 @@ namespace GitHub.Runner.Worker
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, delegatingHandlers);
|
||||
await jobServer.ConnectAsync(jobConnection);
|
||||
|
||||
_jobServerQueue.Start(message, enableTelemetry: jobServerQueueTelemetry);
|
||||
_jobServerQueue.Start(message);
|
||||
server = jobServer;
|
||||
}
|
||||
|
||||
@@ -164,8 +158,6 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
jobContext.SetRunnerContext("os", VarUtil.OS);
|
||||
jobContext.SetRunnerContext("arch", VarUtil.OSArchitecture);
|
||||
|
||||
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||
jobContext.SetRunnerContext("name", _runnerSettings.AgentName);
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(WellKnownDistributedTaskVariables.RunnerEnvironment, out var runnerEnvironment))
|
||||
|
||||
95
src/Sdk/DTWebApi/WebApi/ActionsRunServerHttpClient.cs
Normal file
95
src/Sdk/DTWebApi/WebApi/ActionsRunServerHttpClient.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[ResourceArea(TaskResourceIds.AreaId)]
|
||||
public class ActionsRunServerHttpClient : TaskAgentHttpClient
|
||||
{
|
||||
private static readonly JsonSerializerSettings s_serializerSettings;
|
||||
|
||||
static ActionsRunServerHttpClient()
|
||||
{
|
||||
s_serializerSettings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
||||
s_serializerSettings.DateParseHandling = DateParseHandling.None;
|
||||
s_serializerSettings.FloatParseHandling = FloatParseHandling.Double;
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials)
|
||||
: base(baseUrl, credentials)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials,
|
||||
VssHttpRequestSettings settings)
|
||||
: base(baseUrl, credentials, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials,
|
||||
params DelegatingHandler[] handlers)
|
||||
: base(baseUrl, credentials, handlers)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials,
|
||||
VssHttpRequestSettings settings,
|
||||
params DelegatingHandler[] handlers)
|
||||
: base(baseUrl, credentials, settings, handlers)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
HttpMessageHandler pipeline,
|
||||
Boolean disposeHandler)
|
||||
: base(baseUrl, pipeline, disposeHandler)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||
string messageId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("GET");
|
||||
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
|
||||
object routeValues = new { messageId = messageId };
|
||||
|
||||
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task<T> ReadJsonContentAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<T>(json, s_serializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,24 +141,6 @@ namespace GitHub.DistributedTask.WebApi
|
||||
return ReplaceAgentAsync(poolId, agent.Id, agent, userState, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||
string messageId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("GET");
|
||||
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
|
||||
object routeValues = new { messageId = messageId };
|
||||
|
||||
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected Task<T> SendAsync<T>(
|
||||
HttpMethod method,
|
||||
Guid locationId,
|
||||
|
||||
@@ -9,6 +9,7 @@ using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.OAuth;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using Sdk.RSWebApi.Contracts;
|
||||
using Sdk.WebApi.WebApi;
|
||||
|
||||
@@ -16,6 +17,15 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
public class RunServiceHttpClient : RawHttpClientBase
|
||||
{
|
||||
private static readonly JsonSerializerSettings s_serializerSettings;
|
||||
|
||||
static RunServiceHttpClient()
|
||||
{
|
||||
s_serializerSettings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
||||
s_serializerSettings.DateParseHandling = DateParseHandling.None;
|
||||
s_serializerSettings.FloatParseHandling = FloatParseHandling.Double;
|
||||
}
|
||||
|
||||
public RunServiceHttpClient(
|
||||
Uri baseUrl,
|
||||
VssOAuthCredential credentials)
|
||||
@@ -174,5 +184,11 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
throw new Exception($"Failed to renew job: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<T> ReadJsonContentAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<T>(json, s_serializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
BrokerMessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
_brokerServer
|
||||
.Verify(x => x.CreateSessionAsync(
|
||||
It.Is<TaskAgentSession>(y => y != null),
|
||||
|
||||
@@ -75,11 +75,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
_settings.PoolId,
|
||||
@@ -135,11 +135,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
@@ -185,8 +185,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Setup(x => x.DeleteAgentSessionAsync(
|
||||
@@ -245,10 +245,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
@@ -309,8 +309,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
var arMessages = new TaskAgentMessage[]
|
||||
{
|
||||
@@ -390,8 +390,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
var brokerMigrationMesage = new BrokerMigrationMessage(new Uri("https://actions.broker.com"));
|
||||
|
||||
@@ -497,11 +497,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
_settings.PoolId,
|
||||
@@ -541,8 +541,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Setup(x => x.GetAgentMessageAsync(
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -184,7 +184,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(configureAsService);
|
||||
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Failure));
|
||||
|
||||
var runner = new Runner.Listener.Runner();
|
||||
runner.Initialize(hc);
|
||||
@@ -217,7 +217,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(false);
|
||||
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Failure));
|
||||
|
||||
var runner = new Runner.Listener.Runner();
|
||||
runner.Initialize(hc);
|
||||
@@ -263,7 +263,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -363,7 +363,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -458,7 +458,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
|
||||
@@ -960,6 +960,33 @@ namespace GitHub.Runner.Common.Tests.Util
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void ReplaceInvalidFileNameChars()
|
||||
{
|
||||
Assert.Equal(string.Empty, IOUtil.ReplaceInvalidFileNameChars(null));
|
||||
Assert.Equal(string.Empty, IOUtil.ReplaceInvalidFileNameChars(string.Empty));
|
||||
Assert.Equal("hello.txt", IOUtil.ReplaceInvalidFileNameChars("hello.txt"));
|
||||
#if OS_WINDOWS
|
||||
// Refer https://github.com/dotnet/runtime/blob/ce84f1d8a3f12711bad678a33efbc37b461f684f/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs#L15
|
||||
Assert.Equal(
|
||||
"1_ 2_ 3_ 4_ 5_ 6_ 7_ 8_ 9_ 10_ 11_ 12_ 13_ 14_ 15_ 16_ 17_ 18_ 19_ 20_ 21_ 22_ 23_ 24_ 25_ 26_ 27_ 28_ 29_ 30_ 31_ 32_ 33_ 34_ 35_ 36_ 37_ 38_ 39_ 40_ 41_",
|
||||
IOUtil.ReplaceInvalidFileNameChars($"1\" 2< 3> 4| 5\0 6{(char)1} 7{(char)2} 8{(char)3} 9{(char)4} 10{(char)5} 11{(char)6} 12{(char)7} 13{(char)8} 14{(char)9} 15{(char)10} 16{(char)11} 17{(char)12} 18{(char)13} 19{(char)14} 20{(char)15} 21{(char)16} 22{(char)17} 23{(char)18} 24{(char)19} 25{(char)20} 26{(char)21} 27{(char)22} 28{(char)23} 29{(char)24} 30{(char)25} 31{(char)26} 32{(char)27} 33{(char)28} 34{(char)29} 35{(char)30} 36{(char)31} 37: 38* 39? 40\\ 41/"));
|
||||
#else
|
||||
// Refer https://github.com/dotnet/runtime/blob/ce84f1d8a3f12711bad678a33efbc37b461f684f/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs#L12
|
||||
Assert.Equal("1_ 2_", IOUtil.ReplaceInvalidFileNameChars("1\0 2/"));
|
||||
#endif
|
||||
Assert.Equal("_leading", IOUtil.ReplaceInvalidFileNameChars("/leading"));
|
||||
Assert.Equal("__consecutive leading", IOUtil.ReplaceInvalidFileNameChars("//consecutive leading"));
|
||||
Assert.Equal("trailing_", IOUtil.ReplaceInvalidFileNameChars("trailing/"));
|
||||
Assert.Equal("consecutive trailing__", IOUtil.ReplaceInvalidFileNameChars("consecutive trailing//"));
|
||||
Assert.Equal("middle_middle", IOUtil.ReplaceInvalidFileNameChars("middle/middle"));
|
||||
Assert.Equal("consecutive middle__consecutive middle", IOUtil.ReplaceInvalidFileNameChars("consecutive middle//consecutive middle"));
|
||||
Assert.Equal("_leading_middle_trailing_", IOUtil.ReplaceInvalidFileNameChars("/leading/middle/trailing/"));
|
||||
Assert.Equal("__consecutive leading__consecutive middle__consecutive trailing__", IOUtil.ReplaceInvalidFileNameChars("//consecutive leading//consecutive middle//consecutive trailing//"));
|
||||
}
|
||||
|
||||
private static async Task CreateDirectoryReparsePoint(IHostContext context, string link, string target)
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
|
||||
@@ -212,210 +212,5 @@ namespace GitHub.Runner.Common.Tests.Util
|
||||
File.Delete(brokenSymlink);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void UseWhich2FindGit()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
string gitPath = WhichUtil.Which2("git", trace: trace);
|
||||
|
||||
trace.Info($"Which(\"git\") returns: {gitPath ?? string.Empty}");
|
||||
|
||||
// Assert.
|
||||
Assert.True(!string.IsNullOrEmpty(gitPath) && File.Exists(gitPath), $"Unable to find Git through: {nameof(WhichUtil.Which)}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ReturnsNullWhenNotFound()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
string nosuch = WhichUtil.Which2("no-such-file-cf7e351f", trace: trace);
|
||||
|
||||
trace.Info($"result: {nosuch ?? string.Empty}");
|
||||
|
||||
// Assert.
|
||||
Assert.True(string.IsNullOrEmpty(nosuch), "Path should not be resolved");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ThrowsWhenRequireAndNotFound()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
try
|
||||
{
|
||||
WhichUtil.Which2("no-such-file-cf7e351f", require: true, trace: trace);
|
||||
throw new Exception("which should have thrown");
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Assert.Equal("no-such-file-cf7e351f", ex.FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandleFullyQualifiedPath()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
var gitPath = WhichUtil.Which2("git", require: true, trace: trace);
|
||||
var gitPath2 = WhichUtil.Which2(gitPath, require: true, trace: trace);
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(gitPath, gitPath2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandlesSymlinkToTargetFullPath()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}.exe";
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}";
|
||||
#endif
|
||||
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
|
||||
using (File.Create(target))
|
||||
{
|
||||
File.CreateSymbolicLink(symlink, target);
|
||||
|
||||
// Act.
|
||||
var result = WhichUtil.Which2(symlinkName, require: true, trace: trace);
|
||||
|
||||
// Assert
|
||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find symlink through: {nameof(WhichUtil.Which)}");
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Cleanup
|
||||
File.Delete(symlink);
|
||||
File.Delete(target);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandlesSymlinkToTargetRelativePath()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
||||
string targetName = $"target-{Guid.NewGuid()}.exe";
|
||||
string target = Path.GetTempPath() + targetName;
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
||||
string targetName = $"target-{Guid.NewGuid()}";
|
||||
string target = Path.GetTempPath() + targetName;
|
||||
#endif
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
|
||||
using (File.Create(target))
|
||||
{
|
||||
File.CreateSymbolicLink(symlink, targetName);
|
||||
|
||||
// Act.
|
||||
var result = WhichUtil.Which2(symlinkName, require: true, trace: trace);
|
||||
|
||||
// Assert
|
||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find {symlinkName} through: {nameof(WhichUtil.Which)}");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
File.Delete(symlink);
|
||||
File.Delete(target);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ThrowsWhenSymlinkBroken()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}.exe";
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}";
|
||||
#endif
|
||||
|
||||
|
||||
string target = "no-such-file-cf7e351f";
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
File.CreateSymbolicLink(brokenSymlink, target);
|
||||
|
||||
// Act.
|
||||
var exception = Assert.Throws<FileNotFoundException>(() => WhichUtil.Which2(brokenSymlinkName, require: true, trace: trace));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(brokenSymlinkName, exception.FileName);
|
||||
|
||||
// Cleanup
|
||||
File.Delete(brokenSymlink);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,8 +382,6 @@ runs:
|
||||
}
|
||||
};
|
||||
|
||||
_ec.Object.Global.Variables.Set("DistributedTask.UseActionArchiveCache", bool.TrueString);
|
||||
|
||||
//Act
|
||||
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||
|
||||
@@ -2375,10 +2373,6 @@ runs:
|
||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||
_ec.Setup(x => x.Root).Returns(new GitHub.Runner.Worker.ExecutionContext());
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
if (enableComposite)
|
||||
{
|
||||
variables["DistributedTask.EnableCompositeActions"] = "true";
|
||||
}
|
||||
_ec.Object.Global.Variables = new Variables(_hc, variables);
|
||||
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||
|
||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="6.0.420"
|
||||
DOTNETSDK_VERSION="6.0.421"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.420"
|
||||
"version": "6.0.421"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.315.0
|
||||
2.316.0
|
||||
|
||||
Reference in New Issue
Block a user