Compare commits

..

14 Commits

Author SHA1 Message Date
joanna
6f8d55dcb8 wip 2022-09-05 23:50:49 +02: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
30 changed files with 287 additions and 47 deletions

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 ## 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 ## Misc
- Minor cleanup of error messages when running container hooks (#1949)
## Windows x64 ## 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. 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 tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
``` ```
## [Pre-release] OSX arm64 (Apple silicon) ## OSX arm64 (Apple silicon)
``` bash ``` bash
# Create a folder # Create a folder

View File

@@ -120,6 +120,9 @@ if ERRORLEVEL 1 (
echo [%date% %time%] Update succeed >> "%logfile%" 2>&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 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 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 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" 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 # 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 # 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 date "+[%F %T-%4N] Rename $logfile to be $logfile.succeed" >> "$logfile" 2>&1

View File

@@ -1,5 +1,5 @@
@echo off @echo off
SET UPDATEFILE=update.finished
"%~dp0\bin\Runner.Listener.exe" run %* "%~dp0\bin\Runner.Listener.exe" run %*
rem using `if %ERRORLEVEL% EQU N` insterad of `if ERRORLEVEL N` rem using `if %ERRORLEVEL% EQU N` insterad of `if ERRORLEVEL N`
@@ -22,16 +22,30 @@ if %ERRORLEVEL% EQU 2 (
) )
if %ERRORLEVEL% EQU 3 ( if %ERRORLEVEL% EQU 3 (
rem Sleep 5 seconds to wait for the runner update process finish 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 in 5 seconds" echo "Runner listener exit because of updating, re-launch runner after successful update"
ping 127.0.0.1 -n 6 -w 1000 >NUL 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 exit /b 1
) )
if %ERRORLEVEL% EQU 4 ( if %ERRORLEVEL% EQU 4 (
rem Sleep 5 seconds to wait for the ephemeral runner update process finish 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 ephemeral runner in 5 seconds" echo "Runner listener exit because of updating, re-launch runner after successful update"
ping 127.0.0.1 -n 6 -w 1000 >NUL 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 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 [[ $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 done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
updateFile="update.finished"
"$DIR"/bin/Runner.Listener run $* "$DIR"/bin/Runner.Listener run $*
returnCode=$? returnCode=$?
@@ -31,14 +33,28 @@ elif [[ $returnCode == 2 ]]; then
"$DIR"/safe_sleep.sh 5 "$DIR"/safe_sleep.sh 5
exit 2 exit 2
elif [[ $returnCode == 3 ]]; then elif [[ $returnCode == 3 ]]; then
# Sleep 5 seconds to wait for the runner update process finish # 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 in 5 seconds" echo "Runner listener exit because of updating, re-launch runner after successful update"
"$DIR"/safe_sleep.sh 5 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 exit 2
elif [[ $returnCode == 4 ]]; then elif [[ $returnCode == 4 ]]; then
# Sleep 5 seconds to wait for the ephemeral runner update process finish # 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 ephemeral runner in 5 seconds" echo "Runner listener exit because of updating, re-launch runner after successful update"
"$DIR"/safe_sleep.sh 5 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 exit 2
else else
echo "Exiting with unknown error code: ${returnCode}" 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 [[ $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 done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 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 # run the helper process which keep the listener alive
while :; while :;
do do
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
"$DIR"/run-helper.sh $* "$DIR"/run-helper.sh $*
returnCode=$? returnCode=$?
if [[ $returnCode -eq 2 ]]; then 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<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken); Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
Task DeleteAgentSessionAsync(Int32 poolId, 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 // job request
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken); Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
@@ -298,10 +298,10 @@ namespace GitHub.Runner.Common
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken); 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); 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.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
using GitHub.Services.OAuth; using GitHub.Services.OAuth;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -128,7 +129,7 @@ namespace GitHub.Runner.Listener.Configuration
// Example githubServerUrl is https://my-ghes // Example githubServerUrl is https://my-ghes
var actionsServerUrl = new Uri(runnerSettings.ServerUrl); var actionsServerUrl = new Uri(runnerSettings.ServerUrl);
var githubServerUrl = new Uri(runnerSettings.GitHubUrl); 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}."); 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); bool Cancel(JobCancelMessage message);
Task WaitAsync(CancellationToken token); Task WaitAsync(CancellationToken token);
Task ShutdownAsync(); Task ShutdownAsync();
event EventHandler<JobStatusEventArgs> JobStatus;
} }
// This implementation of IJobDispatcher is not thread safe. // This implementation of IJobDispatcher is not thread safe.
@@ -55,6 +56,8 @@ namespace GitHub.Runner.Listener
private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>(); private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>();
public event EventHandler<JobStatusEventArgs> JobStatus;
public override void Initialize(IHostContext hostContext) public override void Initialize(IHostContext hostContext)
{ {
base.Initialize(hostContext); base.Initialize(hostContext);
@@ -335,6 +338,11 @@ namespace GitHub.Runner.Listener
Busy = true; Busy = true;
try try
{ {
if (JobStatus != null)
{
JobStatus(this, new JobStatusEventArgs(TaskAgentStatus.Busy));
}
if (previousJobDispatch != null) if (previousJobDispatch != null)
{ {
Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker."); Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker.");
@@ -650,6 +658,11 @@ namespace GitHub.Runner.Listener
finally finally
{ {
Busy = false; Busy = false;
if (JobStatus != null)
{
JobStatus(this, new JobStatusEventArgs(TaskAgentStatus.Online));
}
} }
} }

View File

@@ -23,6 +23,7 @@ namespace GitHub.Runner.Listener
Task DeleteSessionAsync(); Task DeleteSessionAsync();
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token); Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
Task DeleteMessageAsync(TaskAgentMessage message); Task DeleteMessageAsync(TaskAgentMessage message);
void OnJobStatus(object sender, JobStatusEventArgs e);
} }
public sealed class MessageListener : RunnerService, IMessageListener public sealed class MessageListener : RunnerService, IMessageListener
@@ -38,6 +39,8 @@ namespace GitHub.Runner.Listener
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4); private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30); private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>(); private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
private CancellationTokenSource _getMessagesTokenSource;
public override void Initialize(IHostContext hostContext) 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) public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
{ {
Trace.Entering(); Trace.Entering();
@@ -184,12 +204,14 @@ namespace GitHub.Runner.Listener
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
TaskAgentMessage message = null; TaskAgentMessage message = null;
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
try try
{ {
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId, message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
_session.SessionId, _session.SessionId,
_lastMessageId, _lastMessageId,
token); runnerStatus,
_getMessagesTokenSource.Token);
// Decrypt the message body if the session is using encryption // Decrypt the message body if the session is using encryption
message = DecryptMessage(message); message = DecryptMessage(message);
@@ -206,6 +228,11 @@ namespace GitHub.Runner.Listener
continuousError = 0; 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) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
Trace.Info("Get next message has been cancelled."); Trace.Info("Get next message has been cancelled.");
@@ -261,6 +288,10 @@ namespace GitHub.Runner.Listener
await HostContext.Delay(_getNextMessageRetryInterval, token); await HostContext.Delay(_getNextMessageRetryInterval, token);
} }
} }
finally
{
_getMessagesTokenSource.Dispose();
}
if (message == null) if (message == null)
{ {

View File

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

View File

@@ -131,6 +131,8 @@ namespace GitHub.Runner.Listener
// For L0, we will skip execute update script. // For L0, we will skip execute update script.
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT"))) if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT")))
{ {
string flagFile = "update.finished";
IOUtil.DeleteFile(flagFile);
// kick off update script // kick off update script
Process invokeScript = new Process(); Process invokeScript = new Process();
#if OS_WINDOWS #if OS_WINDOWS

View File

@@ -57,7 +57,7 @@ namespace GitHub.Runner.Sdk
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200)); 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; settings.AllowAutoRedirect = true;
} }

