Compare commits

..

5 Commits

Author SHA1 Message Date
Julio Barba
82e9857d4f Implement new echo behavior and command (#139)
* Remove controlling echoing by command

* Add 'echo on' and 'echo off' action commands

* PR feedback and add L0 tests

* Register new command

* Eric's PR feedback

* Tweak logging a bit

* Rename EchoOnActionCommandSuccess -> EchoOnActionCommand

* More PR reaction

* Make warning messages in Action Commands not rely on context from echo commands
2019-10-25 10:38:56 -04:00
Tingluo Huang
afd233b735 consume dotnet core 3.0 (#127)
* consume dotnet core 3.0

* update linux dependency doc.
2019-10-24 16:52:29 -04:00
Thomas Boop
83be145bfd Update build.yml 2019-10-24 14:05:10 -04:00
Thomas Boop
6e20aceaff Update build.yml 2019-10-24 14:00:56 -04:00
Lucas Killgore
e89148e33e Don't retry uploads when the http status code response from the server is in the 400's (#131)
* Don't retry uploads when the http status code
response from the server is in the 400's

* Don't retry on fast-fail

* Feedback from code review

* Always try to attach any uploaded files to the build
Don't fast-fail on 'Conflict'

* Add dispose

* Refactored upload code.
Called out specialized 'Conflict' logic.

* Added typed exception.
2019-10-23 12:48:10 -04:00
11 changed files with 356 additions and 182 deletions

View File

@@ -1,12 +1,12 @@
## Features
- N/A
- Set Default shell to powershell for windows runners (#135)
- Use Powershell as fallback if Powershell Core is not available for default shell on windows (#142)
## Bugs
- Reverted removal of additional fields error and warning fields (#147)
- Actions cache would incorrectly cache the action if the tag was updated (#148)
- Removed unintended additional fields on error and warning commands (#137)
## Misc
- Updated to .NET Core 3.0 (#127)
- N/A
## Agent Downloads

View File

@@ -54,6 +54,7 @@ namespace GitHub.Runner.Common
Add<T>(extensions, "GitHub.Runner.Worker.DebugCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.GroupCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.EchoCommandExtension, Runner.Worker");
break;
default:
// This should never happen.

View File

@@ -236,15 +236,15 @@ namespace GitHub.Runner.Plugins.Artifact
// try upload all files for the first time.
UploadResult uploadResult = await ParallelUploadAsync(context, files, maxConcurrentUploads, _uploadCancellationTokenSource.Token);
if (uploadResult.FailedFiles.Count == 0)
if (uploadResult.RetryFiles.Count == 0)
{
// all files have been upload succeed.
context.Output("File upload succeed.");
context.Output("File upload complete.");
return uploadResult.TotalFileSizeUploaded;
}
else
{
context.Output($"{uploadResult.FailedFiles.Count} files failed to upload, retry these files after a minute.");
context.Output($"{uploadResult.RetryFiles.Count} files failed to upload, retry these files after a minute.");
}
// Delay 1 min then retry failed files.
@@ -255,13 +255,13 @@ namespace GitHub.Runner.Plugins.Artifact
}
// Retry upload all failed files.
context.Output($"Start retry {uploadResult.FailedFiles.Count} failed files upload.");
UploadResult retryUploadResult = await ParallelUploadAsync(context, uploadResult.FailedFiles, maxConcurrentUploads, _uploadCancellationTokenSource.Token);
context.Output($"Start retry {uploadResult.RetryFiles.Count} failed files upload.");
UploadResult retryUploadResult = await ParallelUploadAsync(context, uploadResult.RetryFiles, maxConcurrentUploads, _uploadCancellationTokenSource.Token);
if (retryUploadResult.FailedFiles.Count == 0)
if (retryUploadResult.RetryFiles.Count == 0)
{
// all files have been upload succeed after retry.
context.Output("File upload succeed after retry.");
context.Output("File upload complete after retry.");
return uploadResult.TotalFileSizeUploaded + retryUploadResult.TotalFileSizeUploaded;
}
else
@@ -465,75 +465,61 @@ namespace GitHub.Runner.Plugins.Artifact
using (FileStream fs = File.Open(fileToUpload, FileMode.Open, FileAccess.Read, FileShare.Read))
{
string itemPath = (_containerPath.TrimEnd('/') + "/" + fileToUpload.Remove(0, _sourceParentDirectory.Length + 1)).Replace('\\', '/');
uploadTimer.Restart();
bool catchExceptionDuringUpload = false;
HttpResponseMessage response = null;
bool failAndExit = false;
try
{
response = await _fileContainerHttpClient.UploadFileAsync(_containerId, itemPath, fs, _projectId, cancellationToken: token, chunkSize: 4 * 1024 * 1024);
uploadTimer.Restart();
using (HttpResponseMessage response = await _fileContainerHttpClient.UploadFileAsync(_containerId, itemPath, fs, _projectId, cancellationToken: token, chunkSize: 4 * 1024 * 1024))
{
if (response == null || response.StatusCode != HttpStatusCode.Created)
{
context.Output($"Unable to copy file to server StatusCode={response?.StatusCode}: {response?.ReasonPhrase}. Source file path: {fileToUpload}. Target server path: {itemPath}");
if (response?.StatusCode == HttpStatusCode.Conflict)
{
// fail upload task but continue with any other files
context.Error($"Error '{fileToUpload}' has already been uploaded.");
}
else if (_fileContainerHttpClient.IsFastFailResponse(response))
{
// Fast fail: we received an http status code where we should abandon our efforts
context.Output($"Cannot continue uploading files, so draining upload queue of {_fileUploadQueue.Count} items.");
DrainUploadQueue(context);
failedFiles.Clear();
failAndExit = true;
throw new UploadFailedException($"Critical failure uploading '{fileToUpload}'");
}
else
{
context.Debug($"Adding '{fileToUpload}' to retry list.");
failedFiles.Add(fileToUpload);
}
throw new UploadFailedException($"Http failure response '{response?.StatusCode}': '{response?.ReasonPhrase}' while uploading '{fileToUpload}'");
}
uploadTimer.Stop();
context.Debug($"File: '{fileToUpload}' took {uploadTimer.ElapsedMilliseconds} milliseconds to finish upload");
uploadedSize += fs.Length;
OutputLogForFile(context, fileToUpload, $"Detail upload trace for file: {itemPath}", context.Debug);
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
context.Output($"File upload has been cancelled during upload file: '{fileToUpload}'.");
if (response != null)
{
response.Dispose();
response = null;
}
throw;
}
catch (Exception ex)
{
catchExceptionDuringUpload = true;
context.Output($"Fail to upload '{fileToUpload}' due to '{ex.Message}'.");
context.Output(ex.ToString());
}
uploadTimer.Stop();
if (catchExceptionDuringUpload || (response != null && response.StatusCode != HttpStatusCode.Created))
{
if (response != null)
OutputLogForFile(context, fileToUpload, $"Detail upload trace for file that fail to upload: {itemPath}", context.Output);
if (failAndExit)
{
context.Output($"Unable to copy file to server StatusCode={response.StatusCode}: {response.ReasonPhrase}. Source file path: {fileToUpload}. Target server path: {itemPath}");
context.Debug("Exiting upload.");
throw;
}
// output detail upload trace for the file.
ConcurrentQueue<string> logQueue;
if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue))
{
context.Output($"Detail upload trace for file that fail to upload: {itemPath}");
string message;
while (logQueue.TryDequeue(out message))
{
context.Output(message);
}
}
// tracking file that failed to upload.
failedFiles.Add(fileToUpload);
}
else
{
context.Debug($"File: '{fileToUpload}' took {uploadTimer.ElapsedMilliseconds} milliseconds to finish upload");
uploadedSize += fs.Length;
// debug detail upload trace for the file.
ConcurrentQueue<string> logQueue;
if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue))
{
context.Debug($"Detail upload trace for file: {itemPath}");
string message;
while (logQueue.TryDequeue(out message))
{
context.Debug(message);
}
}
}
if (response != null)
{
response.Dispose();
response = null;
}
}
@@ -590,6 +576,30 @@ namespace GitHub.Runner.Plugins.Artifact
}
}
private void DrainUploadQueue(RunnerActionPluginExecutionContext context)
{
while (_fileUploadQueue.TryDequeue(out string fileToUpload))
{
context.Debug($"Clearing upload queue: '{fileToUpload}'");
Interlocked.Increment(ref _uploadFilesProcessed);
}
}
private void OutputLogForFile(RunnerActionPluginExecutionContext context, string itemPath, string logDescription, Action<string> log)
{
// output detail upload trace for the file.
ConcurrentQueue<string> logQueue;
if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue))
{
log(logDescription);
string message;
while (logQueue.TryDequeue(out message))
{
log(message);
}
}
}
private void UploadFileTraceReportReceived(object sender, ReportTraceEventArgs e)
{
ConcurrentQueue<string> logQueue = _fileUploadTraceLog.GetOrAdd(e.File, new ConcurrentQueue<string>());
@@ -607,22 +617,22 @@ namespace GitHub.Runner.Plugins.Artifact
{
public UploadResult()
{
FailedFiles = new List<string>();
RetryFiles = new List<string>();
TotalFileSizeUploaded = 0;
}
public UploadResult(List<string> failedFiles, long totalFileSizeUploaded)
public UploadResult(List<string> retryFiles, long totalFileSizeUploaded)
{
FailedFiles = failedFiles;
RetryFiles = retryFiles ?? new List<string>();
TotalFileSizeUploaded = totalFileSizeUploaded;
}
public List<string> FailedFiles { get; set; }
public List<string> RetryFiles { get; set; }
public long TotalFileSizeUploaded { get; set; }
public void AddUploadResult(UploadResult resultToAdd)
{
this.FailedFiles.AddRange(resultToAdd.FailedFiles);
this.RetryFiles.AddRange(resultToAdd.RetryFiles);
this.TotalFileSizeUploaded += resultToAdd.TotalFileSizeUploaded;
}
}
@@ -657,4 +667,19 @@ namespace GitHub.Runner.Plugins.Artifact
this.FailedFiles.AddRange(resultToAdd.FailedFiles);
}
}
public class UploadFailedException : Exception
{
public UploadFailedException()
: base()
{ }
public UploadFailedException(string message)
: base(message)
{ }
public UploadFailedException(string message, Exception inner)
: base(message, inner)
{ }
}
}

