Compare commits

..

15 Commits

Author SHA1 Message Date
Thomas Boop
e0508abb77 fix composite annotations not appearing as expected 2022-09-09 09:46:07 -04:00
Nikola Jokic
75786756bb fix ACTIONS_RUNNER_CONTAINER_HOOKS name in ADR (#2098) 2022-09-06 10:45:00 -04:00
Ferenc Hammerl
5e0c2ef816 2.296.1 Release (#2092) (#2099)
* docker: escape key-value pair as -e KEY and VALUE being environment var

* removed code duplication, removed unused method and test

* add release notes

Co-authored-by: Nikola Jokic <nikola-jokic@github.com>

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
Co-authored-by: Nikola Jokic <nikola-jokic@github.com>
2022-09-02 15:43:22 +00:00
Nikola Jokic
95459dea5f docker: escape key-value pair as -e KEY and VALUE being environment var (#2091)
* docker: escape key-value pair as -e KEY and VALUE being environment var

* removed code duplication, removed unused method and test
2022-08-31 13:39:58 -04:00
Konrad Pabjan
59894790de Validate lines and columns for Annotations (#2082) 2022-08-24 16:02:51 -04:00
Ava Stancu
cba19c4d7e Release notes for 2.296.0 (#2078)
* Update releaseNote.md

* Update runnerversion
2022-08-23 10:42:40 -04:00
Nikola Jokic
01fd04464d Escaping key and quoting it to avoid key based command injection (#2062)
* escaping key and quoting it to avoid key based command injection

* extracted creation of flags to DockerUtil, with testing included
2022-08-23 10:42:29 -04:00
Tingluo Huang
1cb1779d6b Include step context name and start/finish time in step telemetry (#2069)
* Include step context name in telemetry.

* .
2022-08-22 21:26:52 -04:00
Nicholas Bergesen
42c86665a7 Display full job name and nested workflow details in log (#2049) 2022-08-22 17:20:58 -07:00
Ava Stancu
f9c2bf1dd7 Improved error logs for missing 'using' configuration in metadata file (#2052)
Co-authored-by: Octavia Stancu <avastancu@Octavias-MBP.home>
2022-08-16 17:17:42 +02:00
Ferenc Hammerl
84e7949457 Release notes 2.295.0 (#2046)
* Update releaseNote.md

* Update runnerversion

* Update releaseNote.md
2022-08-10 16:23:51 +02:00
Ferenc Hammerl
694d73d43c Fix broken run-helper update syntax and run-helper not updating on restart (#2050)
* Fix broken syntax in update wait for loop

* Update run-helper after each restart, not only at first
2022-08-09 16:50:52 +02:00
Ferenc Hammerl
352f201c62 Wait for update.sh|cmd to finish instead of waiting 5 seconds - before restarting a runner (#2044)
* update.sh new logic with creating a file and waiting for it for 30 sec

* pr refactor

* removing file when it's detected bu run-helper

* Update src/Misc/layoutroot/run-helper.sh.template

Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>

* clean up update.finished in Runner.Listener right before it starts a process _update.sh

* pr fix IOUtil.DeleteFile

* self update widnows version - cmd

* same logic for returnCode 4 - ephemeral

* Init var so cmd doesn't run into syntax errors

* Use constants, setting a var messed up ERRORLEVEL

* Use var for updatefile

Co-authored-by: stefanruvceski <ruvceskistefan@github.com>
Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
Co-authored-by: Ferenc Hammerl <hammerl.ferenc@gmail.com>
2022-08-08 23:37:43 +02:00
Pavel Iakovenko
503e50acb9 Support running Actions services on subdomain (#2041) 2022-08-05 13:29:49 -04:00
Lokesh Gopu
813af29886 Include current runner status while getting messages (#2026)
* get messages with runner status

* fixed l0 tests

* PR feedback
2022-07-28 16:42:02 -04:00
31 changed files with 312 additions and 58 deletions

View File

@@ -16,7 +16,7 @@ We should give them that option, and publish examples how how they can create th
- For example, the current runner overrides `HOME`, we can do that in the hook, but we shouldn't pass that hook as an ENV with the other env's the user has set, as that is not user input, it is how the runner invokes containers
## Interface
- You will set the variable `ACTIONS_RUNNER_CONTAINER_HOOK=/Users/foo/runner/hooks.js` which is the entrypoint to your hook handler.
- You will set the variable `ACTIONS_RUNNER_CONTAINER_HOOKS=/Users/foo/runner/hooks.js` which is the entrypoint to your hook handler.
- There is no partial opt in, you must handle every hook
- We will pass a command and some args via `stdin`
- An exit code of 0 is a success, every other exit code is a failure

View File

@@ -1,11 +1,6 @@
## Features
- Added support for a JIT runner config (#1925)
- Added `ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION` env option to force actions to run on a specific node version (#1913)
## Bugs
- Fixed a bug where container hooks passed in path as a string rather then an array of strings (#1948)
- Fixed an issue where job and service container envs were corrupted (#2091)
## Misc
- Minor cleanup of error messages when running container hooks (#1949)
## Windows x64
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
@@ -32,7 +27,7 @@ curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
```
## [Pre-release] OSX arm64 (Apple silicon)
## OSX arm64 (Apple silicon)
``` bash
# Create a folder

View File

@@ -120,6 +120,9 @@ if ERRORLEVEL 1 (
echo [%date% %time%] Update succeed >> "%logfile%" 2>&1
type nul > update.finished
echo [%date% %time%] update.finished file creation succeed >> "%logfile%" 2>&1
rem rename the update log file with %logfile%.succeed/.failed/succeedneedrestart
rem runner service host can base on the log file name determin the result of the runner update
echo [%date% %time%] Rename "%logfile%" to be "%logfile%.succeed" >> "%logfile%" 2>&1

View File

@@ -180,6 +180,9 @@ fi
date "+[%F %T-%4N] Update succeed" >> "$logfile"
touch update.finished
date "+[%F %T-%4N] update.finished file creation succeed" >> "$logfile"
# rename the update log file with %logfile%.succeed/.failed/succeedneedrestart
# runner service host can base on the log file name determin the result of the runner update
date "+[%F %T-%4N] Rename $logfile to be $logfile.succeed" >> "$logfile" 2>&1

View File

@@ -1,5 +1,5 @@
@echo off
SET UPDATEFILE=update.finished
"%~dp0\bin\Runner.Listener.exe" run %*
rem using `if %ERRORLEVEL% EQU N` insterad of `if ERRORLEVEL N`
@@ -22,16 +22,30 @@ if %ERRORLEVEL% EQU 2 (
)
if %ERRORLEVEL% EQU 3 (
rem Sleep 5 seconds to wait for the runner update process finish
echo "Runner listener exit because of updating, re-launch runner in 5 seconds"
ping 127.0.0.1 -n 6 -w 1000 >NUL
rem Wait for 30 seconds or for flag file to exists for the ephemeral runner update process finish
echo "Runner listener exit because of updating, re-launch runner after successful update"
FOR /L %%G IN (1,1,30) DO (
IF EXIST %UPDATEFILE% (
echo "Update finished successfully."
del %FILE%
exit /b 1
)
ping 127.0.0.1 -n 2 -w 1000 >NUL
)
exit /b 1
)
if %ERRORLEVEL% EQU 4 (
rem Sleep 5 seconds to wait for the ephemeral runner update process finish
echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds"
ping 127.0.0.1 -n 6 -w 1000 >NUL
rem Wait for 30 seconds or for flag file to exists for the runner update process finish
echo "Runner listener exit because of updating, re-launch runner after successful update"
FOR /L %%G IN (1,1,30) DO (
IF EXIST %UPDATEFILE% (
echo "Update finished successfully."
del %FILE%
exit /b 1
)
ping 127.0.0.1 -n 2 -w 1000 >NUL
)
exit /b 1
)

View File

@@ -17,6 +17,8 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
updateFile="update.finished"
"$DIR"/bin/Runner.Listener run $*
returnCode=$?
@@ -31,14 +33,28 @@ elif [[ $returnCode == 2 ]]; then
"$DIR"/safe_sleep.sh 5
exit 2
elif [[ $returnCode == 3 ]]; then
# Sleep 5 seconds to wait for the runner update process finish
echo "Runner listener exit because of updating, re-launch runner in 5 seconds"
"$DIR"/safe_sleep.sh 5
# Wait for 30 seconds or for flag file to exists for the runner update process finish
echo "Runner listener exit because of updating, re-launch runner after successful update"
for i in {0..30}; do
if test -f "$updateFile"; then
echo "Update finished successfully."
rm "$updateFile"
break
fi
"$DIR"/safe_sleep.sh 1
done
exit 2
elif [[ $returnCode == 4 ]]; then
# Sleep 5 seconds to wait for the ephemeral runner update process finish
echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds"
"$DIR"/safe_sleep.sh 5
# Wait for 30 seconds or for flag file to exists for the ephemeral runner update process finish
echo "Runner listener exit because of updating, re-launch runner after successful update"
for i in {0..30}; do
if test -f "$updateFile"; then
echo "Update finished successfully."
rm "$updateFile"
break
fi
"$DIR"/safe_sleep.sh 1
done
exit 2
else
echo "Exiting with unknown error code: ${returnCode}"

View File

@@ -9,10 +9,10 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
# run the helper process which keep the listener alive
while :;
do
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
"$DIR"/run-helper.sh $*
returnCode=$?
if [[ $returnCode -eq 2 ]]; then

View File

@@ -0,0 +1,14 @@
using System;
using GitHub.DistributedTask.WebApi;
namespace GitHub.Runner.Common
{
public class JobStatusEventArgs : EventArgs
{
public JobStatusEventArgs(TaskAgentStatus status)
{
this.Status = status;
}
public TaskAgentStatus Status { get; private set; }
}
}

View File

@@ -39,7 +39,7 @@ namespace GitHub.Runner.Common
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken);
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, CancellationToken cancellationToken);
// job request
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
@@ -298,10 +298,10 @@ namespace GitHub.Runner.Common
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
}
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken)
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, CancellationToken cancellationToken)
{
CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, cancellationToken: cancellationToken);
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, cancellationToken: cancellationToken);
}
//-----------------------------------------------------------------

View File

@@ -3,6 +3,7 @@ using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
using GitHub.Services.OAuth;
using System;
using System.Collections.Generic;
@@ -128,7 +129,7 @@ namespace GitHub.Runner.Listener.Configuration
// Example githubServerUrl is https://my-ghes
var actionsServerUrl = new Uri(runnerSettings.ServerUrl);
var githubServerUrl = new Uri(runnerSettings.GitHubUrl);
if (!string.Equals(actionsServerUrl.Authority, githubServerUrl.Authority, StringComparison.OrdinalIgnoreCase))
if (!UriUtility.IsSubdomainOf(actionsServerUrl.Authority, githubServerUrl.Authority))
{
throw new InvalidOperationException($"GitHub Actions is not properly configured in GHES. GHES url: {runnerSettings.GitHubUrl}, Actions url: {runnerSettings.ServerUrl}.");
}

View File

@@ -27,6 +27,7 @@ namespace GitHub.Runner.Listener
bool Cancel(JobCancelMessage message);
Task WaitAsync(CancellationToken token);
Task ShutdownAsync();
event EventHandler<JobStatusEventArgs> JobStatus;
}
// This implementation of IJobDispatcher is not thread safe.
@@ -55,6 +56,8 @@ namespace GitHub.Runner.Listener
private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>();
public event EventHandler<JobStatusEventArgs> JobStatus;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
@@ -335,6 +338,11 @@ namespace GitHub.Runner.Listener
Busy = true;
try
{
if (JobStatus != null)
{
JobStatus(this, new JobStatusEventArgs(TaskAgentStatus.Busy));
}
if (previousJobDispatch != null)
{
Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker.");
@@ -650,6 +658,11 @@ namespace GitHub.Runner.Listener
finally
{
Busy = false;
if (JobStatus != null)
{
JobStatus(this, new JobStatusEventArgs(TaskAgentStatus.Online));
}
}
}

View File

@@ -23,6 +23,7 @@ namespace GitHub.Runner.Listener
Task DeleteSessionAsync();
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
Task DeleteMessageAsync(TaskAgentMessage message);
void OnJobStatus(object sender, JobStatusEventArgs e);
}
public sealed class MessageListener : RunnerService, IMessageListener
@@ -38,6 +39,8 @@ namespace GitHub.Runner.Listener
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
private CancellationTokenSource _getMessagesTokenSource;
public override void Initialize(IHostContext hostContext)
{
@@ -170,6 +173,23 @@ namespace GitHub.Runner.Listener
}
}
public void OnJobStatus(object sender, JobStatusEventArgs e)
{
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
{
Trace.Info("Received job status event. JobState: {0}", e.Status);
runnerStatus = e.Status;
try
{
_getMessagesTokenSource?.Cancel();
}
catch (ObjectDisposedException)
{
Trace.Info("_getMessagesTokenSource is already disposed.");
}
}
}
public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
{
Trace.Entering();
@@ -184,12 +204,14 @@ namespace GitHub.Runner.Listener
{
token.ThrowIfCancellationRequested();
TaskAgentMessage message = null;
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
try
{
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
_session.SessionId,
_lastMessageId,
token);
runnerStatus,
_getMessagesTokenSource.Token);
// Decrypt the message body if the session is using encryption
message = DecryptMessage(message);
@@ -206,6 +228,11 @@ namespace GitHub.Runner.Listener
continuousError = 0;
}
}
catch (OperationCanceledException) when (_getMessagesTokenSource.Token.IsCancellationRequested && !token.IsCancellationRequested)
{
Trace.Info("Get messages has been cancelled using local token source. Continue to get messages with new status.");
continue;
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
Trace.Info("Get next message has been cancelled.");
@@ -261,6 +288,10 @@ namespace GitHub.Runner.Listener
await HostContext.Delay(_getNextMessageRetryInterval, token);
}
}
finally
{
_getMessagesTokenSource.Dispose();
}
if (message == null)
{

View File

@@ -360,6 +360,8 @@ namespace GitHub.Runner.Listener
bool runOnceJobReceived = false;
jobDispatcher = HostContext.CreateService<IJobDispatcher>();
jobDispatcher.JobStatus += _listener.OnJobStatus;
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
{
TaskAgentMessage message = null;
@@ -561,6 +563,7 @@ namespace GitHub.Runner.Listener
{
if (jobDispatcher != null)
{
jobDispatcher.JobStatus -= _listener.OnJobStatus;
await jobDispatcher.ShutdownAsync();
}

View File

@@ -131,6 +131,8 @@ namespace GitHub.Runner.Listener
// For L0, we will skip execute update script.
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT")))
{
string flagFile = "update.finished";
IOUtil.DeleteFile(flagFile);
// kick off update script
Process invokeScript = new Process();
#if OS_WINDOWS
@@ -294,12 +296,12 @@ namespace GitHub.Runner.Listener
archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.tar.gz");
}
if (File.Exists(archiveFile))
if (File.Exists(archiveFile))
{
_updateTrace.Enqueue($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded");
_terminal.WriteLine($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded");
}
else
else
{
archiveFile = null;
_terminal.WriteLine($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead");

View File

@@ -57,7 +57,7 @@ namespace GitHub.Runner.Sdk
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
}
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_ALLOW_REDIRECT")))
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
{
settings.AllowAutoRedirect = true;
}

View File

@@ -585,6 +585,8 @@ namespace GitHub.Runner.Worker
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, ContainerInfo container)
{
ValidateLinesAndColumns(command, context);
command.Properties.TryGetValue(IssueCommandProperties.File, out string file);
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);

View File

@@ -503,7 +503,7 @@ namespace GitHub.Runner.Worker
};
}
throw new NotSupportedException(nameof(ConvertRuns));
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.");
}
private void ConvertInputs(

View File

@@ -107,6 +107,7 @@ namespace GitHub.Runner.Worker.Container
public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container)
{
IList<string> dockerOptions = new List<string>();
IDictionary<string, string> environment = new Dictionary<string, string>();
// OPTIONS
dockerOptions.Add($"--name {container.ContainerDisplayName}");
dockerOptions.Add($"--label {DockerInstanceLabel}");
@@ -131,11 +132,12 @@ namespace GitHub.Runner.Worker.Container
{
if (String.IsNullOrEmpty(env.Value))
{
dockerOptions.Add($"-e \"{env.Key}\"");
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
}
else
{
dockerOptions.Add($"-e \"{env.Key}={env.Value.Replace("\"", "\\\"")}\"");
environment.Add(env.Key, env.Value);
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
}
}
@@ -183,7 +185,7 @@ namespace GitHub.Runner.Worker.Container
dockerOptions.Add($"{container.ContainerEntryPointArgs}");
var optionsString = string.Join(" ", dockerOptions);
List<string> outputStrings = await ExecuteDockerCommandAsync(context, "create", optionsString);
List<string> outputStrings = await ExecuteDockerCommandAsync(context, "create", optionsString, environment);
return outputStrings.FirstOrDefault();
}
@@ -202,7 +204,7 @@ namespace GitHub.Runner.Worker.Container
{
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
// the value directly in the command
dockerOptions.Add($"-e {env.Key}");
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
}
// Watermark for GitHub Action environment
@@ -443,6 +445,11 @@ namespace GitHub.Runner.Worker.Container
}
private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options)
{
return await ExecuteDockerCommandAsync(context, command, options, null);
}
private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options, IDictionary<string, string> environment)
{
string arg = $"{command} {options}".Trim();
context.Command($"{DockerPath} {arg}");
@@ -470,7 +477,7 @@ namespace GitHub.Runner.Worker.Container
workingDirectory: context.GetGitHubContext("workspace"),
fileName: DockerPath,
arguments: arg,
environment: null,
environment: environment,
requireExitCodeZero: true,
outputEncoding: null,
cancellationToken: CancellationToken.None);

View File

@@ -17,7 +17,7 @@ namespace GitHub.Runner.Worker.Container
string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$";
List<PortMapping> portMappings = new List<PortMapping>();
foreach(var line in portMappingLines)
foreach (var line in portMappingLines)
{
Match m = Regex.Match(line, pattern, RegexOptions.None, TimeSpan.FromSeconds(1));
if (m.Success)
@@ -61,5 +61,19 @@ namespace GitHub.Runner.Worker.Container
}
return "";
}
public static string CreateEscapedOption(string flag, string key)
{
if (String.IsNullOrEmpty(key))
{
return "";
}
return $"{flag} \"{EscapeString(key)}\"";
}
private static string EscapeString(string value)
{
return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
}
}

View File

@@ -63,6 +63,8 @@ namespace GitHub.Runner.Worker
// Keep track of embedded steps states
Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; }
IList<Issue> EmbeddedIssues { get; }
bool EchoOnActionCommand { get; set; }
bool IsEmbedded { get; }
@@ -91,6 +93,7 @@ namespace GitHub.Runner.Worker
void SetOutput(string name, string value, out string reference);
void SetTimeout(TimeSpan? timeout);
void AddIssue(Issue issue, string message = null);
void AddIssueToTimelineRecord(Issue issue);
void Progress(int percentage, string currentOperation = null);
void UpdateDetailTimelineRecord(TimelineRecord record);
@@ -180,6 +183,8 @@ namespace GitHub.Runner.Worker
public Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; private set; }
public IList<Issue> EmbeddedIssues { get; } = new List<Issue>();
public bool EchoOnActionCommand { get; set; }
// An embedded execution context shares the same record ID, record name, and logger
@@ -369,6 +374,7 @@ namespace GitHub.Runner.Worker
child.StepTelemetry.StepId = recordId;
child.StepTelemetry.Stage = stage.ToString();
child.StepTelemetry.IsEmbedded = isEmbedded;
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName(); ;
return child;
}
@@ -574,7 +580,31 @@ namespace GitHub.Runner.Worker
long logLineNumber = Write(WellKnownTags.Error, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
}
else if (issue.Type == IssueType.Warning)
{
if (!string.IsNullOrEmpty(logMessage))
{
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
}
else if (issue.Type == IssueType.Notice)
{
if (!string.IsNullOrEmpty(logMessage))
{
long logLineNumber = Write(WellKnownTags.Notice, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
}
AddIssueToTimelineRecord(issue);
}
public void AddIssueToTimelineRecord(Issue issue)
{
ArgUtil.NotNull(issue, nameof(issue));
if (issue.Type == IssueType.Error)
{
if (_record.ErrorCount < _maxIssueCount)
{
_record.Issues.Add(issue);
@@ -584,12 +614,6 @@ namespace GitHub.Runner.Worker
}
else if (issue.Type == IssueType.Warning)
{
if (!string.IsNullOrEmpty(logMessage))
{
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
if (_record.WarningCount < _maxIssueCount)
{
_record.Issues.Add(issue);
@@ -599,12 +623,6 @@ namespace GitHub.Runner.Worker
}
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);
@@ -612,8 +630,17 @@ namespace GitHub.Runner.Worker
_record.NoticeCount++;
}
// Composite actions should never upload a timeline record to the server
// We add these to a list and let composite action handler bubble it up recursively
if (this.IsEmbedded)
{
EmbeddedIssues.Add(issue);
}
else
{
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
}
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
}
public void UpdateDetailTimelineRecord(TimelineRecord record)
@@ -959,6 +986,8 @@ namespace GitHub.Runner.Worker
_record.StartTime != null)
{
StepTelemetry.ExecutionTimeInSeconds = (int)Math.Ceiling((_record.FinishTime - _record.StartTime)?.TotalSeconds ?? 0);
StepTelemetry.StartTime = _record.StartTime;
StepTelemetry.FinishTime = _record.FinishTime;
}
if (!IsEmbedded &&

View File

@@ -413,6 +413,12 @@ namespace GitHub.Runner.Worker.Handlers
// Update context
step.ExecutionContext.UpdateGlobalStepsContext();
// Update annotations
foreach (var issue in step.ExecutionContext.EmbeddedIssues)
{
ExecutionContext.AddIssueToTimelineRecord(issue);
}
}
}

View File

@@ -193,7 +193,7 @@ namespace GitHub.Runner.Worker.Handlers
TranslateToContainerPath(environment);
await containerHookManager.RunScriptStepAsync(context,
Container,
workingDirectory,
workingDirectory,
fileName,
arguments,
environment,
@@ -216,7 +216,7 @@ namespace GitHub.Runner.Worker.Handlers
{
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
// the value directly in the command
dockerCommandArgs.Add($"-e {env.Key}");
dockerCommandArgs.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
}
if (!string.IsNullOrEmpty(PrependPath))
{

View File

@@ -316,6 +316,29 @@ namespace GitHub.Runner.Worker
}
}
if (message.Variables.TryGetValue("system.workflowFileFullPath", out VariableValue workflowFileFullPath))
{
context.Output($"Uses: {workflowFileFullPath.Value}");
if (message.ContextData.TryGetValue("inputs", out var pipelineContextData))
{
var inputs = pipelineContextData.AssertDictionary("inputs");
if (inputs.Any())
{
context.Output($"##[group] Inputs");
foreach (var input in inputs)
{
context.Output($" {input.Key}: {input.Value}");
}
context.Output("##[endgroup]");
}
}
if (!string.IsNullOrWhiteSpace(message.JobDisplayName))
{
context.Output($"Complete job name: {message.JobDisplayName}");
}
}
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
foreach (var preStep in prepareResult.PreStepTracker)
{

View File

@@ -457,6 +457,7 @@ namespace GitHub.DistributedTask.WebApi
int poolId,
Guid sessionId,
long? lastMessageId = null,
TaskAgentStatus? status = null,
object userState = null,
CancellationToken cancellationToken = default)
{
@@ -470,6 +471,10 @@ namespace GitHub.DistributedTask.WebApi
{
queryParams.Add("lastMessageId", lastMessageId.Value.ToString(CultureInfo.InvariantCulture));
}
if (status != null)
{
queryParams.Add("status", status.Value.ToString());
}
return SendAsync<TaskAgentMessage>(
httpMethod,

View File

@@ -30,6 +30,9 @@ namespace GitHub.DistributedTask.WebApi
[DataMember(EmitDefaultValue = false)]
public Guid StepId { get; set; }
[DataMember(EmitDefaultValue = false)]
public string StepContextName { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool? HasRunsStep { get; set; }
@@ -57,6 +60,12 @@ namespace GitHub.DistributedTask.WebApi
[DataMember(EmitDefaultValue = false)]
public int? ExecutionTimeInSeconds { get; set; }
[DataMember(EmitDefaultValue = false)]
public DateTime? StartTime { get; set; }
[DataMember(EmitDefaultValue = false)]
public DateTime? FinishTime { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ContainerHookData { get; set; }
}

View File

@@ -10,5 +10,8 @@ namespace GitHub.DistributedTask.WebApi
[EnumMember]
Online = 2,
[EnumMember]
Busy = 3,
}
}

View File

@@ -144,5 +144,32 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
var actual = DockerUtil.ParseRegistryHostnameFromImageName(input);
Assert.Equal(expected, actual);
}
[Theory]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
[InlineData("", "")]
[InlineData("HOME alpine:3.8 sh -c id #", "HOME alpine:3.8 sh -c id #")]
[InlineData("HOME \"alpine:3.8 sh -c id #", "HOME \\\"alpine:3.8 sh -c id #")]
[InlineData("HOME \\\"alpine:3.8 sh -c id #", "HOME \\\\\\\"alpine:3.8 sh -c id #")]
[InlineData("HOME \\\\\"alpine:3.8 sh -c id #", "HOME \\\\\\\\\\\"alpine:3.8 sh -c id #")]
[InlineData("HOME \"\"alpine:3.8 sh -c id #", "HOME \\\"\\\"alpine:3.8 sh -c id #")]
[InlineData("HOME \\\"\"alpine:3.8 sh -c id #", "HOME \\\\\\\"\\\"alpine:3.8 sh -c id #")]
[InlineData("HOME \"\\\"alpine:3.8 sh -c id #", "HOME \\\"\\\\\\\"alpine:3.8 sh -c id #")]
public void CreateEscapedOption_keyOnly(string input, string escaped)
{
var flag = "--example";
var actual = DockerUtil.CreateEscapedOption(flag, input);
string expected;
if (String.IsNullOrEmpty(input))
{
expected = "";
}
else
{
expected = $"{flag} \"{escaped}\"";
}
Assert.Equal(expected, actual);
}
}
}

