mirror of
https://github.com/actions/runner.git
synced 2025-12-11 04:46:58 +00:00
Compare commits
6 Commits
v2.288.0
...
fhammerl/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f6b4be9af | ||
|
|
b3938017b5 | ||
|
|
acb46a3e1e | ||
|
|
5e90b3ebb5 | ||
|
|
493a0bd8fc | ||
|
|
e5d19459a7 |
@@ -1,32 +1,20 @@
|
||||
## Features
|
||||
|
||||
- Make run.sh|cmd handle update without quitting so containers using them as entrypoints don't exit on update (#1646, #1633, #1708)
|
||||
- Add support for Step Summary (#1642, #1667, #1712)
|
||||
- Pass jobId to the actionsDownloadInfo controller (#1639)
|
||||
- updated systemd svc.sh to accept custom service file (#1612)
|
||||
- Add ability to specify runner group when creating service (#1675)
|
||||
- Prefer node16 over node12 when running internal scripts (#1621)
|
||||
- Sending telemetry about actions usage. (#1688)
|
||||
- Bump node12 version to latest (#1651)
|
||||
- Add internal to node version function and use better env var name (#1715)
|
||||
- Force JS Actions Node version to 16 if FF is on unless user opted out (#1716)
|
||||
- Add Runner Configuration option to disable auto update `--disableupdate` (#1558)
|
||||
- Introduce `GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY` env variable to skip SSL Cert Verification on the Runner (#1616)
|
||||
- Adds support for downloading trimmed versions of the runner when the entire package does not need to be upgraded (#1568)
|
||||
|
||||
## Bugs
|
||||
- Fix windows console runner update crash (#1670)
|
||||
- Retry policy for methods GetTenantCredential and GetJITRunnerTokenAsync (#1691)
|
||||
- Skip DeleteAgentSession when the acess token has been revoked. (#1692)
|
||||
- Repaired hashFiles call so if error was thrown, it was returned to process invoker (#1678)
|
||||
- Runner throws null ref exception when new line after EOF is missing (#1687)
|
||||
- Lets allow up to 150 characters for services on linux/mac (#1710)
|
||||
- Set Outcome/Conclusion for composite action steps (#1600)
|
||||
|
||||
## Misc
|
||||
|
||||
- Added examples and aligned language within docs/checks/actions.md (#1664)
|
||||
- Problem with debugging on macOS M1 (#1625)
|
||||
- Fix typo in hashFiles.ts. (#1672)
|
||||
- Allow mocked updates for E2E testing (#1654)
|
||||
- Move JobTelemetry and StepsTelemetry into GlobalContext. (#1680)
|
||||
- Fix inconsistency of outputs (both canceled and cancelled are used (#1624)
|
||||
- Update `run.sh` to more gracefully handle updates (#1494)
|
||||
- Use 8Mb default chunking for File Container Uploads (#1626)
|
||||
- Performance improvements in handling large amounts of live logs (#1592)
|
||||
- Allow `./svc.sh stop` to exit as soon as runner process exits (#1580)
|
||||
- Add additional tracing to help troubleshoot job message corruption (#1587)
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.288.0
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
|
||||
@@ -3,94 +3,136 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
var childProcess = require("child_process");
|
||||
var path = require("path")
|
||||
var path = require("path");
|
||||
const { exit } = require("process");
|
||||
|
||||
var supported = ['linux', 'darwin']
|
||||
var supported = ["linux", "darwin"];
|
||||
|
||||
if (supported.indexOf(process.platform) == -1) {
|
||||
console.log('Unsupported platform: ' + process.platform);
|
||||
console.log('Supported platforms are: ' + supported.toString());
|
||||
process.exit(1);
|
||||
console.log("Unsupported platform: " + process.platform);
|
||||
console.log("Supported platforms are: " + supported.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var stopping = false;
|
||||
var listener = null;
|
||||
|
||||
var runService = function () {
|
||||
var listenerExePath = path.join(__dirname, '../bin/Runner.Listener');
|
||||
var interactive = process.argv[2] === "interactive";
|
||||
var exitServiceAfterNFailures = Number(
|
||||
process.env.GITHUB_ACTIONS_SERVICE_EXIT_AFTER_N_FAILURES
|
||||
);
|
||||
|
||||
if (!stopping) {
|
||||
try {
|
||||
if (interactive) {
|
||||
console.log('Starting Runner listener interactively');
|
||||
listener = childProcess.spawn(listenerExePath, ['run'], { env: process.env });
|
||||
} else {
|
||||
console.log('Starting Runner listener with startup type: service');
|
||||
listener = childProcess.spawn(listenerExePath, ['run', '--startuptype', 'service'], { env: process.env });
|
||||
}
|
||||
|
||||
console.log(`Started listener process, pid: ${listener.pid}`);
|
||||
|
||||
listener.stdout.on('data', (data) => {
|
||||
process.stdout.write(data.toString('utf8'));
|
||||
});
|
||||
|
||||
listener.stderr.on('data', (data) => {
|
||||
process.stdout.write(data.toString('utf8'));
|
||||
});
|
||||
|
||||
listener.on("error", (err) => {
|
||||
console.log(`Runner listener fail to start with error ${err.message}`);
|
||||
});
|
||||
|
||||
listener.on('close', (code) => {
|
||||
console.log(`Runner listener exited with error code ${code}`);
|
||||
|
||||
if (code === 0) {
|
||||
console.log('Runner listener exit with 0 return code, stop the service, no retry needed.');
|
||||
stopping = true;
|
||||
} else if (code === 1) {
|
||||
console.log('Runner listener exit with terminated error, stop the service, no retry needed.');
|
||||
stopping = true;
|
||||
} else if (code === 2) {
|
||||
console.log('Runner listener exit with retryable error, re-launch runner in 5 seconds.');
|
||||
} else if (code === 3) {
|
||||
console.log('Runner listener exit because of updating, re-launch runner in 5 seconds.');
|
||||
} else {
|
||||
console.log('Runner listener exit with undefined return code, re-launch runner in 5 seconds.');
|
||||
}
|
||||
|
||||
if (!stopping) {
|
||||
setTimeout(runService, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
if (exitServiceAfterNFailures <= 0) {
|
||||
exitServiceAfterNFailures = NaN;
|
||||
}
|
||||
|
||||
runService();
|
||||
console.log('Started running service');
|
||||
var consecutiveFailureCount = 0;
|
||||
|
||||
var gracefulShutdown = function (code) {
|
||||
console.log('Shutting down runner listener');
|
||||
stopping = true;
|
||||
if (listener) {
|
||||
console.log('Sending SIGINT to runner listener to stop');
|
||||
listener.kill('SIGINT');
|
||||
console.log("Shutting down runner listener");
|
||||
stopping = true;
|
||||
if (listener) {
|
||||
console.log("Sending SIGINT to runner listener to stop");
|
||||
listener.kill("SIGINT");
|
||||
|
||||
console.log('Sending SIGKILL to runner listener');
|
||||
setTimeout(() => listener.kill('SIGKILL'), 30000).unref();
|
||||
console.log("Sending SIGKILL to runner listener");
|
||||
setTimeout(() => listener.kill("SIGKILL"), 30000).unref();
|
||||
}
|
||||
};
|
||||
|
||||
var runService = function () {
|
||||
var listenerExePath = path.join(__dirname, "../bin/Runner.Listener");
|
||||
var interactive = process.argv[2] === "interactive";
|
||||
|
||||
if (!stopping) {
|
||||
try {
|
||||
if (interactive) {
|
||||
console.log("Starting Runner listener interactively");
|
||||
listener = childProcess.spawn(listenerExePath, ["run"], {
|
||||
env: process.env,
|
||||
});
|
||||
} else {
|
||||
console.log("Starting Runner listener with startup type: service");
|
||||
listener = childProcess.spawn(
|
||||
listenerExePath,
|
||||
["run", "--startuptype", "service"],
|
||||
{ env: process.env }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Started listener process, pid: ${listener.pid}`);
|
||||
|
||||
listener.stdout.on("data", (data) => {
|
||||
if (data.toString("utf8").includes("Listening for Jobs")) {
|
||||
consecutiveFailureCount = 0;
|
||||
}
|
||||
process.stdout.write(data.toString("utf8"));
|
||||
});
|
||||
|
||||
listener.stderr.on("data", (data) => {
|
||||
process.stdout.write(data.toString("utf8"));
|
||||
});
|
||||
|
||||
listener.on("error", (err) => {
|
||||
console.log(`Runner listener fail to start with error ${err.message}`);
|
||||
});
|
||||
|
||||
listener.on("close", (code) => {
|
||||
console.log(`Runner listener exited with error code ${code}`);
|
||||
|
||||
if (code === 0) {
|
||||
console.log(
|
||||
"Runner listener exit with 0 return code, stop the service, no retry needed."
|
||||
);
|
||||
stopping = true;
|
||||
} else if (code === 1) {
|
||||
console.log(
|
||||
"Runner listener exit with terminated error, stop the service, no retry needed."
|
||||
);
|
||||
stopping = true;
|
||||
} else if (code === 2) {
|
||||
console.log(
|
||||
"Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
||||
);
|
||||
consecutiveFailureCount = 0;
|
||||
} else if (code === 3 || code === 4) {
|
||||
console.log(
|
||||
"Runner listener exit because of updating, re-launch runner in 5 seconds."
|
||||
);
|
||||
consecutiveFailureCount = 0;
|
||||
} else {
|
||||
var messagePrefix = "Runner listener exit with undefined return code";
|
||||
consecutiveFailureCount++;
|
||||
if (
|
||||
!isNaN(exitServiceAfterNFailures) &&
|
||||
consecutiveFailureCount >= exitServiceAfterNFailures
|
||||
) {
|
||||
console.error(
|
||||
`${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures`
|
||||
);
|
||||
gracefulShutdown(5);
|
||||
return;
|
||||
} else {
|
||||
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!stopping) {
|
||||
setTimeout(runService, 5000);
|
||||
}
|
||||
});
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
gracefulShutdown(0);
|
||||
runService();
|
||||
console.log("Started running service");
|
||||
|
||||
process.on("SIGINT", () => {
|
||||
gracefulShutdown(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
gracefulShutdown(0);
|
||||
process.on("SIGTERM", () => {
|
||||
gracefulShutdown(0);
|
||||
});
|
||||
|
||||
@@ -45,17 +45,17 @@ elif [[ $returnCode == 1 ]]; then
|
||||
elif [[ $returnCode == 2 ]]; then
|
||||
echo "Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
||||
safe_sleep
|
||||
exit 2
|
||||
exit 1
|
||||
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"
|
||||
safe_sleep
|
||||
exit 2
|
||||
exit 1
|
||||
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"
|
||||
safe_sleep
|
||||
exit 2
|
||||
exit 1
|
||||
else
|
||||
echo "Exiting with unknown error code: ${returnCode}"
|
||||
exit 0
|
||||
|
||||
@@ -15,7 +15,7 @@ while :;
|
||||
do
|
||||
"$DIR"/run-helper.sh $*
|
||||
returnCode=$?
|
||||
if [[ $returnCode -eq 2 ]]; then
|
||||
if [[ $returnCode == 1 ]]; then
|
||||
echo "Restarting runner..."
|
||||
else
|
||||
echo "Exiting runner..."
|
||||
|
||||
@@ -156,8 +156,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/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 readonly string UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
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
|
||||
@@ -220,15 +219,14 @@ namespace GitHub.Runner.Common
|
||||
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";
|
||||
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||
}
|
||||
|
||||
public static class Agent
|
||||
{
|
||||
public static readonly string ToolsDirectory = "agent.ToolsDirectory";
|
||||
|
||||
// Set this env var to "node12" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
|
||||
// Set this env var to force a node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
public static readonly string ForcedNodeVersion = "GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION";
|
||||
}
|
||||
|
||||
public static class System
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace GitHub.Runner.Common.Util
|
||||
{
|
||||
private const string _defaultNodeVersion = "node16";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] {"node12", "node16"});
|
||||
public static string GetInternalNodeVersion()
|
||||
public static string GetNodeVersion()
|
||||
{
|
||||
var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
|
||||
var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedNodeVersion);
|
||||
return !string.IsNullOrEmpty(forcedNodeVersion) && BuiltInNodeVersions.Contains(forcedNodeVersion) ? forcedNodeVersion : _defaultNodeVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ namespace GitHub.Runner.Listener.Check
|
||||
});
|
||||
|
||||
var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert");
|
||||
var node = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
var node = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{downloadCertScript}\"' ");
|
||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
|
||||
await processInvoker.ExecuteAsync(
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace GitHub.Runner.Listener.Check
|
||||
});
|
||||
|
||||
var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js");
|
||||
var node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
var node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{makeWebRequestScript}\"' ");
|
||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
|
||||
await processInvoker.ExecuteAsync(
|
||||
|
||||
@@ -48,12 +48,13 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-");
|
||||
|
||||
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName);
|
||||
if (serviceName.Length > MaxServiceNameLength)
|
||||
|
||||
if (serviceName.Length > 80)
|
||||
{
|
||||
Trace.Verbose($"Calculated service name is too long (> {MaxServiceNameLength} chars). Trying again by calculating a shorter name.");
|
||||
// Add 5 to add -xxxx random number on the end
|
||||
int exceededCharLength = serviceName.Length - MaxServiceNameLength + 5;
|
||||
string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, MaxRepoOrgCharacters);
|
||||
Trace.Verbose($"Calculated service name is too long (> 80 chars). Trying again by calculating a shorter name.");
|
||||
|
||||
int exceededCharLength = serviceName.Length - 80;
|
||||
string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, 45);
|
||||
|
||||
exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length;
|
||||
|
||||
@@ -65,10 +66,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength);
|
||||
}
|
||||
|
||||
// Lets add a suffix with a random number to reduce the chance of collisions between runner names once we truncate
|
||||
var random = new Random();
|
||||
var num = random.Next(1000, 9999).ToString();
|
||||
runnerNameSubstring +=$"-{num}";
|
||||
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring);
|
||||
}
|
||||
|
||||
@@ -76,12 +73,5 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration.");
|
||||
}
|
||||
#if (OS_LINUX || OS_OSX)
|
||||
const int MaxServiceNameLength = 150;
|
||||
const int MaxRepoOrgCharacters = 70;
|
||||
#elif OS_WINDOWS
|
||||
const int MaxServiceNameLength = 80;
|
||||
const int MaxRepoOrgCharacters = 45;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,7 +1070,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
string binDir = HostContext.GetDirectory(WellKnownDirectory.Bin);
|
||||
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
||||
var hashResult = string.Empty;
|
||||
|
||||
|
||||
@@ -680,7 +680,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node12");
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedNodeVersion, "node12");
|
||||
}
|
||||
|
||||
// Environment variables shared across all actions
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace GitHub.Runner.Worker.Expressions
|
||||
string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
string runnerRoot = new DirectoryInfo(binDir).Parent.FullName;
|
||||
|
||||
string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
||||
var hashResult = string.Empty;
|
||||
var p = new ProcessInvoker(new HashFilesTrace(context.Trace));
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
if (fileSize > _attachmentSizeLimit)
|
||||
{
|
||||
context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, _attachmentSizeLimit / 1024, fileSize / 1024));
|
||||
context.Error($"$GITHUB_STEP_SUMMARY supports content up a size of {_attachmentSizeLimit / 1024}k got {fileSize / 1024}k");
|
||||
Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload");
|
||||
|
||||
return;
|
||||
|
||||
@@ -55,23 +55,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
else if (data.ExecutionType == ActionExecutionType.NodeJS)
|
||||
{
|
||||
handler = HostContext.CreateService<INodeScriptActionHandler>();
|
||||
var nodeData = data as NodeJSActionExecutionData;
|
||||
|
||||
// With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
(executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode16") ?? false))
|
||||
{
|
||||
// The user can opt out of this behaviour by setting this variable to true, either setting 'env' in their workflow or as an environment variable on their machine
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
|
||||
var isWorkflowOptOutSet = !string.IsNullOrEmpty(workflowOptOut);
|
||||
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
|
||||
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
|
||||
if (!isOptOut)
|
||||
{
|
||||
nodeData.NodeVersion = "node16";
|
||||
}
|
||||
}
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
(handler as INodeScriptActionHandler).Data = data as NodeJSActionExecutionData;
|
||||
}
|
||||
else if (data.ExecutionType == ActionExecutionType.Script)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
|
||||
@@ -266,7 +266,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS
|
||||
{
|
||||
// launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process
|
||||
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
||||
string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js");
|
||||
arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}";
|
||||
fileName = node;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using Xunit;
|
||||
@@ -16,7 +15,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
|
||||
settings.AgentName = "thisiskindofalongrunnerabcde";
|
||||
settings.AgentName = "thisiskindofalongrunnername1";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
|
||||
|
||||
@@ -44,7 +43,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
Assert.Equal("actions", serviceNameParts[0]);
|
||||
Assert.Equal("runner", serviceNameParts[1]);
|
||||
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
|
||||
Assert.Equal("thisiskindofalongrunnerabcde", serviceNameParts[3]);
|
||||
Assert.Equal("thisiskindofalongrunnername1", serviceNameParts[3]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
|
||||
settings.AgentName = "thisiskindofalongrunnernabcde";
|
||||
settings.AgentName = "thisiskindofalongrunnername12";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
|
||||
|
||||
@@ -83,129 +82,88 @@ namespace GitHub.Runner.Common.Tests
|
||||
Assert.Equal("actions", serviceNameParts[0]);
|
||||
Assert.Equal("runner", serviceNameParts[1]);
|
||||
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
|
||||
Assert.Equal("thisiskindofalongrunnernabcde", serviceNameParts[3]); // should not have random numbers unless we exceed 80
|
||||
Assert.Equal("thisiskindofalongrunnername12", serviceNameParts[3]);
|
||||
}
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Service")]
|
||||
public void CalculateServiceNameLimitsServiceNameTo80Chars()
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Service")]
|
||||
public void CalculateServiceNameLimitsServiceNameTo80Chars()
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
|
||||
settings.AgentName = "thisisareallyreallylongbutstillvalidagentname";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample";
|
||||
|
||||
string serviceNamePattern = "actions.runner.{0}.{1}";
|
||||
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
|
||||
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
ServiceControlManager scm = new ServiceControlManager();
|
||||
|
||||
settings.AgentName = "thisisareallyreallylongbutstillvalidagentname";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample";
|
||||
scm.Initialize(hc);
|
||||
scm.CalculateServiceName(
|
||||
settings,
|
||||
serviceNamePattern,
|
||||
serviceDisplayNamePattern,
|
||||
out string serviceName,
|
||||
out string serviceDisplayName);
|
||||
|
||||
string serviceNamePattern = "actions.runner.{0}.{1}";
|
||||
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
|
||||
// Verify name has been shortened to 80 characters
|
||||
Assert.Equal(80, serviceName.Length);
|
||||
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
ServiceControlManager scm = new ServiceControlManager();
|
||||
var serviceNameParts = serviceName.Split('.');
|
||||
|
||||
scm.Initialize(hc);
|
||||
scm.CalculateServiceName(
|
||||
settings,
|
||||
serviceNamePattern,
|
||||
serviceDisplayNamePattern,
|
||||
out string serviceName,
|
||||
out string serviceDisplayName);
|
||||
|
||||
// Verify name has been shortened to 80 characters
|
||||
Assert.Equal(80, serviceName.Length);
|
||||
|
||||
var serviceNameParts = serviceName.Split('.');
|
||||
|
||||
// Verify that each component has been shortened to a sensible length
|
||||
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
|
||||
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
|
||||
Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-'
|
||||
Assert.Matches(@"^(thisisareallyr-[0-9]{4})$", serviceNameParts[3]); // Remainder of unused chars, 4 random numbers added at the end
|
||||
}
|
||||
// Verify that each component has been shortened to a sensible length
|
||||
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
|
||||
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
|
||||
Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-'
|
||||
Assert.Equal("thisisareallyreally", serviceNameParts[3]); // Remainder of unused chars
|
||||
}
|
||||
}
|
||||
|
||||
// Special 'defensive' test that verifies we can gracefully handle creating service names
|
||||
// in case GitHub.com changes its org/repo naming convention in the future,
|
||||
// and some of these characters may be invalid for service names
|
||||
// Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Service")]
|
||||
public void CalculateServiceNameSanitizeOutOfRangeChars()
|
||||
// Special 'defensive' test that verifies we can gracefully handle creating service names
|
||||
// in case GitHub.com changes its org/repo naming convention in the future,
|
||||
// and some of these characters may be invalid for service names
|
||||
// Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Service")]
|
||||
public void CalculateServiceNameSanitizeOutOfRangeChars()
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
|
||||
settings.AgentName = "name";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()";
|
||||
|
||||
string serviceNamePattern = "actions.runner.{0}.{1}";
|
||||
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
|
||||
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
ServiceControlManager scm = new ServiceControlManager();
|
||||
|
||||
settings.AgentName = "name";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()";
|
||||
scm.Initialize(hc);
|
||||
scm.CalculateServiceName(
|
||||
settings,
|
||||
serviceNamePattern,
|
||||
serviceDisplayNamePattern,
|
||||
out string serviceName,
|
||||
out string serviceDisplayName);
|
||||
|
||||
string serviceNamePattern = "actions.runner.{0}.{1}";
|
||||
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
|
||||
var serviceNameParts = serviceName.Split('.');
|
||||
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
ServiceControlManager scm = new ServiceControlManager();
|
||||
|
||||
scm.Initialize(hc);
|
||||
scm.CalculateServiceName(
|
||||
settings,
|
||||
serviceNamePattern,
|
||||
serviceDisplayNamePattern,
|
||||
out string serviceName,
|
||||
out string serviceDisplayName);
|
||||
|
||||
var serviceNameParts = serviceName.Split('.');
|
||||
|
||||
// Verify service name parts are sanitized correctly
|
||||
Assert.Equal("actions", serviceNameParts[0]);
|
||||
Assert.Equal("runner", serviceNameParts[1]);
|
||||
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
|
||||
Assert.Equal("name", serviceNameParts[3]);
|
||||
}
|
||||
// Verify service name parts are sanitized correctly
|
||||
Assert.Equal("actions", serviceNameParts[0]);
|
||||
Assert.Equal("runner", serviceNameParts[1]);
|
||||
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
|
||||
Assert.Equal("name", serviceNameParts[3]);
|
||||
}
|
||||
#else
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Service")]
|
||||
public void CalculateServiceNameLimitsServiceNameTo150Chars()
|
||||
{
|
||||
RunnerSettings settings = new RunnerSettings();
|
||||
|
||||
settings.AgentName = "thisisareallyreallylongbutstillvalidagentnameiamusingforthisexampletotestverylongnamelimits";
|
||||
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
|
||||
settings.GitHubUrl = "https://github.com/myreallylongorganizationexampleonlinux/myreallylongrepoexampleonlinux1234";
|
||||
|
||||
string serviceNamePattern = "actions.runner.{0}.{1}";
|
||||
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
|
||||
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
ServiceControlManager scm = new ServiceControlManager();
|
||||
|
||||
scm.Initialize(hc);
|
||||
scm.CalculateServiceName(
|
||||
settings,
|
||||
serviceNamePattern,
|
||||
serviceDisplayNamePattern,
|
||||
out string serviceName,
|
||||
out string serviceDisplayName);
|
||||
|
||||
// Verify name has been shortened to 150
|
||||
Assert.Equal(150, serviceName.Length);
|
||||
|
||||
var serviceNameParts = serviceName.Split('.');
|
||||
|
||||
// Verify that each component has been shortened to a sensible length
|
||||
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
|
||||
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
|
||||
Assert.Equal("myreallylongorganizationexampleonlinux-myreallylongrepoexampleonlinux1", serviceNameParts[2]); // First 70 chars, '/' has been replaced with '-'
|
||||
Assert.Matches(@"^(thisisareallyreallylongbutstillvalidagentnameiamusingforthi-[0-9]{4})$", serviceNameParts[3]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
||||
{
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class HandlerFactoryL0
|
||||
{
|
||||
private Mock<IExecutionContext> _ec;
|
||||
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
||||
{
|
||||
var hostContext = new TestHostContext(this, testName);
|
||||
_ec = new Mock<IExecutionContext>();
|
||||
_ec.SetupAllProperties();
|
||||
_ec.Object.Initialize(hostContext);
|
||||
var handler = new Mock<INodeScriptActionHandler>();
|
||||
handler.SetupAllProperties();
|
||||
hostContext.EnqueueInstance(handler.Object);
|
||||
//hostContext.EnqueueInstance(new ActionCommandManager() as IActionCommandManager);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData("node12", "", "", "", "node12")]
|
||||
[InlineData("node12", "true", "", "", "node16")]
|
||||
[InlineData("node12", "true", "", "true", "node12")]
|
||||
[InlineData("node12", "true", "true", "", "node12")]
|
||||
[InlineData("node12", "true", "true", "true", "node12")]
|
||||
[InlineData("node12", "true", "false", "true", "node16")] // workflow overrides env
|
||||
[InlineData("node16", "", "", "", "node16")]
|
||||
[InlineData("node16", "true", "", "", "node16")]
|
||||
[InlineData("node16", "true", "", "true", "node16")]
|
||||
[InlineData("node16", "true", "true", "", "node16")]
|
||||
[InlineData("node16", "true", "true", "true", "node16")]
|
||||
[InlineData("node16", "true", "false", "true", "node16")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string serverFeatureFlag, string workflowOptOut, string machineOptOut, string expectedVersion)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var hf = new HandlerFactory();
|
||||
hf.Initialize(hc);
|
||||
|
||||
// Server Feature Flag
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
if (!string.IsNullOrEmpty(serverFeatureFlag))
|
||||
{
|
||||
variables["DistributedTask.ForceGithubJavascriptActionsToNode16"] = serverFeatureFlag;
|
||||
}
|
||||
Variables serverVariables = new Variables(hc, variables);
|
||||
|
||||
// Workflow opt-out
|
||||
var workflowVariables = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(workflowOptOut))
|
||||
{
|
||||
workflowVariables[Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion] = workflowOptOut;
|
||||
}
|
||||
|
||||
// Machine opt-out
|
||||
if (!string.IsNullOrEmpty(machineOptOut))
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, machineOptOut);
|
||||
}
|
||||
|
||||
_ec.Setup(x => x.Global).Returns(new GlobalContext()
|
||||
{
|
||||
Variables = serverVariables,
|
||||
EnvironmentVariables = workflowVariables
|
||||
});
|
||||
|
||||
|
||||
// Act.
|
||||
var data = new NodeJSActionExecutionData();
|
||||
data.NodeVersion = inputVersion;
|
||||
var handler = hf.Create(
|
||||
_ec.Object,
|
||||
new ScriptReference(),
|
||||
new Mock<IStepHost>().Object,
|
||||
data,
|
||||
new Dictionary<string, string>(),
|
||||
new Dictionary<string, string>(),
|
||||
new Variables(hc, new Dictionary<string, VariableValue>()), "", new List<JobExtensionRunner>()
|
||||
) as INodeScriptActionHandler;
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
2.288.0
|
||||
2.287.1
|
||||
|
||||
Reference in New Issue
Block a user