diff --git a/src/Runner.Listener/Runner.cs b/src/Runner.Listener/Runner.cs index 00afd99c7..5ef0a3d64 100644 --- a/src/Runner.Listener/Runner.cs +++ b/src/Runner.Listener/Runner.cs @@ -12,6 +12,7 @@ using GitHub.Runner.Common; using GitHub.Runner.Sdk; using System.Linq; using GitHub.Runner.Listener.Check; +using System.Collections.Generic; namespace GitHub.Runner.Listener { @@ -407,6 +408,27 @@ namespace GitHub.Runner.Listener { autoUpdateInProgress = true; var runnerUpdateMessage = JsonUtility.FromString(message.Body); +#if DEBUG + // Can mock the update for testing + if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE"))) + { + + // The mock_update_messages.json file should be an object with keys being the current version and values being the targeted mock version object + // Example: { "2.283.2": {"targetVersion":"2.284.1"}, "2.284.1": {"targetVersion":"2.285.0"}} + var mockUpdatesPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), "mock_update_messages.json"); + if (File.Exists(mockUpdatesPath)) + { + var mockUpdateMessages = JsonUtility.FromString>(File.ReadAllText(mockUpdatesPath)); + if (mockUpdateMessages.ContainsKey(BuildConstants.RunnerPackage.Version)) + { + var mockTargetVersion = mockUpdateMessages[BuildConstants.RunnerPackage.Version].TargetVersion; + _term.WriteLine($"Mocking update, using version {mockTargetVersion} instead of {runnerUpdateMessage.TargetVersion}"); + Trace.Info($"Mocking update, using version {mockTargetVersion} instead of {runnerUpdateMessage.TargetVersion}"); + runnerUpdateMessage = new AgentRefreshMessage(runnerUpdateMessage.AgentId, mockTargetVersion, runnerUpdateMessage.Timeout); + } + } + } +#endif var selfUpdater = HostContext.GetService(); selfUpdateTask = selfUpdater.SelfUpdate(runnerUpdateMessage, jobDispatcher, false, HostContext.RunnerShutdownToken); Trace.Info("Refresh message received, kick-off selfupdate background process."); diff --git a/src/Runner.Listener/SelfUpdater.cs b/src/Runner.Listener/SelfUpdater.cs index f6556ac2d..776ab9689 100644 --- a/src/Runner.Listener/SelfUpdater.cs +++ b/src/Runner.Listener/SelfUpdater.cs @@ -104,7 +104,7 @@ namespace GitHub.Runner.Listener } } - await DownloadLatestRunner(token); + await DownloadLatestRunner(token, updateMessage.TargetVersion); Trace.Info($"Download latest runner and unzip into runner root."); // wait till all running job finish @@ -206,7 +206,7 @@ namespace GitHub.Runner.Listener /// /// /// - private async Task DownloadLatestRunner(CancellationToken token) + private async Task DownloadLatestRunner(CancellationToken token, string targetVersion) { string latestRunnerDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.UpdateDirectory); IOUtil.DeleteDirectory(latestRunnerDirectory, token); @@ -266,14 +266,57 @@ namespace GitHub.Runner.Listener try { - archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, token); +#if DEBUG + // Much of the update process (targetVersion, archive) is server-side, this is a way to control it from here for testing specific update scenarios + // Add files like 'runner2.281.2.tar.gz' or 'runner2.283.0.zip' (depending on your platform) to your runner root folder + // Note that runners still need to be older than the server's runner version in order to receive an 'AgentRefreshMessage' and trigger this update + // Wrapped in #if DEBUG as this should not be in the RELEASE build + if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE"))) + { + var waitForDebugger = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE_WAIT_FOR_DEBUGGER")); + if (waitForDebugger) + { + int waitInSeconds = 20; + while (!Debugger.IsAttached && waitInSeconds-- > 0) + { + await Task.Delay(1000); + } + Debugger.Break(); + } + if (_targetPackage.Platform.StartsWith("win")) + { + archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.zip"); + } + else + { + archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.tar.gz"); + } + + if (File.Exists(archiveFile)) + { + _updateTrace.Enqueue($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded"); + _terminal.WriteLine($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded"); + } + else + { + archiveFile = null; + _terminal.WriteLine($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead"); + _updateTrace.Enqueue($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead"); + } + } +#endif + // archiveFile is not null only if we mocked it above if (string.IsNullOrEmpty(archiveFile)) { - throw new TaskCanceledException($"Runner package '{packageDownloadUrl}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts"); - } + archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, token); - await ValidateRunnerHash(archiveFile, packageHashValue); + if (string.IsNullOrEmpty(archiveFile)) + { + throw new TaskCanceledException($"Runner package '{packageDownloadUrl}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts"); + } + await ValidateRunnerHash(archiveFile, packageHashValue); + } await ExtractRunnerPackage(archiveFile, latestRunnerDirectory, token); } diff --git a/src/Test/TestData/mock_update_messages.json b/src/Test/TestData/mock_update_messages.json new file mode 100644 index 000000000..6c00a9ef5 --- /dev/null +++ b/src/Test/TestData/mock_update_messages.json @@ -0,0 +1 @@ +{ "2.283.2": {"targetVersion":"2.284.1"}, "2.284.1": {"targetVersion":"2.285.0"}}