View File

@@ -192,8 +192,8 @@ namespace GitHub.Runner.Common.Tests.Listener
_runnerServer
.Setup(x => x.GetAgentMessageAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), tokenSource.Token))
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken) =>
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<CancellationToken>()))
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, CancellationToken cancellationToken) =>
{
await Task.Yield();
return messages.Dequeue();
@@ -208,7 +208,7 @@ namespace GitHub.Runner.Common.Tests.Listener
//Assert
_runnerServer
.Verify(x => x.GetAgentMessageAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), tokenSource.Token), Times.Exactly(arMessages.Length));
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<CancellationToken>()), Times.Exactly(arMessages.Length));
}
}
@@ -293,7 +293,7 @@ namespace GitHub.Runner.Common.Tests.Listener
_runnerServer
.Setup(x => x.GetAgentMessageAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), tokenSource.Token))
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<CancellationToken>()))
.Throws(new TaskAgentAccessTokenExpiredException("test"));
try
{
@@ -311,7 +311,7 @@ namespace GitHub.Runner.Common.Tests.Listener
//Assert
_runnerServer
.Verify(x => x.GetAgentMessageAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), tokenSource.Token), Times.Once);
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<CancellationToken>()), Times.Once);
_runnerServer
.Verify(x => x.DeleteAgentSessionAsync(

View File

@@ -698,6 +698,31 @@ namespace GitHub.Runner.Common.Tests.Worker
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_CompositeActionNoUsing()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
var action_path = Path.Combine(TestUtil.GetTestDataPath(), "composite_action_without_using_token.yml");
//Assert
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
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);
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]

View File

@@ -0,0 +1,9 @@
name: "composite action"
description: "test composite action without value for the 'using' token in 'runs'"
runs:
steps:
- id: mystep
shell: bash
run: |
echo "hello world"

View File

@@ -1 +1 @@
2.294.0
2.296.1