View File

@@ -74,17 +74,22 @@ namespace GitHub.Runner.Plugins.Artifact
context.Output($"Uploading artifact '{artifactName}' from '{fullPath}' for run #{buildId}");
FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId, containerId, artifactName);
long size = await fileContainerHelper.CopyToContainerAsync(context, fullPath, token);
var propertiesDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
propertiesDictionary.Add("artifactsize", size.ToString());
string fileContainerFullPath = StringUtil.Format($"#/{containerId}/{artifactName}");
context.Output($"Uploaded '{fullPath}' to server");
BuildServer buildHelper = new BuildServer(context.VssConnection);
string jobId = context.Variables.GetValueOrDefault(WellKnownDistributedTaskVariables.JobId).Value ?? string.Empty;
var artifact = await buildHelper.AssociateArtifact(projectId, buildId, jobId, artifactName, ArtifactResourceTypes.Container, fileContainerFullPath, propertiesDictionary, token);
context.Output($"Associated artifact {artifactName} ({artifact.Id}) with run #{buildId}");
try
{
long size = await fileContainerHelper.CopyToContainerAsync(context, fullPath, token);
propertiesDictionary.Add("artifactsize", size.ToString());
context.Output($"Uploaded '{size}' bytes from '{fullPath}' to server");
}
// if any of the results were successful, make sure to attach them to the build
finally
{
string fileContainerFullPath = StringUtil.Format($"#/{containerId}/{artifactName}");
BuildServer buildHelper = new BuildServer(context.VssConnection);
string jobId = context.Variables.GetValueOrDefault(WellKnownDistributedTaskVariables.JobId).Value ?? string.Empty;
var artifact = await buildHelper.AssociateArtifact(projectId, buildId, jobId, artifactName, ArtifactResourceTypes.Container, fileContainerFullPath, propertiesDictionary, token);
context.Output($"Associated artifact {artifactName} ({artifact.Id}) with run #{buildId}");
}
}
}
}

