diff --git a/releaseNote.md b/releaseNote.md index 47b819528..aeed81a09 100644 --- a/releaseNote.md +++ b/releaseNote.md @@ -1,13 +1,15 @@ ## Features -- Collect more telemetry -- Make `runner.name` available as a runner context variable -- Add attempt number (`run_attempt`) to GitHub context -- When using the `--ephemeral` flag, ensure that the runner cleans up local `.runner` and `.credentials` files after completion (#1337) +## Bugs + +- Fixed an issue where ephemeral runners deregistered themselves when jobs were not successful (#1384) +- Fixed an issue where you were not able to un-configure a runner that changed groups (#1359) +- Disable `stop-commands` command using well known keywords as a token (#1371) ## Misc -- Improved network troubleshooting docs +- Don't retry 422 error codes when downloading actions (#1352) +- Handle upgrade more smoothly on OSX (#1381) ## 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. diff --git a/src/Misc/layoutbin/update.sh.template b/src/Misc/layoutbin/update.sh.template index 38b31a546..668f91b23 100755 --- a/src/Misc/layoutbin/update.sh.template +++ b/src/Misc/layoutbin/update.sh.template @@ -18,6 +18,8 @@ downloadrunnerversion=_DOWNLOAD_RUNNER_VERSION_ logfile="_UPDATE_LOG_" restartinteractiverunner=_RESTART_INTERACTIVE_RUNNER_ +telemetryfile="$rootfolder/_diag/.telemetry" + # log user who run the script date "+[%F %T-%4N] --------whoami--------" >> "$logfile" 2>&1 whoami >> "$logfile" 2>&1 @@ -118,40 +120,101 @@ then exit 1 fi -# fix upgrade issue with macOS +# fix upgrade issue with macOS when running as a service +attemptedtargetedfix=0 currentplatform=$(uname | awk '{print tolower($0)}') -if [[ "$currentplatform" == 'darwin' ]]; then - # need a short-term fix for https://github.com/actions/runner/issues/743 - # we will recreate all the ./externals/node12/bin/node of the past 5 versions - # v2.280.3 v2.280.2 v2.280.1 v2.279.0 v2.278.0 - if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]] +if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then + # We needed a fix for https://github.com/actions/runner/issues/743 + # We will recreate the ./externals/node12/bin/node of the past runner version that launched the runnerlistener service + # Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file + + # We need the pid for the nodejs loop, get that here, its the parent of the runner C# pid + # assumption here is only one process is invoking rootfolder/runsvc.sh + procgroup=$(ps x -o pgid,command | grep "$rootfolder/runsvc.sh" | grep -v grep | awk '{print $1}') + if [[ $? -eq 0 && -n "$procgroup" ]] then - mkdir -p "$rootfolder/externals.2.280.3/node12/bin" - cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node" + # inspect the open file handles to find the node process + # we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks + path=$(lsof -a -g "$procgroup" -F n | grep node12/bin/node | grep externals | tail -1 | cut -c2-) + if [[ $? -eq 0 && -n "$path" ]] + then + # trim the last 5 characters of the path '/node' + trimmedpath=$(dirname "$path") + if [[ $? -eq 0 && -n "$trimmedpath" ]] + then + attemptedtargetedfix=1 + # Create the path if it does not exist + if [[ ! -e "$path" ]] + then + date "+[%F %T-%4N] Creating fallback node at path $path" >> "$logfile" 2>&1 + mkdir -p "$trimmedpath" + cp "$rootfolder/externals/node12/bin/node" "$path" + else + date "+[%F %T-%4N] Path for fallback node exists, skipping creating $path" >> "$logfile" 2>&1 + fi + else + date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to trim runner path. TrimmedPath: $trimmedpath, path: $path, pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1 + date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to trim runner path. TrimmedPath: $trimmedpath, path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1 + fi + else + date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1 + date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1 + fi + else + date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1 + date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1 fi - if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]] + if [ $attemptedtargetedfix -eq 0 ] then - mkdir -p "$rootfolder/externals.2.280.2/node12/bin" - cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node" - fi - if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]] - then - mkdir -p "$rootfolder/externals.2.280.1/node12/bin" - cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node" - fi + date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$logfile" 2>&1 + date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$telemetryfile" 2>&1 + if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.280.3/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node" + fi - if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]] - then - mkdir -p "$rootfolder/externals.2.279.0/node12/bin" - cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node" - fi + if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.280.2/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node" + fi - if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]] - then - mkdir -p "$rootfolder/externals.2.278.0/node12/bin" - cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node" + if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.280.1/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node" + fi + + # GHES 3.2 + if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.279.0/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node" + fi + + # GHES 3.1.2 or later + if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.278.0/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node" + fi + + # GHES 3.1.0 + if [[ ! -e "$rootfolder/externals.2.276.1/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.276.1/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.276.1/node12/bin/node" + fi + + # GHES 3.0 + if [[ ! -e "$rootfolder/externals.2.273.5/node12/bin/node" ]] + then + mkdir -p "$rootfolder/externals.2.273.5/node12/bin" + cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.273.5/node12/bin/node" + fi fi fi diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index 05b939270..c43aae122 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -26,6 +26,7 @@ namespace GitHub.Runner.Common Certificates, Options, SetupInfo, + Telemetry } public static class Constants @@ -128,7 +129,7 @@ namespace GitHub.Runner.Common public static readonly string Ephemeral = "ephemeral"; public static readonly string Help = "help"; public static readonly string Replace = "replace"; - public static readonly string Once = "once"; // TODO: Remove in 10/2021 + public static readonly string Once = "once"; // Keep this around since customers still relies on it public static readonly string RunAsService = "runasservice"; public static readonly string Unattended = "unattended"; public static readonly string Version = "version"; @@ -154,6 +155,7 @@ namespace GitHub.Runner.Common public static readonly string LowDiskSpace = "LOW_DISK_SPACE"; public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND"; public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/"; + public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`."; } public static class RunnerEvent @@ -213,6 +215,7 @@ namespace GitHub.Runner.Common // Keep alphabetical // public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS"; + public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS"; public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG"; public static readonly string StepDebug = "ACTIONS_STEP_DEBUG"; } diff --git a/src/Runner.Common/HostContext.cs b/src/Runner.Common/HostContext.cs index 2bdc6452d..34b303cd9 100644 --- a/src/Runner.Common/HostContext.cs +++ b/src/Runner.Common/HostContext.cs @@ -342,6 +342,12 @@ namespace GitHub.Runner.Common GetDirectory(WellKnownDirectory.Root), ".setup_info"); break; + + case WellKnownConfigFile.Telemetry: + path = Path.Combine( + GetDirectory(WellKnownDirectory.Diag), + ".telemetry"); + break; default: throw new NotSupportedException($"Unexpected well known config file: '{configFile}'"); diff --git a/src/Runner.Common/RunnerServer.cs b/src/Runner.Common/RunnerServer.cs index 64c9e0601..1b15dbe25 100644 --- a/src/Runner.Common/RunnerServer.cs +++ b/src/Runner.Common/RunnerServer.cs @@ -29,8 +29,10 @@ namespace GitHub.Runner.Common // Configuration Task AddAgentAsync(Int32 agentPoolId, TaskAgent agent); Task DeleteAgentAsync(int agentPoolId, int agentId); + Task DeleteAgentAsync(int agentId); Task> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation); Task> GetAgentsAsync(int agentPoolId, string agentName = null); + Task> GetAgentsAsync(string agentName); Task ReplaceAgentAsync(int agentPoolId, TaskAgent agent); // messagequeue @@ -252,6 +254,11 @@ namespace GitHub.Runner.Common return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false); } + public Task> GetAgentsAsync(string agentName) + { + return GetAgentsAsync(0, agentName); // search in all all agentPools + } + public Task ReplaceAgentAsync(int agentPoolId, TaskAgent agent) { CheckConnection(RunnerConnectionType.Generic); @@ -264,6 +271,11 @@ namespace GitHub.Runner.Common return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId); } + public Task DeleteAgentAsync(int agentId) + { + return DeleteAgentAsync(0, agentId); // agentPool is ignored server side + } + //----------------------------------------------------------------- // MessageQueue //----------------------------------------------------------------- diff --git a/src/Runner.Listener/CommandSettings.cs b/src/Runner.Listener/CommandSettings.cs index b5e1c1a69..da0d301fb 100644 --- a/src/Runner.Listener/CommandSettings.cs +++ b/src/Runner.Listener/CommandSettings.cs @@ -31,6 +31,7 @@ namespace GitHub.Runner.Listener Constants.Runner.CommandLine.Flags.Commit, Constants.Runner.CommandLine.Flags.Ephemeral, Constants.Runner.CommandLine.Flags.Help, + Constants.Runner.CommandLine.Flags.Once, Constants.Runner.CommandLine.Flags.Replace, Constants.Runner.CommandLine.Flags.RunAsService, Constants.Runner.CommandLine.Flags.Unattended, @@ -68,7 +69,7 @@ namespace GitHub.Runner.Listener public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version); public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral); - // TODO: Remove in 10/2021 + // Keep this around since customers still relies on it public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once); // Constructor. diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 73c93cece..db6e84fee 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -415,7 +415,7 @@ namespace GitHub.Runner.Listener.Configuration // Determine the service deployment type based on connection data. (Hosted/OnPremises) await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds); - var agents = await _runnerServer.GetAgentsAsync(settings.PoolId, settings.AgentName); + var agents = await _runnerServer.GetAgentsAsync(settings.AgentName); Trace.Verbose("Returns {0} agents", agents.Count); TaskAgent agent = agents.FirstOrDefault(); if (agent == null) @@ -424,7 +424,7 @@ namespace GitHub.Runner.Listener.Configuration } else { - await _runnerServer.DeleteAgentAsync(settings.PoolId, settings.AgentId); + await _runnerServer.DeleteAgentAsync(settings.AgentId); _term.WriteLine(); _term.WriteSuccessMessage("Runner removed successfully"); diff --git a/src/Runner.Listener/Runner.cs b/src/Runner.Listener/Runner.cs index 8f55cd348..128fcbdfa 100644 --- a/src/Runner.Listener/Runner.cs +++ b/src/Runner.Listener/Runner.cs @@ -233,8 +233,14 @@ namespace GitHub.Runner.Listener Trace.Info($"Set runner startup type - {startType}"); HostContext.StartupType = startType; + if (command.RunOnce) + { + _term.WriteLine("Warning: '--once' is going to be deprecated in the future, please consider using '--ephemeral' during runner registration.", ConsoleColor.Yellow); + _term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow); + } + // Run the runner interactively or as service - return await RunAsync(settings, command.RunOnce || settings.Ephemeral); // TODO: Remove RunOnce later. + return await RunAsync(settings, command.RunOnce || settings.Ephemeral); } else { @@ -310,6 +316,9 @@ namespace GitHub.Runner.Listener IJobDispatcher jobDispatcher = null; CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken); + + // Should we try to cleanup ephemeral runners + bool runOnceJobCompleted = false; try { var notification = HostContext.GetService(); @@ -371,6 +380,7 @@ namespace GitHub.Runner.Listener Task completeTask = await Task.WhenAny(getNextMessage, jobDispatcher.RunOnceJobCompleted.Task); if (completeTask == jobDispatcher.RunOnceJobCompleted.Task) { + runOnceJobCompleted = true; Trace.Info("Job has finished at backend, the runner will exit since it is running under onetime use mode."); Trace.Info("Stop message queue looping."); messageQueueLoopTokenSource.Cancel(); @@ -479,7 +489,7 @@ namespace GitHub.Runner.Listener messageQueueLoopTokenSource.Dispose(); - if (settings.Ephemeral) + if (settings.Ephemeral && runOnceJobCompleted) { var configManager = HostContext.GetService(); configManager.DeleteLocalRunnerConfig(); diff --git a/src/Runner.Worker/ActionCommandManager.cs b/src/Runner.Worker/ActionCommandManager.cs index 4455b545e..45b2fefdb 100644 --- a/src/Runner.Worker/ActionCommandManager.cs +++ b/src/Runner.Worker/ActionCommandManager.cs @@ -108,22 +108,18 @@ namespace GitHub.Runner.Worker // Stop command if (string.Equals(actionCommand.Command, _stopCommand, StringComparison.OrdinalIgnoreCase)) { - context.Output(input); - context.Debug("Paused processing commands until '##[{actionCommand.Data}]' is received"); + ValidateStopToken(context, actionCommand.Data); + _stopToken = actionCommand.Data; - if (_registeredCommands.Contains(actionCommand.Data) - || string.IsNullOrEmpty(actionCommand.Data) - || string.Equals(actionCommand.Data, "pause-logging", StringComparison.OrdinalIgnoreCase)) - { - var telemetry = new JobTelemetry - { - Message = $"Invoked ::stopCommand:: with token: [{actionCommand.Data}]", - Type = JobTelemetryType.ActionCommand - }; - context.JobTelemetry.Add(telemetry); - } _stopProcessCommand = true; _registeredCommands.Add(_stopToken); + if (_stopToken.Length > 6) + { + HostContext.SecretMasker.AddValue(_stopToken); + } + + context.Output(input); + context.Debug("Paused processing commands until the token you called ::stopCommands:: with is received"); return true; } // Found command @@ -157,6 +153,40 @@ namespace GitHub.Runner.Worker return true; } + private void ValidateStopToken(IExecutionContext context, string stopToken) + { +#if OS_WINDOWS + var envContext = context.ExpressionValues["env"] as DictionaryContextData; +#else + var envContext = context.ExpressionValues["env"] as CaseSensitiveDictionaryContextData; +#endif + var allowUnsecureStopCommandTokens = false; + allowUnsecureStopCommandTokens = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedStopCommandTokens)); + if (!allowUnsecureStopCommandTokens && envContext.ContainsKey(Constants.Variables.Actions.AllowUnsupportedStopCommandTokens)) + { + allowUnsecureStopCommandTokens = StringUtil.ConvertToBoolean(envContext[Constants.Variables.Actions.AllowUnsupportedStopCommandTokens].ToString()); + } + + bool isTokenInvalid = _registeredCommands.Contains(stopToken) + || string.IsNullOrEmpty(stopToken) + || string.Equals(stopToken, "pause-logging", StringComparison.OrdinalIgnoreCase); + + if (isTokenInvalid) + { + var telemetry = new JobTelemetry + { + Message = $"Invoked ::stopCommand:: with token: [{stopToken}]", + Type = JobTelemetryType.ActionCommand + }; + context.JobTelemetry.Add(telemetry); + } + + if (isTokenInvalid && !allowUnsecureStopCommandTokens) + { + throw new Exception(Constants.Runner.UnsupportedStopCommandTokenDisabled); + } + } + internal static bool EnhancedAnnotationsEnabled(IExecutionContext context) { return context.Global.Variables.GetBoolean("DistributedTask.EnhancedAnnotations") ?? false; diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 9bc6b8734..34da0a65b 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -633,7 +633,12 @@ namespace GitHub.Runner.Worker } catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled. { - if (attempt < 3) + // UnresolvableActionDownloadInfoException is a 422 client error, don't retry + // Some possible cases are: + // * Repo is rate limited + // * Repo or tag doesn't exist, or isn't public + // * Policy validation failed + if (attempt < 3 && !(ex is WebApi.UnresolvableActionDownloadInfoException)) { executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}"); executionContext.Debug(ex.ToString()); @@ -649,6 +654,7 @@ namespace GitHub.Runner.Worker // Some possible cases are: // * Repo is rate limited // * Repo or tag doesn't exist, or isn't public + // * Policy validation failed if (ex is WebApi.UnresolvableActionDownloadInfoException) { throw; diff --git a/src/Runner.Worker/JobRunner.cs b/src/Runner.Worker/JobRunner.cs index faeb4dc8e..3a30f1c87 100644 --- a/src/Runner.Worker/JobRunner.cs +++ b/src/Runner.Worker/JobRunner.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Net.Http; @@ -228,6 +229,9 @@ namespace GitHub.Runner.Worker return result; } + // Load any upgrade telemetry + LoadFromTelemetryFile(jobContext.JobTelemetry); + // Make sure we don't submit secrets as telemetry MaskTelemetrySecrets(jobContext.JobTelemetry); @@ -285,6 +289,30 @@ namespace GitHub.Runner.Worker } } + private void LoadFromTelemetryFile(List jobTelemetry) + { + try + { + var telemetryFilePath = HostContext.GetConfigFile(WellKnownConfigFile.Telemetry); + if (File.Exists(telemetryFilePath)) + { + var telemetryData = File.ReadAllText(telemetryFilePath, Encoding.UTF8); + var telemetry = new JobTelemetry + { + Message = $"Runner File Telemetry:\n{telemetryData}", + Type = JobTelemetryType.General + }; + jobTelemetry.Add(telemetry); + IOUtil.DeleteFile(telemetryFilePath); + } + } + catch (Exception e) + { + Trace.Error("Error when trying to load telemetry from telemetry file"); + Trace.Error(e); + } + } + private async Task ShutdownQueue(bool throwOnFailure) { if (_jobServerQueue != null) diff --git a/src/Test/L0/Worker/ActionCommandManagerL0.cs b/src/Test/L0/Worker/ActionCommandManagerL0.cs index f9080dbfc..ac5c4af93 100644 --- a/src/Test/L0/Worker/ActionCommandManagerL0.cs +++ b/src/Test/L0/Worker/ActionCommandManagerL0.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Worker; using GitHub.Runner.Worker.Container; @@ -83,6 +84,7 @@ namespace GitHub.Runner.Common.Tests.Worker { using (TestHostContext hc = CreateTestContext()) { + _ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues()); _ec.Setup(x => x.Write(It.IsAny(), It.IsAny())) .Returns((string tag, string line) => { @@ -105,6 +107,88 @@ namespace GitHub.Runner.Common.Tests.Worker } } + [Theory] + [InlineData("stop-commands", "1")] + [InlineData("", "1")] + [InlineData("set-env", "1")] + [InlineData("stop-commands", "true")] + [InlineData("", "true")] + [InlineData("set-env", "true")] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void StopProcessCommand__AllowsInvalidStopTokens__IfEnvVarIsSet(string invalidToken, string allowUnsupportedStopCommandTokens) + { + using (TestHostContext hc = CreateTestContext()) + { + _ec.Object.Global.EnvironmentVariables = new Dictionary(); + var expressionValues = new DictionaryContextData + { + ["env"] = +#if OS_WINDOWS + new DictionaryContextData{ { Constants.Variables.Actions.AllowUnsupportedStopCommandTokens, new StringContextData(allowUnsupportedStopCommandTokens) }} +#else + new CaseSensitiveDictionaryContextData{ { Constants.Variables.Actions.AllowUnsupportedStopCommandTokens, new StringContextData(allowUnsupportedStopCommandTokens) }} +#endif + }; + _ec.Setup(x => x.ExpressionValues).Returns(expressionValues); + _ec.Setup(x => x.JobTelemetry).Returns(new List()); + + Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null)); + } + } + + [Theory] + [InlineData("stop-commands")] + [InlineData("")] + [InlineData("set-env")] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void StopProcessCommand__FailOnInvalidStopTokens(string invalidToken) + { + using (TestHostContext hc = CreateTestContext()) + { + _ec.Object.Global.EnvironmentVariables = new Dictionary(); + _ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues()); + _ec.Setup(x => x.JobTelemetry).Returns(new List()); + Assert.Throws(() => _commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null)); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void StopProcessCommandAcceptsValidToken() + { + var validToken = "randomToken"; + using (TestHostContext hc = CreateTestContext()) + { + _ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues()); + Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{validToken}", null)); + Assert.False(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null)); + Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::{validToken}::", null)); + Assert.True(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null)); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void StopProcessCommandMasksValidTokenForEntireRun() + { + var validToken = "randomToken"; + using (TestHostContext hc = CreateTestContext()) + { + _ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues()); + Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{validToken}", null)); + Assert.False(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null)); + Assert.Equal("***", hc.SecretMasker.MaskSecrets(validToken)); + + Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::{validToken}::", null)); + Assert.True(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null)); + Assert.Equal("***", hc.SecretMasker.MaskSecrets(validToken)); + } + } + [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] @@ -202,15 +286,15 @@ namespace GitHub.Runner.Common.Tests.Worker return 1; }); - var registeredCommands = new HashSet(new string[1]{ "warning" }); + var registeredCommands = new HashSet(new string[1] { "warning" }); ActionCommand command; - + // Columns when lines are different ActionCommand.TryParseV2("::warning line=1,endLine=2,col=1,endColumn=2::this is a warning", registeredCommands, out command); Assert.Equal("1", command.Properties["col"]); IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object); Assert.False(command.Properties.ContainsKey("col")); - + // No lines with columns ActionCommand.TryParseV2("::warning col=1,endColumn=2::this is a warning", registeredCommands, out command); Assert.Equal("1", command.Properties["col"]); @@ -375,5 +459,19 @@ namespace GitHub.Runner.Common.Tests.Worker return hostContext; } + + private DictionaryContextData GetExpressionValues() + { + return new DictionaryContextData + { + ["env"] = +#if OS_WINDOWS + new DictionaryContextData() +#else + new CaseSensitiveDictionaryContextData() +#endif + }; + } + } } diff --git a/src/runnerversion b/src/runnerversion index cd48e540f..0bddf2b74 100644 --- a/src/runnerversion +++ b/src/runnerversion @@ -1 +1 @@ -2.283.1 +2.283.2