From e1fa1fcbc3de1c0b57f6fe2a82f77a8ed3a138b1 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Tue, 20 Aug 2024 09:55:30 -0400 Subject: [PATCH 01/13] fix: add `jq`, `git`, `unzip` and `curl` to default packages installed (#3056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add `git` and `curl` to default packages installed Hi 👋🏼 These packages are used in a ton of actions on the marketplace. It would be nice if they were installed and ready for use instead of having to install them with `apt-get` on every single Github workflow. * Update Dockerfile * Update images/Dockerfile Co-authored-by: Guillermo Caracuel <633810+gcaracuel@users.noreply.github.com> * Update images/Dockerfile Co-authored-by: Tingluo Huang --------- Co-authored-by: Guillermo Caracuel <633810+gcaracuel@users.noreply.github.com> Co-authored-by: Tingluo Huang --- images/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/images/Dockerfile b/images/Dockerfile index dd8437e1d27..7064053acfb 100644 --- a/images/Dockerfile +++ b/images/Dockerfile @@ -41,12 +41,13 @@ ENV ImageOS=ubuntu22 # 'gpg-agent' and 'software-properties-common' are needed for the 'add-apt-repository' command that follows RUN apt update -y \ - && apt install -y --no-install-recommends sudo lsb-release gpg-agent software-properties-common \ + && apt install -y --no-install-recommends sudo lsb-release gpg-agent software-properties-common curl jq unzip \ && rm -rf /var/lib/apt/lists/* # Configure git-core/ppa based on guidance here: https://git-scm.com/download/linux RUN add-apt-repository ppa:git-core/ppa \ - && apt update -y + && apt update -y \ + && apt install -y --no-install-recommends git RUN adduser --disabled-password --gecos "" --uid 1001 runner \ && groupadd docker --gid 123 \ From 99b464e10200ade9ee2ed20c9b7568c1dabc5d2b Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Tue, 27 Aug 2024 12:05:26 -0400 Subject: [PATCH 02/13] Trace GitHub RequestId to log. (#3442) --- src/Runner.Worker/ActionManager.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 0ec72b0585f..f32cad28ea9 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -1102,6 +1102,7 @@ private async Task DownloadRepositoryArchive(IExecutionContext executionContext, int timeoutSeconds = 20 * 60; while (retryCount < 3) { + string requestId = string.Empty; using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds))) using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken)) { @@ -1117,7 +1118,7 @@ private async Task DownloadRepositoryArchive(IExecutionContext executionContext, httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); using (var response = await httpClient.GetAsync(downloadUrl)) { - var requestId = UrlUtil.GetGitHubRequestId(response.Headers); + requestId = UrlUtil.GetGitHubRequestId(response.Headers); if (!string.IsNullOrEmpty(requestId)) { Trace.Info($"Request URL: {downloadUrl} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}"); @@ -1155,7 +1156,7 @@ private async Task DownloadRepositoryArchive(IExecutionContext executionContext, catch (OperationCanceledException ex) when (!executionContext.CancellationToken.IsCancellationRequested && retryCount >= 2) { Trace.Info($"Action download final retry timeout after {timeoutSeconds} seconds."); - throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message}"); + throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message} {requestId}"); } catch (ActionNotFoundException) { @@ -1170,11 +1171,11 @@ private async Task DownloadRepositoryArchive(IExecutionContext executionContext, if (actionDownloadTimeout.Token.IsCancellationRequested) { // action download didn't finish within timeout - executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds."); + executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds. {requestId}"); } else { - executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message}"); + executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message} {requestId}"); } } } From 36c66c808363839ea7069bef87fd4572c8bb6c54 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Tue, 3 Sep 2024 17:06:35 -0500 Subject: [PATCH 03/13] Fix issues for composite actions (Run Service flow) (#3446) --- src/Runner.Worker/ExecutionContext.cs | 15 +++-- src/Test/L0/Worker/ExecutionContextL0.cs | 78 +++++++++++++++++++++++- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 77c145d1ddc..f34a9c54a28 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -83,7 +83,7 @@ public interface IExecutionContext : IRunnerService // Initialize void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token); void CancelToken(); - IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, TimeSpan? timeout = null); + IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, List embeddedIssueCollector = null, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, TimeSpan? timeout = null); IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary intraActionState = null, string siblingScopeName = null); // logging @@ -135,7 +135,6 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext private readonly TimelineRecord _record = new(); private readonly Dictionary _detailRecords = new(); - private readonly List _embeddedIssueCollector; private readonly object _loggerLock = new(); private readonly object _matchersLock = new(); private readonly ExecutionContext _parentExecutionContext; @@ -154,6 +153,7 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext private CancellationTokenSource _cancellationTokenSource; private TaskCompletionSource _forceCompleted = new(); private bool _throttlingReported = false; + private List _embeddedIssueCollector; // only job level ExecutionContext will track throttling delay. private long _totalThrottlingDelayInMilliseconds = 0; @@ -356,6 +356,7 @@ public IExecutionContext CreateChild( int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, + List embeddedIssueCollector = null, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, @@ -365,6 +366,10 @@ public IExecutionContext CreateChild( var child = new ExecutionContext(this, isEmbedded); child.Initialize(HostContext); + if ((Global.Variables.GetBoolean("RunService.FixEmbeddedIssues") ?? false) && embeddedIssueCollector != null) + { + child._embeddedIssueCollector = embeddedIssueCollector; + } child.Global = Global; child.ScopeName = scopeName; child.ContextName = contextName; @@ -433,7 +438,7 @@ public IExecutionContext CreateEmbeddedChild( Dictionary intraActionState = null, string siblingScopeName = null) { - return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName, timeout: GetRemainingTimeout(), recordOrder: _record.Order); + return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, embeddedIssueCollector: _embeddedIssueCollector, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName, timeout: GetRemainingTimeout(), recordOrder: _record.Order); } public void Start(string currentOperation = null) @@ -520,7 +525,6 @@ public TaskResult Complete(TaskResult? result = null, string currentOperation = Global.StepsResult.Add(stepResult); } - if (Root != this) { // only dispose TokenSource for step level ExecutionContext @@ -837,7 +841,6 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation // Actions environment ActionsEnvironment = message.ActionsEnvironment; - // Service container info Global.ServiceContainers = new List(); @@ -1418,7 +1421,7 @@ private static void ResolvePathsInExpressionValuesDictionary(this IExecutionCont { if (key == PipelineTemplateConstants.HostWorkspace) { - // The HostWorkspace context var is excluded so that there is a var that always points to the host path. + // The HostWorkspace context var is excluded so that there is a var that always points to the host path. // This var can be used to translate back from container paths, e.g. in HashFilesFunction, which always runs on the host machine continue; } diff --git a/src/Test/L0/Worker/ExecutionContextL0.cs b/src/Test/L0/Worker/ExecutionContextL0.cs index 08abcd09585..bf2a5835b88 100644 --- a/src/Test/L0/Worker/ExecutionContextL0.cs +++ b/src/Test/L0/Worker/ExecutionContextL0.cs @@ -773,6 +773,82 @@ public void PublishStepTelemetry_EmbeddedStep() [Trait("Level", "L0")] [Trait("Category", "Worker")] public void PublishStepResult_EmbeddedStep() + { + using (TestHostContext hc = CreateTestContext()) + { + // Job request + TaskOrchestrationPlanReference plan = new(); + TimelineReference timeline = new(); + Guid jobId = Guid.NewGuid(); + string jobName = "some job name"; + var variables = new Dictionary() + { + ["RunService.FixEmbeddedIssues"] = new VariableValue("true"), + }; + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, variables, new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() + { + Alias = Pipelines.PipelineConstants.SelfAlias, + Id = "github", + Version = "sha1" + }); + jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); + + // Mocks + var pagingLogger = new Mock(); + var pagingLogger2 = new Mock(); + var jobServerQueue = new Mock(); + jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); + hc.EnqueueInstance(pagingLogger.Object); + hc.EnqueueInstance(pagingLogger2.Object); + hc.SetSingleton(jobServerQueue.Object); + + // Job context + var jobContext = new Runner.Worker.ExecutionContext(); + jobContext.Initialize(hc); + jobContext.InitializeJob(jobRequest, CancellationToken.None); + jobContext.Start(); + + // Step 1 context + var step1 = jobContext.CreateChild(Guid.NewGuid(), "my_step", "my_step", null, null, ActionRunStage.Main); + step1.Start(); + + // Embedded step 1a context + var embeddedStep1a = step1.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main); + embeddedStep1a.Start(); + embeddedStep1a.StepTelemetry.Type = "node16"; + embeddedStep1a.StepTelemetry.Action = "actions/checkout"; + embeddedStep1a.StepTelemetry.Ref = "v2"; + embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); + embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); + embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default); + embeddedStep1a.Complete(); + + // Embedded step 1b context + var embeddedStep1b = step1.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main); + embeddedStep1b.Start(); + embeddedStep1b.StepTelemetry.Type = "node16"; + embeddedStep1b.StepTelemetry.Action = "actions/checkout"; + embeddedStep1b.StepTelemetry.Ref = "v2"; + embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Error, Message = "error 2" }, ExecutionContextLogOptions.Default); + embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning 2" }, ExecutionContextLogOptions.Default); + embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice 2" }, ExecutionContextLogOptions.Default); + embeddedStep1b.Complete(); + + step1.Complete(); + + // Assert + Assert.Equal(3, jobContext.Global.StepsResult.Count); + Assert.Equal(0, jobContext.Global.StepsResult[0].Annotations.Count); + Assert.Equal(0, jobContext.Global.StepsResult[1].Annotations.Count); + Assert.Equal(6, jobContext.Global.StepsResult[2].Annotations.Count); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void PublishStepResult_EmbeddedStep_Legacy() { using (TestHostContext hc = CreateTestContext()) { @@ -807,7 +883,7 @@ public void PublishStepResult_EmbeddedStep() ec.InitializeJob(jobRequest, CancellationToken.None); ec.Start(); - var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true); + var embeddedStep = ec.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main); embeddedStep.Start(); embeddedStep.StepTelemetry.Type = "node16"; From 65764d9ddc30e05781b2db1ce647c1433de33582 Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Thu, 5 Sep 2024 16:12:29 -0400 Subject: [PATCH 04/13] Capature actions_type after resolving alpine container. (#3455) --- src/Runner.Worker/Handlers/NodeScriptActionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs b/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs index 9d412a72314..6090e5be315 100644 --- a/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs +++ b/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs @@ -93,7 +93,6 @@ public async Task RunAsync(ActionRunStage stage) ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre; ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost; } - ExecutionContext.StepTelemetry.Type = Data.NodeVersion; ArgUtil.NotNullOrEmpty(target, nameof(target)); target = Path.Combine(ActionDirectory, target); @@ -124,6 +123,7 @@ public async Task RunAsync(ActionRunStage stage) Data.NodeVersion = "node20"; } var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext, Data.NodeVersion); + ExecutionContext.StepTelemetry.Type = nodeRuntimeVersion; string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}"); // Format the arguments passed to node. From 4c0a43f0e48b21fc23bf7458cd5657863a7a80db Mon Sep 17 00:00:00 2001 From: Luke Tomlinson Date: Thu, 5 Sep 2024 17:08:57 -0400 Subject: [PATCH 05/13] Handle Error Body in Responses from Broker (#3454) --- src/Runner.Common/BrokerServer.cs | 2 +- src/Sdk/RSWebApi/Contracts/BrokerError.cs | 20 +++++++++ src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs | 10 +++++ src/Sdk/WebApi/WebApi/BrokerHttpClient.cs | 41 +++++++++++++++++-- 4 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 src/Sdk/RSWebApi/Contracts/BrokerError.cs create mode 100644 src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs diff --git a/src/Runner.Common/BrokerServer.cs b/src/Runner.Common/BrokerServer.cs index 5e1311715c5..4c612e9615e 100644 --- a/src/Runner.Common/BrokerServer.cs +++ b/src/Runner.Common/BrokerServer.cs @@ -92,7 +92,7 @@ public Task ForceRefreshConnection(VssCredentials credentials) public bool ShouldRetryException(Exception ex) { - if (ex is AccessDeniedException ade && ade.ErrorCode == 1) + if (ex is AccessDeniedException ade) { return false; } diff --git a/src/Sdk/RSWebApi/Contracts/BrokerError.cs b/src/Sdk/RSWebApi/Contracts/BrokerError.cs new file mode 100644 index 00000000000..c2e4bfa7b6b --- /dev/null +++ b/src/Sdk/RSWebApi/Contracts/BrokerError.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; + +namespace GitHub.Actions.RunService.WebApi +{ + [DataContract] + public class BrokerError + { + [DataMember(Name = "source", EmitDefaultValue = false)] + public string Source { get; set; } + + [DataMember(Name = "errorKind", EmitDefaultValue = false)] + public string ErrorKind { get; set; } + + [DataMember(Name = "statusCode", EmitDefaultValue = false)] + public int StatusCode { get; set; } + + [DataMember(Name = "errorMessage", EmitDefaultValue = false)] + public string Message { get; set; } + } +} diff --git a/src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs b/src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs new file mode 100644 index 00000000000..15c423017db --- /dev/null +++ b/src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs @@ -0,0 +1,10 @@ +using System.Runtime.Serialization; + +namespace GitHub.Actions.RunService.WebApi +{ + [DataContract] + public class BrokerErrorKind + { + public const string RunnerVersionTooOld = "RunnerVersionTooOld"; + } +} diff --git a/src/Sdk/WebApi/WebApi/BrokerHttpClient.cs b/src/Sdk/WebApi/WebApi/BrokerHttpClient.cs index e9ad938fb9f..8b67da2e5b9 100644 --- a/src/Sdk/WebApi/WebApi/BrokerHttpClient.cs +++ b/src/Sdk/WebApi/WebApi/BrokerHttpClient.cs @@ -103,6 +103,7 @@ public async Task GetRunnerMessageAsync( new HttpMethod("GET"), requestUri: requestUri, queryParameters: queryParams, + readErrorBody: true, cancellationToken: cancellationToken); if (result.IsSuccess) @@ -110,8 +111,21 @@ public async Task GetRunnerMessageAsync( return result.Value; } - // the only time we throw a `Forbidden` exception from Listener /messages is when the runner is - // disable_update and is too old to poll + if (TryParseErrorBody(result.ErrorBody, out BrokerError brokerError)) + { + switch (brokerError.ErrorKind) + { + case BrokerErrorKind.RunnerVersionTooOld: + throw new AccessDeniedException(brokerError.Message) + { + ErrorCode = 1 + }; + default: + break; + } + } + + // temporary back compat if (result.StatusCode == HttpStatusCode.Forbidden) { throw new AccessDeniedException($"{result.Error} Runner version v{runnerVersion} is deprecated and cannot receive messages.") @@ -120,7 +134,7 @@ public async Task GetRunnerMessageAsync( }; } - throw new Exception($"Failed to get job message: {result.Error}"); + throw new Exception($"Failed to get job message. Request to {requestUri} failed with status: {result.StatusCode}. Error message {result.Error}"); } public async Task CreateSessionAsync( @@ -172,5 +186,26 @@ public async Task DeleteSessionAsync( throw new Exception($"Failed to delete broker session: {result.Error}"); } + + private static bool TryParseErrorBody(string errorBody, out BrokerError error) + { + if (!string.IsNullOrEmpty(errorBody)) + { + try + { + error = JsonUtility.FromString(errorBody); + if (error?.Source == "actions-broker-listener") + { + return true; + } + } + catch (Exception) + { + } + } + + error = null; + return false; + } } } From 0b0cb5520d3bf0dc49e7c353df5062ff18ce521e Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Fri, 6 Sep 2024 17:16:17 -0400 Subject: [PATCH 06/13] Add runner or worker to the useragent. (#3457) --- src/Runner.Common/HostContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Runner.Common/HostContext.cs b/src/Runner.Common/HostContext.cs index 06b0cd5892a..46ea7042d8b 100644 --- a/src/Runner.Common/HostContext.cs +++ b/src/Runner.Common/HostContext.cs @@ -248,6 +248,7 @@ public HostContext(string hostType, string logFile = null) var currentProcess = Process.GetCurrentProcess(); _userAgents.Add(new ProductInfoHeaderValue("Pid", currentProcess.Id.ToString())); _userAgents.Add(new ProductInfoHeaderValue("CreationTime", Uri.EscapeDataString(DateTime.UtcNow.ToString("O")))); + _userAgents.Add(new ProductInfoHeaderValue($"({hostType})")); } public string GetDirectory(WellKnownDirectory directory) From ddf41af7678a8bc585d9e6b8ab70d941cdb687b2 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Fri, 6 Sep 2024 17:04:17 -0500 Subject: [PATCH 07/13] Cleanup back-compat code for interpreting Run Service status codes (#3456) --- src/Sdk/RSWebApi/RunServiceHttpClient.cs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/Sdk/RSWebApi/RunServiceHttpClient.cs b/src/Sdk/RSWebApi/RunServiceHttpClient.cs index 14bdd2a6379..2a1b0b998ad 100644 --- a/src/Sdk/RSWebApi/RunServiceHttpClient.cs +++ b/src/Sdk/RSWebApi/RunServiceHttpClient.cs @@ -107,15 +107,6 @@ public async Task GetJobMessageAsync( } } - // Temporary back compat - switch (result.StatusCode) - { - case HttpStatusCode.NotFound: - throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}"); - case HttpStatusCode.Conflict: - throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired: {messageId}"); - } - if (!string.IsNullOrEmpty(result.ErrorBody)) { throw new Exception($"Failed to get job message: {result.Error}. {Truncate(result.ErrorBody)}"); @@ -171,13 +162,6 @@ public async Task CompleteJobAsync( } } - // Temporary back compat - switch (result.StatusCode) - { - case HttpStatusCode.NotFound: - throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}"); - } - if (!string.IsNullOrEmpty(result.ErrorBody)) { throw new Exception($"Failed to complete job: {result.Error}. {Truncate(result.ErrorBody)}"); @@ -225,13 +209,6 @@ public async Task RenewJobAsync( } } - // Temporary back compat - switch (result.StatusCode) - { - case HttpStatusCode.NotFound: - throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}"); - } - if (!string.IsNullOrEmpty(result.ErrorBody)) { throw new Exception($"Failed to renew job: {result.Error}. {Truncate(result.ErrorBody)}"); From 6d7446a45ebc638a842895d5742d6cf9afa3b66d Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Wed, 25 Sep 2024 09:01:53 -0400 Subject: [PATCH 08/13] fix missing default user-agent for jitconfig runner. (#3473) --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 4 ++-- src/Runner.Common/HostContext.cs | 20 ++++++++++++++------ src/Runner.Listener/Runner.cs | 4 ++++ src/Test/L0/TestHostContext.cs | 5 +++++ 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7eabfb9cfe2..c041cf336a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,7 +75,7 @@ jobs: # Upload runner package tar.gz/zip as artifact - name: Publish Artifact if: github.event_name != 'pull_request' - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: runner-package-${{ matrix.runtime }} path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de94bcc4b26..73dc6477759 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,7 +120,7 @@ jobs: # Since each package name is unique, so we don't need to put ${{matrix}} info into artifact name - name: Publish Artifact if: github.event_name != 'pull_request' - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: runner-packages path: | @@ -135,7 +135,7 @@ jobs: # Download runner package tar.gz/zip produced by 'build' job - name: Download Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: runner-packages path: ./ diff --git a/src/Runner.Common/HostContext.cs b/src/Runner.Common/HostContext.cs index 46ea7042d8b..0b2ae0ae979 100644 --- a/src/Runner.Common/HostContext.cs +++ b/src/Runner.Common/HostContext.cs @@ -36,6 +36,7 @@ public interface IHostContext : IDisposable event EventHandler Unloading; void ShutdownRunner(ShutdownReason reason); void WritePerfCounter(string counter); + void LoadDefaultUserAgents(); } public enum StartupType @@ -67,6 +68,7 @@ public sealed class HostContext : EventListener, IObserver, private StartupType _startupType; private string _perfFile; private RunnerWebProxy _webProxy = new(); + private string _hostType = string.Empty; public event EventHandler Unloading; public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token; @@ -78,6 +80,7 @@ public HostContext(string hostType, string logFile = null) { // Validate args. ArgUtil.NotNullOrEmpty(hostType, nameof(hostType)); + _hostType = hostType; _loadContext = AssemblyLoadContext.GetLoadContext(typeof(HostContext).GetTypeInfo().Assembly); _loadContext.Unloading += LoadContext_Unloading; @@ -196,6 +199,16 @@ public HostContext(string hostType, string logFile = null) } } + if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY"))) + { + _trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable."); + } + + LoadDefaultUserAgents(); + } + + public void LoadDefaultUserAgents() + { if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress)) { _trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)"); @@ -205,11 +218,6 @@ public HostContext(string hostType, string logFile = null) _userAgents.Add(new ProductInfoHeaderValue("HttpProxyConfigured", bool.TrueString)); } - if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY"))) - { - _trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable."); - } - var credFile = GetConfigFile(WellKnownConfigFile.Credentials); if (File.Exists(credFile)) { @@ -248,7 +256,7 @@ public HostContext(string hostType, string logFile = null) var currentProcess = Process.GetCurrentProcess(); _userAgents.Add(new ProductInfoHeaderValue("Pid", currentProcess.Id.ToString())); _userAgents.Add(new ProductInfoHeaderValue("CreationTime", Uri.EscapeDataString(DateTime.UtcNow.ToString("O")))); - _userAgents.Add(new ProductInfoHeaderValue($"({hostType})")); + _userAgents.Add(new ProductInfoHeaderValue($"({_hostType})")); } public string GetDirectory(WellKnownDirectory directory) diff --git a/src/Runner.Listener/Runner.cs b/src/Runner.Listener/Runner.cs index e2f638522b8..8649af7354f 100644 --- a/src/Runner.Listener/Runner.cs +++ b/src/Runner.Listener/Runner.cs @@ -237,6 +237,10 @@ public async Task ExecuteCommand(CommandSettings command) File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden); Trace.Info($"Saved {configContent.Length} bytes to '{configFile}'."); } + + // make sure we have the right user agent data added from the jitconfig + HostContext.LoadDefaultUserAgents(); + VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy); } catch (Exception ex) { diff --git a/src/Test/L0/TestHostContext.cs b/src/Test/L0/TestHostContext.cs index c44f13f1c4a..124bcd5bd06 100644 --- a/src/Test/L0/TestHostContext.cs +++ b/src/Test/L0/TestHostContext.cs @@ -370,6 +370,11 @@ private void LoadContext_Unloading(AssemblyLoadContext obj) Unloading(this, null); } } + + public void LoadDefaultUserAgents() + { + return; + } } public class DelayEventArgs : EventArgs From 3696b7d89fd8d900973152a7e5c4b505ec6789d9 Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Mon, 30 Sep 2024 10:57:08 -0400 Subject: [PATCH 09/13] Create launch httpclient using the right handler and setting. (#3476) --- src/Runner.Common/LaunchServer.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Runner.Common/LaunchServer.cs b/src/Runner.Common/LaunchServer.cs index e1b1b0f4f7c..f8584ac5341 100644 --- a/src/Runner.Common/LaunchServer.cs +++ b/src/Runner.Common/LaunchServer.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Sdk; +using GitHub.Services.Common; using GitHub.Services.Launch.Client; -using GitHub.Services.WebApi; namespace GitHub.Runner.Common { @@ -23,8 +24,21 @@ public sealed class LaunchServer : RunnerService, ILaunchServer public void InitializeLaunchClient(Uri uri, string token) { - var httpMessageHandler = HostContext.CreateHttpClientHandler(); - this._launchClient = new LaunchHttpClient(uri, httpMessageHandler, token, disposeHandler: true); + // Using default 100 timeout + RawClientHttpRequestSettings settings = VssUtil.GetHttpRequestSettings(null); + + // Create retry handler + IEnumerable delegatingHandlers = new List(); + if (settings.MaxRetryRequest > 0) + { + delegatingHandlers = new DelegatingHandler[] { new VssHttpRetryMessageHandler(settings.MaxRetryRequest) }; + } + + // Setup RawHttpMessageHandler without credentials + var httpMessageHandler = new RawHttpMessageHandler(new NoOpCredentials(null), settings); + var pipeline = HttpClientFactory.CreatePipeline(httpMessageHandler, delegatingHandlers); + + this._launchClient = new LaunchHttpClient(uri, pipeline, token, disposeHandler: true); } public Task ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, From e292ec220ea757e673106ed013cec2e3ac8f8665 Mon Sep 17 00:00:00 2001 From: Raj R <58966550+rajrku@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:04:48 -0700 Subject: [PATCH 10/13] Adding Snapshot additional mapping tokens (#3468) * Adding Snapshot additional mapping tokens * Lint failure fixes * Lint failure fixes - 2 * Lint failure fixes - 3 * Fixed a few nits * Lint fixes * Removed unncessary white space --- src/Runner.Worker/JobExtension.cs | 2 +- .../PipelineTemplateConstants.cs | 1 + .../PipelineTemplateConverter.cs | 34 +++++++++++++- src/Sdk/DTPipelines/Pipelines/Snapshot.cs | 12 ++++- src/Sdk/DTPipelines/workflow-v1.0.json | 17 +++++++ src/Test/L0/Worker/JobExtensionL0.cs | 46 ++++++++++++++++--- 6 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index a36e4beb4d8..e111a77aac5 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -403,7 +403,7 @@ public async Task> InitializeJob(IExecutionContext jobContext, Pipel var snapshotOperationProvider = HostContext.GetService(); jobContext.RegisterPostJobStep(new JobExtensionRunner( runAsync: (executionContext, _) => snapshotOperationProvider.CreateSnapshotRequestAsync(executionContext, snapshotRequest), - condition: $"{PipelineTemplateConstants.Success}()", + condition: snapshotRequest.Condition, displayName: $"Create custom image", data: null)); } diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs index a7e90fce334..8d81c7d2d53 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs @@ -30,6 +30,7 @@ public sealed class PipelineTemplateConstants public const String If = "if"; public const String Image = "image"; public const String ImageName = "image-name"; + public const String CustomImageVersion = "version"; public const String Include = "include"; public const String Inputs = "inputs"; public const String Job = "job"; diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs index 9d2c0bdca7a..40f6a13345f 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.Linq; using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2.Sdk; @@ -349,6 +350,10 @@ internal static List> ConvertToJobServiceCont internal static Snapshot ConvertToJobSnapshotRequest(TemplateContext context, TemplateToken token) { string imageName = null; + string version = "1.*"; + string versionString = string.Empty; + var condition = $"{PipelineTemplateConstants.Success}()"; + if (token is StringToken snapshotStringLiteral) { imageName = snapshotStringLiteral.Value; @@ -359,11 +364,19 @@ internal static Snapshot ConvertToJobSnapshotRequest(TemplateContext context, Te foreach (var snapshotPropertyPair in snapshotMapping) { var propertyName = snapshotPropertyPair.Key.AssertString($"{PipelineTemplateConstants.Snapshot} key"); + var propertyValue = snapshotPropertyPair.Value; switch (propertyName.Value) { case PipelineTemplateConstants.ImageName: imageName = snapshotPropertyPair.Value.AssertString($"{PipelineTemplateConstants.Snapshot} {propertyName}").Value; break; + case PipelineTemplateConstants.If: + condition = ConvertToIfCondition(context, propertyValue, false); + break; + case PipelineTemplateConstants.CustomImageVersion: + versionString = propertyValue.AssertString($"job {PipelineTemplateConstants.Snapshot} {PipelineTemplateConstants.CustomImageVersion}").Value; + version = IsSnapshotImageVersionValid(versionString) ? versionString : null; + break; default: propertyName.AssertUnexpectedValue($"{PipelineTemplateConstants.Snapshot} key"); break; @@ -376,7 +389,26 @@ internal static Snapshot ConvertToJobSnapshotRequest(TemplateContext context, Te return null; } - return new Snapshot(imageName); + return new Snapshot(imageName) + { + Condition = condition, + Version = version + }; + } + + private static bool IsSnapshotImageVersionValid(string versionString) + { + var versionSegments = versionString.Split("."); + + if (versionSegments.Length != 2 || + !versionSegments[1].Equals("*") || + !Int32.TryParse(versionSegments[0], NumberStyles.None, CultureInfo.InvariantCulture, result: out int parsedMajor) || + parsedMajor < 0) + { + return false; + } + + return true; } private static ActionStep ConvertToStep( diff --git a/src/Sdk/DTPipelines/Pipelines/Snapshot.cs b/src/Sdk/DTPipelines/Pipelines/Snapshot.cs index 60f8da04f4f..c1a05674aea 100644 --- a/src/Sdk/DTPipelines/Pipelines/Snapshot.cs +++ b/src/Sdk/DTPipelines/Pipelines/Snapshot.cs @@ -1,17 +1,27 @@ using System; using System.Runtime.Serialization; +using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Pipelines.ObjectTemplating; namespace GitHub.DistributedTask.Pipelines { [DataContract] public class Snapshot { - public Snapshot(string imageName) + public Snapshot(string imageName, string condition = null, string version = null) { ImageName = imageName; + Condition = condition ?? $"{PipelineTemplateConstants.Success}()"; + Version = version ?? "1.*"; } [DataMember(EmitDefaultValue = false)] public String ImageName { get; set; } + + [DataMember(EmitDefaultValue = false)] + public String Condition { get; set; } + + [DataMember(EmitDefaultValue = false)] + public String Version { get; set; } } } diff --git a/src/Sdk/DTPipelines/workflow-v1.0.json b/src/Sdk/DTPipelines/workflow-v1.0.json index a3837edff02..ec09cfe58b7 100644 --- a/src/Sdk/DTPipelines/workflow-v1.0.json +++ b/src/Sdk/DTPipelines/workflow-v1.0.json @@ -169,11 +169,28 @@ "image-name": { "type": "non-empty-string", "required": true + }, + "if": "snapshot-if", + "version": { + "type": "non-empty-string", + "required": false } } } }, + "snapshot-if": { + "context": [ + "github", + "inputs", + "vars", + "needs", + "strategy", + "matrix" + ], + "string": {} + }, + "runs-on": { "context": [ "github", diff --git a/src/Test/L0/Worker/JobExtensionL0.cs b/src/Test/L0/Worker/JobExtensionL0.cs index 4f5834fbe69..5db32d47a30 100644 --- a/src/Test/L0/Worker/JobExtensionL0.cs +++ b/src/Test/L0/Worker/JobExtensionL0.cs @@ -506,7 +506,27 @@ public Task EnsureSnapshotPostJobStepForMappingToken() return EnsureSnapshotPostJobStepForToken(mappingToken, snapshot); } - private async Task EnsureSnapshotPostJobStepForToken(TemplateToken snapshotToken, Pipelines.Snapshot expectedSnapshot) + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public Task EnsureSnapshotPostJobStepForMappingToken_WithIf_Is_False() + { + var snapshot = new Pipelines.Snapshot("TestImageNameFromMappingToken", condition: $"{PipelineTemplateConstants.Success}() && 1==0", version: "2.*"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + var condition = new StringToken(null, null, null, snapshot.Condition); + var version = new StringToken(null, null, null, snapshot.Version); + + var mappingToken = new MappingToken(null, null, null) + { + { new StringToken(null,null,null, PipelineTemplateConstants.ImageName), imageNameValueStringToken }, + { new StringToken(null,null,null, PipelineTemplateConstants.If), condition }, + { new StringToken(null,null,null, PipelineTemplateConstants.CustomImageVersion), version } + }; + + return EnsureSnapshotPostJobStepForToken(mappingToken, snapshot, skipSnapshotStep: true); + } + + private async Task EnsureSnapshotPostJobStepForToken(TemplateToken snapshotToken, Pipelines.Snapshot expectedSnapshot, bool skipSnapshotStep = false) { using (TestHostContext hc = CreateTestContext()) { @@ -524,14 +544,28 @@ private async Task EnsureSnapshotPostJobStepForToken(TemplateToken snapshotToken Assert.Equal(1, postJobSteps.Count); var snapshotStep = postJobSteps.First(); + _jobEc.JobSteps.Enqueue(snapshotStep); + + var _stepsRunner = new StepsRunner(); + _stepsRunner.Initialize(hc); + await _stepsRunner.RunAsync(_jobEc); + Assert.Equal("Create custom image", snapshotStep.DisplayName); - Assert.Equal($"{PipelineTemplateConstants.Success}()", snapshotStep.Condition); + Assert.Equal(expectedSnapshot.Condition ?? $"{PipelineTemplateConstants.Success}()", snapshotStep.Condition); // Run the mock snapshot step, so we can verify it was executed with the expected snapshot object. - await snapshotStep.RunAsync(); - - Assert.NotNull(_requestedSnapshot); - Assert.Equal(expectedSnapshot.ImageName, _requestedSnapshot.ImageName); + // await snapshotStep.RunAsync(); + if (skipSnapshotStep) + { + Assert.Null(_requestedSnapshot); + } + else + { + Assert.NotNull(_requestedSnapshot); + Assert.Equal(expectedSnapshot.ImageName, _requestedSnapshot.ImageName); + Assert.Equal(expectedSnapshot.Condition ?? $"{PipelineTemplateConstants.Success}()", _requestedSnapshot.Condition); + Assert.Equal(expectedSnapshot.Version ?? "1.*", _requestedSnapshot.Version); + } } } } From 149123c2322b61d9a497b49cdce6784b23ebfb5d Mon Sep 17 00:00:00 2001 From: eric sciple Date: Thu, 3 Oct 2024 13:38:35 -0500 Subject: [PATCH 11/13] Prepare v2.320.0 (#3484) --- releaseNote.md | 17 ++++++++++++----- src/runnerversion | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/releaseNote.md b/releaseNote.md index 9753b6d2311..70d85af2dcd 100644 --- a/releaseNote.md +++ b/releaseNote.md @@ -1,10 +1,17 @@ ## What's Changed -- .NET 8 OS compatibility test https://github.com/actions/runner/pull/3422 -- Ignore ssl cert on websocket client https://github.com/actions/runner/pull/3423 -- Revert "Bump runner to dotnet 8" https://github.com/actions/runner/pull/3412 - -**Full Changelog**: https://github.com/actions/runner/compare/v2.318.0...v2.319.0 +- Adding Snapshot additional mapping tokens https://github.com/actions/runner/pull/3468 +- Create launch httpclient using the right handler and setting https://github.com/actions/runner/pull/3476 +- Fix missing default user-agent for jitconfig runner https://github.com/actions/runner/pull/3473 +- Cleanup back-compat code for interpreting Run Service status codes https://github.com/actions/runner/pull/3456 +- Add runner or worker to the useragent https://github.com/actions/runner/pull/3457 +- Handle Error Body in Responses from Broker https://github.com/actions/runner/pull/3454 +- Fix issues for composite actions (Run Service flow) https://github.com/actions/runner/pull/3446 +- Trace GitHub RequestId to log https://github.com/actions/runner/pull/3442 +- Add `jq`, `git`, `unzip` and `curl` to default packages installed https://github.com/actions/runner/pull/3056 +- Add pid to user-agent and session owner https://github.com/actions/runner/pull/3432 + +**Full Changelog**: https://github.com/actions/runner/compare/v2.319.1...v2.320.0 _Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet. To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository. diff --git a/src/runnerversion b/src/runnerversion index 4c47f6f2f86..8084ad3118b 100644 --- a/src/runnerversion +++ b/src/runnerversion @@ -1 +1 @@ -2.319.1 +2.320.0 From 4bf0e1bdb037a027720ef32c47e1c05bf9152b6a Mon Sep 17 00:00:00 2001 From: eric sciple Date: Thu, 3 Oct 2024 11:53:21 -0700 Subject: [PATCH 12/13] v2.320.0 release --- releaseVersion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releaseVersion b/releaseVersion index ef96e25e847..8084ad3118b 100644 --- a/releaseVersion +++ b/releaseVersion @@ -1 +1 @@ - +2.320.0 From 78f21670b4a58aff19b5189207f54eb2bee324d1 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Thu, 3 Oct 2024 12:20:00 -0700 Subject: [PATCH 13/13] Fix release workflow to use distinct artifact names --- .github/workflows/release.yml | 37 +++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73dc6477759..f4571b6cb8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,12 +117,11 @@ jobs: working-directory: _package # Upload runner package tar.gz/zip as artifact. - # Since each package name is unique, so we don't need to put ${{matrix}} info into artifact name - name: Publish Artifact if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: - name: runner-packages + name: runner-packages-${{ matrix.runtime }} path: | _package @@ -134,10 +133,40 @@ jobs: - uses: actions/checkout@v3 # Download runner package tar.gz/zip produced by 'build' job - - name: Download Artifact + - name: Download Artifact (win-x64) uses: actions/download-artifact@v4 with: - name: runner-packages + name: runner-packages-win-x64 + path: ./ + - name: Download Artifact (win-arm64) + uses: actions/download-artifact@v4 + with: + name: runner-packages-win-arm64 + path: ./ + - name: Download Artifact (osx-x64) + uses: actions/download-artifact@v4 + with: + name: runner-packages-osx-x64 + path: ./ + - name: Download Artifact (osx-arm64) + uses: actions/download-artifact@v4 + with: + name: runner-packages-osx-arm64 + path: ./ + - name: Download Artifact (linux-x64) + uses: actions/download-artifact@v4 + with: + name: runner-packages-linux-x64 + path: ./ + - name: Download Artifact (linux-arm) + uses: actions/download-artifact@v4 + with: + name: runner-packages-linux-arm + path: ./ + - name: Download Artifact (linux-arm64) + uses: actions/download-artifact@v4 + with: + name: runner-packages-linux-arm64 path: ./ # Create ReleaseNote file