View File

@@ -107,26 +107,35 @@ namespace GitHub.Runner.Worker
}
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
{
bool omitEcho;
bool commandHasBeenOutput = false;
try
{
extension.ProcessCommand(context, input, actionCommand, out omitEcho);
if (context.EchoOnActionCommand)
{
context.Output(input);
context.Debug($"Processing command '{actionCommand.Command}'");
commandHasBeenOutput = true;
}
extension.ProcessCommand(context, input, actionCommand);
if (context.EchoOnActionCommand)
{
context.Debug($"Processed command '{actionCommand.Command}' successfully");
}
}
catch (Exception ex)
{
omitEcho = true;
context.Output(input);
if (!commandHasBeenOutput)
{
context.Output(input);
}
context.Error($"Unable to process command '{input}' successfully.");
context.Error(ex);
context.CommandResult = TaskResult.Failed;
}
if (!omitEcho)
{
context.Output(input);
context.Debug($"Processed command");
}
}
else
{
@@ -143,7 +152,7 @@ namespace GitHub.Runner.Worker
{
string Command { get; }
void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho);
void ProcessCommand(IExecutionContext context, string line, ActionCommand command);
}
public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, IActionCommandExtension
@@ -152,7 +161,7 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetRepoPathCommandProperties.repoFullName, out string repoFullName) || string.IsNullOrEmpty(repoFullName))
{
@@ -166,8 +175,6 @@ namespace GitHub.Runner.Worker
var directoryManager = HostContext.GetService<IPipelineDirectoryManager>();
var trackingConfig = directoryManager.UpdateRepositoryDirectory(context, repoFullName, command.Data, StringUtil.ConvertToBoolean(workspaceRepo));
omitEcho = true;
}
private static class SetRepoPathCommandProperties
@@ -183,7 +190,7 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
{
@@ -192,9 +199,7 @@ namespace GitHub.Runner.Worker
context.EnvironmentVariables[envName] = command.Data;
context.SetEnvContext(envName, command.Data);
context.Output(line);
context.Debug($"{envName}='{command.Data}'");
omitEcho = true;
}
private static class SetEnvCommandProperties
@@ -209,7 +214,7 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
{
@@ -217,9 +222,7 @@ namespace GitHub.Runner.Worker
}
context.SetOutput(outputName, command.Data, out var reference);
context.Output(line);
context.Debug($"{reference}='{command.Data}'");
omitEcho = true;
}
private static class SetOutputCommandProperties
@@ -234,7 +237,7 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
{
@@ -243,7 +246,6 @@ namespace GitHub.Runner.Worker
context.IntraActionState[stateName] = command.Data;
context.Debug($"Save intra-action state {stateName} = {command.Data}");
omitEcho = true;
}
private static class SaveStateCommandProperties
@@ -258,19 +260,17 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (string.IsNullOrWhiteSpace(command.Data))
{
context.Warning("Can't add secret mask for empty string.");
context.Warning("Can't add secret mask for empty string in ##[add-mask] command.");
}
else
{
HostContext.SecretMasker.AddValue(command.Data);
Trace.Info($"Add new secret mask with length of {command.Data.Length}");
}
omitEcho = true;
}
}
@@ -280,12 +280,11 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
ArgUtil.NotNullOrEmpty(command.Data, "path");
context.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
context.PrependPath.Add(command.Data);
omitEcho = false;
}
}
@@ -295,9 +294,8 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
omitEcho = false;
var file = command.Data;
// File is required
@@ -342,23 +340,22 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
omitEcho = false;
command.Properties.TryGetValue(RemoveMatcherCommandProperties.Owner, out string owner);
var file = command.Data;
// Owner and file are mutually exclusive
if (!string.IsNullOrEmpty(owner) && !string.IsNullOrEmpty(file))
{
context.Warning("Either specify a matcher owner name or a file path. Both values cannot be set.");
context.Warning("Either specify an owner name or a file path in ##[remove-matcher] command. Both values cannot be set.");
return;
}
// Owner or file is required
if (string.IsNullOrEmpty(owner) && string.IsNullOrEmpty(file))
{
context.Warning("Either a matcher owner name or a file path must be specified.");
context.Warning("Either an owner name or a file path must be specified in ##[remove-matcher] command.");
return;
}
@@ -410,9 +407,8 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
{
omitEcho = true;
context.Debug(command.Data);
}
}
@@ -438,13 +434,8 @@ namespace GitHub.Runner.Worker
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
{
omitEcho = true;
command.Properties.TryGetValue(IssueCommandProperties.File, out string file);
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);
Issue issue = new Issue()
{
Category = "General",
@@ -452,54 +443,8 @@ namespace GitHub.Runner.Worker
Message = command.Data
};
if (!string.IsNullOrEmpty(file))
{
issue.Category = "Code";
if (context.Container != null)
{
// Translate file path back from container path
file = context.Container.TranslateToHostPath(file);
command.Properties[IssueCommandProperties.File] = file;
}
// Get the values that represent the server path given a local path
string repoName = context.GetGitHubContext("repository");
var repoPath = context.GetGitHubContext("workspace");
string relativeSourcePath = IOUtil.MakeRelative(file, repoPath);
if (!string.Equals(relativeSourcePath, file, IOUtil.FilePathStringComparison))
{
// add repo info
if (!string.IsNullOrEmpty(repoName))
{
command.Properties["repo"] = repoName;
}
if (!string.IsNullOrEmpty(relativeSourcePath))
{
// replace sourcePath with the new relative path
// prefer `/` on all platforms
command.Properties[IssueCommandProperties.File] = relativeSourcePath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
}
}
foreach (var property in command.Properties)
{
issue.Data[property.Key] = property.Value;
}
context.AddIssue(issue);
}
private static class IssueCommandProperties
{
public const String File = "file";
public const String Line = "line";
public const String Column = "col";
}
}
public sealed class GroupCommandExtension : GroupingCommandExtension
@@ -517,11 +462,37 @@ namespace GitHub.Runner.Worker
public abstract string Command { get; }
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
var data = this is GroupCommandExtension ? command.Data : string.Empty;
context.Output($"##[{Command}]{data}");
omitEcho = true;
}
}
public sealed class EchoCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "echo";
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
ArgUtil.NotNullOrEmpty(command.Data, "value");
switch (command.Data.Trim().ToUpperInvariant())
{
case "ON":
context.EchoOnActionCommand = true;
context.Debug("Setting echo command value to 'on'");
break;
case "OFF":
context.EchoOnActionCommand = false;
context.Debug("Setting echo command value to 'off'");
break;
default:
throw new Exception($"Invalid echo command value. Possible values can be: 'on', 'off'. Current value is: '{command.Data}'.");
break;
}
}
}
}