View File

@@ -585,6 +585,8 @@ namespace GitHub.Runner.Worker
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, ContainerInfo container) 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.File, out string file);
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line); command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column); 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( private void ConvertInputs(

View File

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

View File

@@ -17,7 +17,7 @@ namespace GitHub.Runner.Worker.Container
string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$"; string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$";
List<PortMapping> portMappings = new List<PortMapping>(); 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)); Match m = Regex.Match(line, pattern, RegexOptions.None, TimeSpan.FromSeconds(1));
if (m.Success) if (m.Success)
@@ -61,5 +61,19 @@ namespace GitHub.Runner.Worker.Container
} }
return ""; 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

@@ -134,7 +134,6 @@ namespace GitHub.Runner.Worker
ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(executionContext, nameof(executionContext));
ArgUtil.NotNull(container, nameof(container)); ArgUtil.NotNull(container, nameof(container));
ArgUtil.NotNullOrEmpty(container.ContainerImage, nameof(container.ContainerImage)); ArgUtil.NotNullOrEmpty(container.ContainerImage, nameof(container.ContainerImage));
Trace.Info($"Container name: {container.ContainerName}"); Trace.Info($"Container name: {container.ContainerName}");
Trace.Info($"Container image: {container.ContainerImage}"); Trace.Info($"Container image: {container.ContainerImage}");
Trace.Info($"Container options: {container.ContainerCreateOptions}"); Trace.Info($"Container options: {container.ContainerCreateOptions}");
@@ -198,6 +197,8 @@ namespace GitHub.Runner.Worker
container.ContainerId = await _dockerManager.DockerCreate(executionContext, container); container.ContainerId = await _dockerManager.DockerCreate(executionContext, container);
ArgUtil.NotNullOrEmpty(container.ContainerId, nameof(container.ContainerId)); ArgUtil.NotNullOrEmpty(container.ContainerId, nameof(container.ContainerId));
// Start container // Start container
int startExitCode = await _dockerManager.DockerStart(executionContext, container.ContainerId); int startExitCode = await _dockerManager.DockerStart(executionContext, container.ContainerId);
if (startExitCode != 0) if (startExitCode != 0)
@@ -207,20 +208,37 @@ namespace GitHub.Runner.Worker
try try
{ {
// Make sure container is up and running // Make sure container is up and running
var psOutputs = await _dockerManager.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --filter status=running --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\""); var psOutputs = await _dockerManager.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --filter status=running --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\"");
if (psOutputs.FirstOrDefault(x => !string.IsNullOrEmpty(x))?.StartsWith(container.ContainerId) != true) if (psOutputs.FirstOrDefault(x => !string.IsNullOrEmpty(x))?.StartsWith(container.ContainerId) != true)
{ {
// container is not up and running, pull docker log for this container. // container is not up and running, pull docker log for this container.
await _dockerManager.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\""); await _dockerManager.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\"");
//executionContext.Output("##[group]Getting docker logs..");
int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId); int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
if (logsExitCode != 0) if (logsExitCode != 0)
{ {
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}"); executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
} }
//executionContext.Output("##[endgroup]");
executionContext.Warning($"Docker container {container.ContainerId} is not in running state."); executionContext.Warning($"Docker container {container.ContainerId} is not in running state.");
} }
else {
executionContext.Output($"##[group]Container {container.ContainerId} is not running!");
string opt = "--format=\'{{.State.ExitCode}}\'";
await _dockerManager.DockerInspect(executionContext, container.ContainerId, opt);
await _dockerManager.DockerLogs(executionContext, container.ContainerId);
//if exit code is not 0 we can maybe print a message?
//executionContext.Output("Container exit code: ");
executionContext.Output("##[endgroup]");
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -369,6 +369,7 @@ namespace GitHub.Runner.Worker
child.StepTelemetry.StepId = recordId; child.StepTelemetry.StepId = recordId;
child.StepTelemetry.Stage = stage.ToString(); child.StepTelemetry.Stage = stage.ToString();
child.StepTelemetry.IsEmbedded = isEmbedded; child.StepTelemetry.IsEmbedded = isEmbedded;
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName(); ;
return child; return child;
} }
@@ -959,6 +960,8 @@ namespace GitHub.Runner.Worker
_record.StartTime != null) _record.StartTime != null)
{ {
StepTelemetry.ExecutionTimeInSeconds = (int)Math.Ceiling((_record.FinishTime - _record.StartTime)?.TotalSeconds ?? 0); StepTelemetry.ExecutionTimeInSeconds = (int)Math.Ceiling((_record.FinishTime - _record.StartTime)?.TotalSeconds ?? 0);
StepTelemetry.StartTime = _record.StartTime;
StepTelemetry.FinishTime = _record.FinishTime;
} }
if (!IsEmbedded && if (!IsEmbedded &&

View File

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

View File

@@ -279,7 +279,7 @@ namespace GitHub.Runner.Worker
preJobSteps.Add(new JobExtensionRunner(runAsync: containerProvider.StartContainersAsync, preJobSteps.Add(new JobExtensionRunner(runAsync: containerProvider.StartContainersAsync,
condition: $"{PipelineTemplateConstants.Success}()", condition: $"{PipelineTemplateConstants.Success}()",
displayName: "Initialize containers", displayName: "**Initialize containers**",
data: (object)containers)); data: (object)containers));
} }
@@ -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>>(); var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
foreach (var preStep in prepareResult.PreStepTracker) foreach (var preStep in prepareResult.PreStepTracker)
{ {

View File

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

View File

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

View File

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

View File

@@ -144,5 +144,32 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
var actual = DockerUtil.ParseRegistryHostnameFromImageName(input); var actual = DockerUtil.ParseRegistryHostnameFromImageName(input);
Assert.Equal(expected, actual); 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 _runnerServer
.Setup(x => x.GetAgentMessageAsync( .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>()))
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken) => .Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, CancellationToken cancellationToken) =>
{ {
await Task.Yield(); await Task.Yield();
return messages.Dequeue(); return messages.Dequeue();
@@ -208,7 +208,7 @@ namespace GitHub.Runner.Common.Tests.Listener
//Assert //Assert
_runnerServer _runnerServer
.Verify(x => x.GetAgentMessageAsync( .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 _runnerServer
.Setup(x => x.GetAgentMessageAsync( .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")); .Throws(new TaskAgentAccessTokenExpiredException("test"));
try try
{ {
@@ -311,7 +311,7 @@ namespace GitHub.Runner.Common.Tests.Listener
//Assert //Assert
_runnerServer _runnerServer
.Verify(x => x.GetAgentMessageAsync( .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 _runnerServer
.Verify(x => x.DeleteAgentSessionAsync( .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] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [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