mirror of
https://github.com/actions/runner.git
synced 2025-12-11 12:57:05 +00:00
Compare commits
10 Commits
users/tihu
...
v2.286.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4a2c808eb | ||
|
|
86122a034a | ||
|
|
f1ddeb0d06 | ||
|
|
9bfbc48f45 | ||
|
|
ead1826afb | ||
|
|
9de17f197c | ||
|
|
45decac397 | ||
|
|
55ed60b9fc | ||
|
|
698d3a2e66 | ||
|
|
d0ab54ce45 |
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,12 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: 🛑 Report a bug in the runner application
|
||||||
about: Create a report to help us improve
|
about: If you have issues with GitHub Actions, please follow the "support for GitHub Actions" link, below.
|
||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
👋 You're opening a bug report against the GitHub Actions **runner application**.
|
||||||
|
|
||||||
|
🛑 Please stop if you're not certain that the bug you're seeing is in the runner application - if you have general problems with actions, workflows, or runners, please see the [GitHub Community Support Forum](https://github.community/c/code-to-cloud/52) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃
|
||||||
|
-->
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
|||||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: ✅ Support for GitHub Actions
|
||||||
|
url: https://github.community/c/code-to-cloud/52
|
||||||
|
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
|
||||||
|
- name: ✅ Feedback and suggestions for GitHub Actions
|
||||||
|
url: https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback
|
||||||
|
about: If you have feedback or suggestions about GitHub Actions, please open a discussion (or add to an existing one) in the GitHub Actions Feedback. GitHub Actions Product Managers and Engineers monitor the feedback forum.
|
||||||
|
- name: ‼️ GitHub Security Bug Bounty
|
||||||
|
url: https://bounty.github.com/
|
||||||
|
about: Please report security vulnerabilities here.
|
||||||
13
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
13
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
@@ -1,19 +1,24 @@
|
|||||||
---
|
---
|
||||||
name: Feature Request
|
name: 🛑 Request a feature in the runner application
|
||||||
about: Create a request to help us improve
|
about: If you have feature requests for GitHub Actions, please use the "feedback and suggestions for GitHub Actions" link below.
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Thank you 🙇♀ for wanting to create a feature in this repository. Before you do, please ensure you are filing the issue in the right place. Issues should only be opened on if the issue **relates to code in this repository**.
|
<!--
|
||||||
|
👋 You're opening a request for an enhancement in the GitHub Actions **runner application**.
|
||||||
|
|
||||||
|
🛑 Please stop if you're not certain that the feature you want is in the runner application - if you have a suggestion for improving GitHub Actions, please see the [GitHub Actions Feedback](https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback) discussion forum which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃
|
||||||
|
|
||||||
|
Some additional useful links:
|
||||||
* If you have found a security issue [please submit it here](https://hackerone.com/github)
|
* If you have found a security issue [please submit it here](https://hackerone.com/github)
|
||||||
* If you have questions or issues with the service, writing workflows or actions, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
|
* If you have questions or issues with the service, writing workflows or actions, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
|
||||||
* If you are having an issue or question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
|
* If you are having an issue or have a question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
|
||||||
|
|
||||||
If you have a feature request that is relevant to this repository, the runner, then please include the information below:
|
If you have a feature request that is relevant to this repository, the runner, then please include the information below:
|
||||||
|
-->
|
||||||
|
|
||||||
**Describe the enhancement**
|
**Describe the enhancement**
|
||||||
A clear and concise description of what the features or enhancement you need.
|
A clear and concise description of what the features or enhancement you need.
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
devScript: ./dev.sh
|
devScript: ./dev.sh
|
||||||
|
|
||||||
- runtime: win-x64
|
- runtime: win-x64
|
||||||
os: windows-latest
|
os: windows-2019
|
||||||
devScript: ./dev
|
devScript: ./dev
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|||||||
5
.github/workflows/codeql.yml
vendored
5
.github/workflows/codeql.yml
vendored
@@ -1,7 +1,12 @@
|
|||||||
name: "Code Scanning - Action"
|
name: "Code Scanning - Action"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0'
|
- cron: '0 0 * * 0'
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
|||||||
devScript: ./dev.sh
|
devScript: ./dev.sh
|
||||||
|
|
||||||
- runtime: win-x64
|
- runtime: win-x64
|
||||||
os: windows-latest
|
os: windows-2019
|
||||||
devScript: ./dev
|
devScript: ./dev
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 138 KiB |
@@ -1,16 +1,14 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- n/a
|
- N/A
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Revert node12 version due to fs.copyFileSync hang #1537
|
- Fix breaking change in dotnet 6 around globalization-invariant. (#1609)
|
||||||
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
- n/a
|
- N/A
|
||||||
|
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<Update to ./src/runnerversion when creating release>
|
2.286.1
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<NoWarn>NU1701;NU1603</NoWarn>
|
<NoWarn>NU1701;NU1603</NoWarn>
|
||||||
<Version>$(Version)</Version>
|
<Version>$(Version)</Version>
|
||||||
|
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ using GitHub.Services.Common;
|
|||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -34,6 +36,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private int _poolId;
|
private int _poolId;
|
||||||
private int _agentId;
|
private int _agentId;
|
||||||
|
private readonly List<string> _updateTrace = new List<string>();
|
||||||
|
|
||||||
public bool Busy { get; private set; }
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
@@ -54,6 +57,8 @@ namespace GitHub.Runner.Listener
|
|||||||
Busy = true;
|
Busy = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var totalUpdateTime = Stopwatch.StartNew();
|
||||||
|
|
||||||
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
|
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
|
||||||
{
|
{
|
||||||
Trace.Info($"Can't find available update package.");
|
Trace.Info($"Can't find available update package.");
|
||||||
@@ -61,16 +66,17 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"An update is available.");
|
Trace.Info($"An update is available.");
|
||||||
|
_updateTrace.Add($"RunnerPlatform: {_targetPackage.Platform}");
|
||||||
|
|
||||||
// Print console line that warn user not shutdown runner.
|
// Print console line that warn user not shutdown runner.
|
||||||
await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner.");
|
await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner.");
|
||||||
await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner", $"RunnerPlatform: {_targetPackage.Platform}");
|
await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner");
|
||||||
|
|
||||||
var downloadTrace = await DownloadLatestRunner(token);
|
await DownloadLatestRunner(token);
|
||||||
Trace.Info($"Download latest runner and unzip into runner root.");
|
Trace.Info($"Download latest runner and unzip into runner root.");
|
||||||
|
|
||||||
// wait till all running job finish
|
// wait till all running job finish
|
||||||
await UpdateRunnerUpdateStateAsync("Waiting for current job finish running.", downloadTrace);
|
await UpdateRunnerUpdateStateAsync("Waiting for current job finish running.");
|
||||||
|
|
||||||
await jobDispatcher.WaitAsync(token);
|
await jobDispatcher.WaitAsync(token);
|
||||||
Trace.Info($"All running job has exited.");
|
Trace.Info($"All running job has exited.");
|
||||||
@@ -82,7 +88,8 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"Delete old version runner backup.");
|
Trace.Info($"Delete old version runner backup.");
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
// generate update script from template
|
// generate update script from template
|
||||||
await UpdateRunnerUpdateStateAsync("Generate and execute update script.", $"DeleteRunnerBackupTime: {stopWatch.ElapsedMilliseconds}ms");
|
_updateTrace.Add($"DeleteRunnerBackupTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
|
||||||
|
|
||||||
string updateScript = GenerateUpdateScript(restartInteractiveRunner);
|
string updateScript = GenerateUpdateScript(restartInteractiveRunner);
|
||||||
Trace.Info($"Generate update script into: {updateScript}");
|
Trace.Info($"Generate update script into: {updateScript}");
|
||||||
@@ -99,12 +106,21 @@ namespace GitHub.Runner.Listener
|
|||||||
invokeScript.Start();
|
invokeScript.Start();
|
||||||
Trace.Info($"Update script start running");
|
Trace.Info($"Update script start running");
|
||||||
|
|
||||||
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should be back online within 10 seconds.", $"RestartInteractiveRunner: {restartInteractiveRunner}");
|
totalUpdateTime.Stop();
|
||||||
|
|
||||||
|
_updateTrace.Add($"TotalUpdateTime: {totalUpdateTime.ElapsedMilliseconds}ms");
|
||||||
|
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should be back online within 10 seconds.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_updateTrace.Add(ex.ToString());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
await UpdateRunnerUpdateStateAsync("Runner update process finished.");
|
||||||
Busy = false;
|
Busy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,189 +169,29 @@ namespace GitHub.Runner.Listener
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task<string> DownloadLatestRunner(CancellationToken token)
|
private async Task DownloadLatestRunner(CancellationToken token)
|
||||||
{
|
{
|
||||||
var traceStringBuilder = new StringBuilder();
|
|
||||||
traceStringBuilder.AppendLine($"DownloadUrl: {_targetPackage.DownloadUrl}");
|
|
||||||
string latestRunnerDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.UpdateDirectory);
|
string latestRunnerDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.UpdateDirectory);
|
||||||
IOUtil.DeleteDirectory(latestRunnerDirectory, token);
|
IOUtil.DeleteDirectory(latestRunnerDirectory, token);
|
||||||
Directory.CreateDirectory(latestRunnerDirectory);
|
Directory.CreateDirectory(latestRunnerDirectory);
|
||||||
|
|
||||||
int runnerSuffix = 1;
|
|
||||||
string archiveFile = null;
|
string archiveFile = null;
|
||||||
bool downloadSucceeded = false;
|
var packageDownloadUrl = _targetPackage.DownloadUrl;
|
||||||
|
var packageHashValue = _targetPackage.HashValue;
|
||||||
|
_updateTrace.Add($"DownloadUrl: {packageDownloadUrl}");
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Download the runner, using multiple attempts in order to be resilient against any networking/CDN issues
|
archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, token);
|
||||||
for (int attempt = 1; attempt <= Constants.RunnerDownloadRetryMaxAttempts; attempt++)
|
|
||||||
|
if (string.IsNullOrEmpty(archiveFile))
|
||||||
{
|
{
|
||||||
// Generate an available package name, and do our best effort to clean up stale local zip files
|
throw new TaskCanceledException($"Runner package '{packageDownloadUrl}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts");
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (_targetPackage.Platform.StartsWith("win"))
|
|
||||||
{
|
|
||||||
archiveFile = Path.Combine(latestRunnerDirectory, $"runner{runnerSuffix}.zip");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
archiveFile = Path.Combine(latestRunnerDirectory, $"runner{runnerSuffix}.tar.gz");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// delete .zip file
|
|
||||||
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
|
|
||||||
{
|
|
||||||
Trace.Verbose("Deleting latest runner package zip '{0}'", archiveFile);
|
|
||||||
IOUtil.DeleteFile(archiveFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// couldn't delete the file for whatever reason, so generate another name
|
|
||||||
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
|
|
||||||
runnerSuffix++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow a 15-minute package download timeout, which is good enough to update the runner from a 1 Mbit/s ADSL connection.
|
|
||||||
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_DOWNLOAD_TIMEOUT") ?? string.Empty, out int timeoutSeconds))
|
|
||||||
{
|
|
||||||
timeoutSeconds = 15 * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Attempt {attempt}: save latest runner into {archiveFile}.");
|
|
||||||
|
|
||||||
using (var downloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
|
||||||
using (var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(downloadTimeout.Token, token))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Trace.Info($"Download runner: begin download");
|
|
||||||
long downloadSize = 0;
|
|
||||||
|
|
||||||
//open zip stream in async mode
|
|
||||||
using (HttpClient httpClient = new HttpClient(HostContext.CreateHttpClientHandler()))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_targetPackage.Token))
|
|
||||||
{
|
|
||||||
Trace.Info($"Adding authorization token ({_targetPackage.Token.Length} chars)");
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _targetPackage.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Downloading {_targetPackage.DownloadUrl}");
|
|
||||||
|
|
||||||
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
|
||||||
using (Stream result = await httpClient.GetStreamAsync(_targetPackage.DownloadUrl))
|
|
||||||
{
|
|
||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
|
||||||
await result.CopyToAsync(fs, 81920, downloadCts.Token);
|
|
||||||
await fs.FlushAsync(downloadCts.Token);
|
|
||||||
downloadSize = fs.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Download runner: finished download");
|
|
||||||
downloadSucceeded = true;
|
|
||||||
stopWatch.Stop();
|
|
||||||
traceStringBuilder.AppendLine($"PackageDownloadTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
traceStringBuilder.AppendLine($"Attempts: {attempt}");
|
|
||||||
traceStringBuilder.AppendLine($"PackageSize: {downloadSize / 1024 / 1024}MB");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Info($"Runner download has been canceled.");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (downloadCts.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Warning($"Runner download has timed out after {timeoutSeconds} seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Warning($"Failed to get package '{archiveFile}' from '{_targetPackage.DownloadUrl}'. Exception {ex}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!downloadSucceeded)
|
await ValidateRunnerHash(archiveFile, packageHashValue);
|
||||||
{
|
|
||||||
throw new TaskCanceledException($"Runner package '{archiveFile}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts");
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatch.Restart();
|
await ExtractRunnerPackage(archiveFile, latestRunnerDirectory, token);
|
||||||
// If we got this far, we know that we've successfully downloaded the runner package
|
|
||||||
// Validate Hash Matches if it is provided
|
|
||||||
using (FileStream stream = File.OpenRead(archiveFile))
|
|
||||||
{
|
|
||||||
if (!String.IsNullOrEmpty(_targetPackage.HashValue))
|
|
||||||
{
|
|
||||||
using (SHA256 sha256 = SHA256.Create())
|
|
||||||
{
|
|
||||||
byte[] srcHashBytes = await sha256.ComputeHashAsync(stream);
|
|
||||||
var hash = PrimitiveExtensions.ConvertToHexString(srcHashBytes);
|
|
||||||
if (hash != _targetPackage.HashValue)
|
|
||||||
{
|
|
||||||
// Hash did not match, we can't recover from this, just throw
|
|
||||||
throw new Exception($"Computed runner hash {hash} did not match expected Runner Hash {_targetPackage.HashValue} for {_targetPackage.Filename}");
|
|
||||||
}
|
|
||||||
Trace.Info($"Validated Runner Hash matches {_targetPackage.Filename} : {_targetPackage.HashValue}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (archiveFile.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ZipFile.ExtractToDirectory(archiveFile, latestRunnerDirectory);
|
|
||||||
}
|
|
||||||
else if (archiveFile.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
string tar = WhichUtil.Which("tar", trace: Trace);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(tar))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"tar -xzf");
|
|
||||||
}
|
|
||||||
|
|
||||||
// tar -xzf
|
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
|
||||||
{
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
Trace.Info(args.Data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
Trace.Error(args.Data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int exitCode = await processInvoker.ExecuteAsync(latestRunnerDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
|
|
||||||
if (exitCode != 0)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. return code: {exitCode}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"{archiveFile}");
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatch.Stop();
|
|
||||||
Trace.Info($"Finished getting latest runner package at: {latestRunnerDirectory}.");
|
|
||||||
traceStringBuilder.AppendLine($"PackageExtractTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -355,7 +211,204 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopWatch.Restart();
|
await CopyLatestRunnerToRoot(latestRunnerDirectory, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> DownLoadRunner(string downloadDirectory, string packageDownloadUrl, string packageHashValue, CancellationToken token)
|
||||||
|
{
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
int runnerSuffix = 1;
|
||||||
|
string archiveFile = null;
|
||||||
|
bool downloadSucceeded = false;
|
||||||
|
|
||||||
|
// Download the runner, using multiple attempts in order to be resilient against any networking/CDN issues
|
||||||
|
for (int attempt = 1; attempt <= Constants.RunnerDownloadRetryMaxAttempts; attempt++)
|
||||||
|
{
|
||||||
|
// Generate an available package name, and do our best effort to clean up stale local zip files
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (_targetPackage.Platform.StartsWith("win"))
|
||||||
|
{
|
||||||
|
archiveFile = Path.Combine(downloadDirectory, $"runner{runnerSuffix}.zip");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
archiveFile = Path.Combine(downloadDirectory, $"runner{runnerSuffix}.tar.gz");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// delete .zip file
|
||||||
|
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
|
||||||
|
{
|
||||||
|
Trace.Verbose("Deleting latest runner package zip '{0}'", archiveFile);
|
||||||
|
IOUtil.DeleteFile(archiveFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// couldn't delete the file for whatever reason, so generate another name
|
||||||
|
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
|
||||||
|
runnerSuffix++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow a 15-minute package download timeout, which is good enough to update the runner from a 1 Mbit/s ADSL connection.
|
||||||
|
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_DOWNLOAD_TIMEOUT") ?? string.Empty, out int timeoutSeconds))
|
||||||
|
{
|
||||||
|
timeoutSeconds = 15 * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Attempt {attempt}: save latest runner into {archiveFile}.");
|
||||||
|
|
||||||
|
using (var downloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
||||||
|
using (var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(downloadTimeout.Token, token))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Trace.Info($"Download runner: begin download");
|
||||||
|
long downloadSize = 0;
|
||||||
|
|
||||||
|
//open zip stream in async mode
|
||||||
|
using (HttpClient httpClient = new HttpClient(HostContext.CreateHttpClientHandler()))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_targetPackage.Token))
|
||||||
|
{
|
||||||
|
Trace.Info($"Adding authorization token ({_targetPackage.Token.Length} chars)");
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _targetPackage.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Downloading {packageDownloadUrl}");
|
||||||
|
|
||||||
|
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
||||||
|
using (Stream result = await httpClient.GetStreamAsync(packageDownloadUrl))
|
||||||
|
{
|
||||||
|
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||||
|
await result.CopyToAsync(fs, 81920, downloadCts.Token);
|
||||||
|
await fs.FlushAsync(downloadCts.Token);
|
||||||
|
downloadSize = fs.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Download runner: finished download");
|
||||||
|
downloadSucceeded = true;
|
||||||
|
stopWatch.Stop();
|
||||||
|
_updateTrace.Add($"PackageDownloadTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
_updateTrace.Add($"Attempts: {attempt}");
|
||||||
|
_updateTrace.Add($"PackageSize: {downloadSize / 1024 / 1024}MB");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Trace.Info($"Runner download has been canceled.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (downloadCts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Trace.Warning($"Runner download has timed out after {timeoutSeconds} seconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Warning($"Failed to get package '{archiveFile}' from '{packageDownloadUrl}'. Exception {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadSucceeded)
|
||||||
|
{
|
||||||
|
return archiveFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateRunnerHash(string archiveFile, string packageHashValue)
|
||||||
|
{
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
// Validate Hash Matches if it is provided
|
||||||
|
using (FileStream stream = File.OpenRead(archiveFile))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(packageHashValue))
|
||||||
|
{
|
||||||
|
using (SHA256 sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
byte[] srcHashBytes = await sha256.ComputeHashAsync(stream);
|
||||||
|
var hash = PrimitiveExtensions.ConvertToHexString(srcHashBytes);
|
||||||
|
if (hash != packageHashValue)
|
||||||
|
{
|
||||||
|
// Hash did not match, we can't recover from this, just throw
|
||||||
|
throw new Exception($"Computed runner hash {hash} did not match expected Runner Hash {packageHashValue} for {_targetPackage.Filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
Trace.Info($"Validated Runner Hash matches {_targetPackage.Filename} : {packageHashValue}");
|
||||||
|
_updateTrace.Add($"ValidateHashTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExtractRunnerPackage(string archiveFile, string extractDirectory, CancellationToken token)
|
||||||
|
{
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
if (archiveFile.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ZipFile.ExtractToDirectory(archiveFile, extractDirectory);
|
||||||
|
}
|
||||||
|
else if (archiveFile.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string tar = WhichUtil.Which("tar", trace: Trace);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(tar))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"tar -xzf");
|
||||||
|
}
|
||||||
|
|
||||||
|
// tar -xzf
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
Trace.Info(args.Data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
Trace.Error(args.Data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int exitCode = await processInvoker.ExecuteAsync(extractDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. return code: {exitCode}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"{archiveFile}");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
Trace.Info($"Finished getting latest runner package at: {extractDirectory}.");
|
||||||
|
_updateTrace.Add($"PackageExtractTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task CopyLatestRunnerToRoot(string latestRunnerDirectory, CancellationToken token)
|
||||||
|
{
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
// copy latest runner into runner root folder
|
// copy latest runner into runner root folder
|
||||||
// copy bin from _work/_update -> bin.version under root
|
// copy bin from _work/_update -> bin.version under root
|
||||||
string binVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.BinDirectory}.{_targetPackage.Version}");
|
string binVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.BinDirectory}.{_targetPackage.Version}");
|
||||||
@@ -383,9 +436,8 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
traceStringBuilder.AppendLine($"CopyRunnerToRootTime: {stopWatch.ElapsedMilliseconds}ms");
|
_updateTrace.Add($"CopyRunnerToRootTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
return Task.CompletedTask;
|
||||||
return traceStringBuilder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeletePreviousVersionRunnerBackup(CancellationToken token)
|
private void DeletePreviousVersionRunnerBackup(CancellationToken token)
|
||||||
@@ -505,18 +557,22 @@ namespace GitHub.Runner.Listener
|
|||||||
return updateScript;
|
return updateScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateRunnerUpdateStateAsync(string currentState, string trace = "")
|
private async Task UpdateRunnerUpdateStateAsync(string currentState)
|
||||||
{
|
{
|
||||||
_terminal.WriteLine(currentState);
|
_terminal.WriteLine(currentState);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(trace))
|
if (_updateTrace.Count > 0)
|
||||||
{
|
{
|
||||||
Trace.Info(trace);
|
foreach (var trace in _updateTrace)
|
||||||
|
{
|
||||||
|
Trace.Info(trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _runnerServer.UpdateAgentUpdateStateAsync(_poolId, _agentId, currentState, trace);
|
await _runnerServer.UpdateAgentUpdateStateAsync(_poolId, _agentId, currentState, string.Join(Environment.NewLine, _updateTrace));
|
||||||
|
_updateTrace.Clear();
|
||||||
}
|
}
|
||||||
catch (VssResourceNotFoundException)
|
catch (VssResourceNotFoundException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<NoWarn>NU1701;NU1603</NoWarn>
|
<NoWarn>NU1701;NU1603</NoWarn>
|
||||||
<Version>$(Version)</Version>
|
<Version>$(Version)</Version>
|
||||||
|
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -338,6 +338,18 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(container.ContainerId))
|
if (!string.IsNullOrEmpty(container.ContainerId))
|
||||||
{
|
{
|
||||||
|
if(!container.IsJobContainer)
|
||||||
|
{
|
||||||
|
// Print logs for service container jobs (not the "action" job itself b/c that's already logged).
|
||||||
|
executionContext.Output($"Print service container logs: {container.ContainerDisplayName}");
|
||||||
|
|
||||||
|
int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
|
||||||
|
if (logsExitCode != 0)
|
||||||
|
{
|
||||||
|
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
|
executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
|
||||||
|
|
||||||
int rmExitCode = await _dockerManager.DockerRemove(executionContext, container.ContainerId);
|
int rmExitCode = await _dockerManager.DockerRemove(executionContext, container.ContainerId);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<NoWarn>NU1701;NU1603</NoWarn>
|
<NoWarn>NU1701;NU1603</NoWarn>
|
||||||
<Version>$(Version)</Version>
|
<Version>$(Version)</Version>
|
||||||
|
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
@@ -59,10 +60,21 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File ID in file service
|
||||||
|
/// </summary>
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public Int32? FileId
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Auth token to download the package
|
/// Auth token to download the package
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public String Token
|
public String Token
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -70,7 +82,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MD5 hash as a base64 string
|
/// SHA256 hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public String HashValue
|
public String HashValue
|
||||||
@@ -90,7 +102,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The UI uses this to display instructions, i.e. "unzip MyAgent.zip"
|
/// The UI uses this to display instructions, e.g. "unzip MyAgent.zip"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public String Filename
|
public String Filename
|
||||||
|
|||||||
178
src/Test/L0/Listener/SelfUpdaterL0.cs
Normal file
178
src/Test/L0/Listener/SelfUpdaterL0.cs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Listener
|
||||||
|
{
|
||||||
|
public sealed class SelfUpdaterL0
|
||||||
|
{
|
||||||
|
private Mock<IRunnerServer> _runnerServer;
|
||||||
|
private Mock<ITerminal> _term;
|
||||||
|
private Mock<IConfigurationStore> _configStore;
|
||||||
|
private Mock<IJobDispatcher> _jobDispatcher;
|
||||||
|
private AgentRefreshMessage _refreshMessage = new AgentRefreshMessage(1, "2.299.0");
|
||||||
|
|
||||||
|
#if !OS_WINDOWS
|
||||||
|
private string _packageUrl = $"https://github.com/actions/runner/releases/download/v2.285.1/actions-runner-{BuildConstants.RunnerPackage.PackageName}-2.285.1.tar.gz";
|
||||||
|
#else
|
||||||
|
private string _packageUrl = $"https://github.com/actions/runner/releases/download/v2.285.1/actions-runner-{BuildConstants.RunnerPackage.PackageName}-2.285.1.zip";
|
||||||
|
#endif
|
||||||
|
public SelfUpdaterL0()
|
||||||
|
{
|
||||||
|
_runnerServer = new Mock<IRunnerServer>();
|
||||||
|
_term = new Mock<ITerminal>();
|
||||||
|
_configStore = new Mock<IConfigurationStore>();
|
||||||
|
_jobDispatcher = new Mock<IJobDispatcher>();
|
||||||
|
_configStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1, AgentId = 1 });
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdater();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
|
var p = new ProcessInvokerWrapper();
|
||||||
|
p.Initialize(hc);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback((int p, int a, string s, string t) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(t);
|
||||||
|
})
|
||||||
|
.Returns(Task.FromResult(new TaskAgent()));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
||||||
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
||||||
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync_NoUpdateOnOldVersion()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdater();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.200.0", true, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.200.0"), DownloadUrl = _packageUrl }));
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback((int p, int a, string s, string t) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(t);
|
||||||
|
})
|
||||||
|
.Returns(Task.FromResult(new TaskAgent()));
|
||||||
|
|
||||||
|
var result = await updater.SelfUpdate(new AgentRefreshMessage(1, "2.200.0"), _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync_DownloadRetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdater();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = $"https://github.com/actions/runner/notexists" }));
|
||||||
|
|
||||||
|
var p = new ProcessInvokerWrapper();
|
||||||
|
p.Initialize(hc);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback((int p, int a, string s, string t) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(t);
|
||||||
|
})
|
||||||
|
.Returns(Task.FromResult(new TaskAgent()));
|
||||||
|
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<TaskCanceledException>(() => updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||||
|
Assert.Contains($"failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync_ValidateHash()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdater();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, HashValue = "bad_hash" }));
|
||||||
|
|
||||||
|
var p = new ProcessInvokerWrapper();
|
||||||
|
p.Initialize(hc);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback((int p, int a, string s, string t) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(t);
|
||||||
|
})
|
||||||
|
.Returns(Task.FromResult(new TaskAgent()));
|
||||||
|
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<Exception>(() => updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||||
|
Assert.Contains("did not match expected Runner Hash", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -197,7 +197,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
execTask = processInvoker.ExecuteAsync("", "cmd.exe", $"/c \"choice /T {SecondsToRun} /D y\"", null, tokenSource.Token);
|
execTask = processInvoker.ExecuteAsync("", "cmd.exe", $"/c \"choice /T {SecondsToRun} /D y\"", null, tokenSource.Token);
|
||||||
#else
|
#else
|
||||||
execTask = processInvoker.ExecuteAsync("", "bash", $"-c \"sleep {SecondsToRun}s\"", null, tokenSource.Token);
|
execTask = processInvoker.ExecuteAsync("", "bash", $"-c \"sleep {SecondsToRun}\"", null, tokenSource.Token);
|
||||||
#endif
|
#endif
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
tokenSource.Cancel();
|
tokenSource.Cancel();
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.285.1
|
2.286.1
|
||||||
|
|||||||
Reference in New Issue
Block a user