View File

@@ -58,9 +58,6 @@ namespace GitHub.Runner.Worker
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is depreciated. Please remove it from the repository's secrets");
}
// Clear the cache (local runner)
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
foreach (var action in actions)
{
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
@@ -448,7 +445,7 @@ namespace GitHub.Runner.Worker
}
else
{
// make sure we get a clean folder ready to use.
// make sure we get an clean folder ready to use.
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
Directory.CreateDirectory(destDirectory);
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");

View File

@@ -12,14 +12,14 @@ using GitHub.Services.WebApi;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using Newtonsoft.Json;
using System.Text;
using System.Collections;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker
{
@@ -62,6 +62,8 @@ namespace GitHub.Runner.Worker
// Only job level ExecutionContext has PostJobSteps
Stack<IStep> PostJobSteps { get; }
bool EchoOnActionCommand { get; set; }
// Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
void CancelToken();
@@ -153,6 +155,8 @@ namespace GitHub.Runner.Worker
// Only job level ExecutionContext has PostJobSteps
public Stack<IStep> PostJobSteps { get; private set; }
public bool EchoOnActionCommand { get; set; }
public TaskResult? Result
{
@@ -292,6 +296,7 @@ namespace GitHub.Runner.Worker
child.PrependPath = PrependPath;
child.Container = Container;
child.ServiceContainers = ServiceContainers;
child.EchoOnActionCommand = EchoOnActionCommand;
if (recordOrder != null)
{
@@ -704,6 +709,9 @@ namespace GitHub.Runner.Worker
_logger = HostContext.CreateService<IPagingLogger>();
_logger.Setup(_mainTimelineId, _record.Id);
// Initialize 'echo on action command success' property, default to false, unless Step_Debug is set
EchoOnActionCommand = Variables.Step_Debug ?? false;
// Verbosity (from GitHub.Step_Debug).
WriteDebug = Variables.Step_Debug ?? false;

View File

@@ -397,6 +397,11 @@ namespace GitHub.Services.FileContainer.Client
{
break;
}
else if (IsFastFailResponse(response))
{
FileUploadTrace(itemPath, $"Chunk '{currentChunk}' attempt '{attempt}' of file '{itemPath}' received non-success status code {response.StatusCode} for sending request and cannot continue.");
break;
}
else
{
FileUploadTrace(itemPath, $"Chunk '{currentChunk}' attempt '{attempt}' of file '{itemPath}' received non-success status code {response.StatusCode} for sending request.");
@@ -538,6 +543,17 @@ namespace GitHub.Services.FileContainer.Client
cancellationToken);
}
public bool IsFastFailResponse(HttpResponseMessage response)
{
int statusCode = (int)response?.StatusCode;
return statusCode >= 400 && statusCode <= 499;
}
protected override bool ShouldThrowError(HttpResponseMessage response)
{
return !response.IsSuccessStatusCode && !IsFastFailResponse(response);
}
private async Task<HttpResponseMessage> ContainerGetRequestAsync(
Int64 containerId,
String itemPath,

View File

@@ -5,6 +5,7 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using Xunit;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Worker
{
@@ -146,5 +147,159 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.True(commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EchoProcessCommand()
{
using (TestHostContext _hc = new TestHostContext(this))
{
var extensionManager = new Mock<IExtensionManager>();
var echoCommand = new EchoCommandExtension();
echoCommand.Initialize(_hc);
extensionManager.Setup(x => x.GetExtensions<IActionCommandExtension>())
.Returns(new List<IActionCommandExtension>() { echoCommand });
_hc.SetSingleton<IExtensionManager>(extensionManager.Object);
Mock<IExecutionContext> _ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string tag, string line) =>
{
_hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.SetupAllProperties();
ActionCommandManager commandManager = new ActionCommandManager();
commandManager.Initialize(_hc);
Assert.False(_ec.Object.EchoOnActionCommand);
Assert.True(commandManager.TryProcessCommand(_ec.Object, "::echo::on"));
Assert.True(_ec.Object.EchoOnActionCommand);
Assert.True(commandManager.TryProcessCommand(_ec.Object, "::echo::off"));
Assert.False(_ec.Object.EchoOnActionCommand);
Assert.True(commandManager.TryProcessCommand(_ec.Object, "::echo::ON"));
Assert.True(_ec.Object.EchoOnActionCommand);
Assert.True(commandManager.TryProcessCommand(_ec.Object, "::echo::Off "));
Assert.False(_ec.Object.EchoOnActionCommand);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EchoProcessCommandDebugOn()
{
using (TestHostContext _hc = new TestHostContext(this))
{
// Set up a few things
// 1. Job request message (with ACTIONS_STEP_DEBUG = true)
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
JobEnvironment environment = new JobEnvironment();
environment.SystemConnection = new ServiceEndpoint();
List<TaskInstance> tasks = new List<TaskInstance>();
Guid JobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, environment, tasks));
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
// Some service dependencies
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
_hc.SetSingleton(jobServerQueue.Object);
var extensionManager = new Mock<IExtensionManager>();
var echoCommand = new EchoCommandExtension();
echoCommand.Initialize(_hc);
extensionManager.Setup(x => x.GetExtensions<IActionCommandExtension>())
.Returns(new List<IActionCommandExtension>() { echoCommand });
_hc.SetSingleton<IExtensionManager>(extensionManager.Object);
var configurationStore = new Mock<IConfigurationStore>();
configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings());
_hc.SetSingleton(configurationStore.Object);
var pagingLogger = new Mock<IPagingLogger>();
_hc.EnqueueInstance(pagingLogger.Object);
ActionCommandManager commandManager = new ActionCommandManager();
commandManager.Initialize(_hc);
var _ec = new Runner.Worker.ExecutionContext();
_ec.Initialize(_hc);
// Initialize the job (to exercise logic that sets EchoOnActionCommand)
_ec.InitializeJob(jobRequest, System.Threading.CancellationToken.None);
_ec.Complete();
Assert.True(_ec.EchoOnActionCommand);
Assert.True(commandManager.TryProcessCommand(_ec, "::echo::off"));
Assert.False(_ec.EchoOnActionCommand);
Assert.True(commandManager.TryProcessCommand(_ec, "::echo::on"));
Assert.True(_ec.EchoOnActionCommand);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EchoProcessCommandInvalid()
{
using (TestHostContext _hc = new TestHostContext(this))
{
var extensionManager = new Mock<IExtensionManager>();
var echoCommand = new EchoCommandExtension();
echoCommand.Initialize(_hc);
extensionManager.Setup(x => x.GetExtensions<IActionCommandExtension>())
.Returns(new List<IActionCommandExtension>() { echoCommand });
_hc.SetSingleton<IExtensionManager>(extensionManager.Object);
Mock<IExecutionContext> _ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string tag, string line) =>
{
_hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.SetupAllProperties();
ActionCommandManager commandManager = new ActionCommandManager();
commandManager.Initialize(_hc);
// Echo commands below are considered "processed", but are invalid
// 1. Invalid echo value
Assert.True(commandManager.TryProcessCommand(_ec.Object, "::echo::invalid"));
Assert.Equal(TaskResult.Failed, _ec.Object.CommandResult);
Assert.False(_ec.Object.EchoOnActionCommand);
// 2. No value
Assert.True(commandManager.TryProcessCommand(_ec.Object, "::echo::"));
Assert.Equal(TaskResult.Failed, _ec.Object.CommandResult);
Assert.False(_ec.Object.EchoOnActionCommand);
}
}
}
}

View File

@@ -233,7 +233,6 @@ namespace GitHub.Runner.Common.Tests.Worker
}
}
/*
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
@@ -273,7 +272,6 @@ namespace GitHub.Runner.Common.Tests.Worker
Teardown();
}
}
*/
[Fact]
[Trait("Level", "L0")]
@@ -308,7 +306,6 @@ namespace GitHub.Runner.Common.Tests.Worker
}
}
/*
#if OS_LINUX
[Fact]
[Trait("Level", "L0")]
@@ -775,7 +772,6 @@ namespace GitHub.Runner.Common.Tests.Worker
Teardown();
}
}
*/
[Fact]
[Trait("Level", "L0")]

View File

@@ -1 +1 @@
2.160.0
2.159.2