mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
14 Commits
v2.311.0
...
remove-use
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac7f83f61d | ||
|
|
d061d61093 | ||
|
|
3f3d9b0d99 | ||
|
|
af485fb660 | ||
|
|
9e3e57ff90 | ||
|
|
ac89b31d2f | ||
|
|
65201ff6be | ||
|
|
661b261959 | ||
|
|
8a25302ba3 | ||
|
|
c7d65c42d6 | ||
|
|
a9bae6f37a | ||
|
|
3136ce3a71 | ||
|
|
a4c57f2747 | ||
|
|
ce4e62c849 |
17
.github/workflows/close-bugs-bot.yml
vendored
Normal file
17
.github/workflows/close-bugs-bot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Close Bugs Bot
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *' # every day at midnight
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v8
|
||||||
|
with:
|
||||||
|
close-issue-message: "This issue does not seem to be a problem with the runner application, it concerns the GitHub actions platform more generally. Could you please post your feedback on the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃"
|
||||||
|
exempt-issue-labels: "keep"
|
||||||
|
stale-issue-label: "actions-bug"
|
||||||
|
only-labels: "actions-bug"
|
||||||
|
days-before-stale: 0
|
||||||
|
days-before-close: 1
|
||||||
17
.github/workflows/close-features-bot.yml
vendored
Normal file
17
.github/workflows/close-features-bot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Close Features Bot
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *' # every day at midnight
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v8
|
||||||
|
with:
|
||||||
|
close-issue-message: "Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions-and-packages) which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please [read our contribution guidelines](https://github.com/actions/runner#contribute). 😃"
|
||||||
|
exempt-issue-labels: "keep"
|
||||||
|
stale-issue-label: "actions-feature"
|
||||||
|
only-labels: "actions-feature"
|
||||||
|
days-before-stale: 0
|
||||||
|
days-before-close: 1
|
||||||
@@ -7,8 +7,10 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
|||||||
|
|
||||||
- For GitHub.com
|
- For GitHub.com
|
||||||
- The runner needs to access `https://api.github.com` for downloading actions.
|
- The runner needs to access `https://api.github.com` for downloading actions.
|
||||||
|
- The runner needs to access `https://codeload.github.com` for downloading actions tar.gz/zip.
|
||||||
- The runner needs to access `https://vstoken.actions.githubusercontent.com/_apis/.../` for requesting an access token.
|
- The runner needs to access `https://vstoken.actions.githubusercontent.com/_apis/.../` for requesting an access token.
|
||||||
- The runner needs to access `https://pipelines.actions.githubusercontent.com/_apis/.../` for receiving workflow jobs.
|
- The runner needs to access `https://pipelines.actions.githubusercontent.com/_apis/.../` for receiving workflow jobs.
|
||||||
|
- The runner needs to access `https://results-receiver.actions.githubusercontent.com/.../` for reporting progress and uploading logs during a workflow job execution.
|
||||||
---
|
---
|
||||||
**NOTE:** for the full list of domains that are required to be in the firewall allow list refer to the [GitHub self-hosted runners requirements documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github).
|
**NOTE:** for the full list of domains that are required to be in the firewall allow list refer to the [GitHub self-hosted runners requirements documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github).
|
||||||
|
|
||||||
@@ -16,12 +18,15 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
|||||||
|
|
||||||
```
|
```
|
||||||
curl -v https://api.github.com/zen
|
curl -v https://api.github.com/zen
|
||||||
|
curl -v https://codeload.github.com/_ping
|
||||||
curl -v https://vstoken.actions.githubusercontent.com/_apis/health
|
curl -v https://vstoken.actions.githubusercontent.com/_apis/health
|
||||||
curl -v https://pipelines.actions.githubusercontent.com/_apis/health
|
curl -v https://pipelines.actions.githubusercontent.com/_apis/health
|
||||||
|
curl -v https://results-receiver.actions.githubusercontent.com/health
|
||||||
```
|
```
|
||||||
|
|
||||||
- For GitHub Enterprise Server
|
- For GitHub Enterprise Server
|
||||||
- The runner needs to access `https://[hostname]/api/v3` for downloading actions.
|
- The runner needs to access `https://[hostname]/api/v3` for downloading actions.
|
||||||
|
- The runner needs to access `https://codeload.[hostname]/_ping` for downloading actions tar.gz/zip.
|
||||||
- The runner needs to access `https://[hostname]/_services/vstoken/_apis/.../` for requesting an access token.
|
- The runner needs to access `https://[hostname]/_services/vstoken/_apis/.../` for requesting an access token.
|
||||||
- The runner needs to access `https://[hostname]/_services/pipelines/_apis/.../` for receiving workflow jobs.
|
- The runner needs to access `https://[hostname]/_services/pipelines/_apis/.../` for receiving workflow jobs.
|
||||||
|
|
||||||
@@ -29,6 +34,7 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
|||||||
|
|
||||||
```
|
```
|
||||||
curl -v https://[hostname]/api/v3/zen
|
curl -v https://[hostname]/api/v3/zen
|
||||||
|
curl -v https://codeload.[hostname]/_ping
|
||||||
curl -v https://[hostname]/_services/vstoken/_apis/health
|
curl -v https://[hostname]/_services/vstoken/_apis/health
|
||||||
curl -v https://[hostname]/_services/pipelines/_apis/health
|
curl -v https://[hostname]/_services/pipelines/_apis/health
|
||||||
```
|
```
|
||||||
@@ -44,6 +50,10 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
|||||||
- Ping api.github.com or myGHES.com using dotnet
|
- Ping api.github.com or myGHES.com using dotnet
|
||||||
- Make HTTP GET to https://api.github.com or https://myGHES.com/api/v3 using dotnet, check response headers contains `X-GitHub-Request-Id`
|
- Make HTTP GET to https://api.github.com or https://myGHES.com/api/v3 using dotnet, check response headers contains `X-GitHub-Request-Id`
|
||||||
---
|
---
|
||||||
|
- DNS lookup for codeload.github.com or codeload.myGHES.com using dotnet
|
||||||
|
- Ping codeload.github.com or codeload.myGHES.com using dotnet
|
||||||
|
- Make HTTP GET to https://codeload.github.com/_ping or https://codeload.myGHES.com/_ping using dotnet, check response headers contains `X-GitHub-Request-Id`
|
||||||
|
---
|
||||||
- DNS lookup for vstoken.actions.githubusercontent.com using dotnet
|
- DNS lookup for vstoken.actions.githubusercontent.com using dotnet
|
||||||
- Ping vstoken.actions.githubusercontent.com using dotnet
|
- Ping vstoken.actions.githubusercontent.com using dotnet
|
||||||
- Make HTTP GET to https://vstoken.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/vstoken/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
- Make HTTP GET to https://vstoken.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/vstoken/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
||||||
@@ -52,6 +62,10 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
|||||||
- Ping pipelines.actions.githubusercontent.com using dotnet
|
- Ping pipelines.actions.githubusercontent.com using dotnet
|
||||||
- Make HTTP GET to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
- Make HTTP GET to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
||||||
- Make HTTP POST to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
- Make HTTP POST to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
||||||
|
---
|
||||||
|
- DNS lookup for results-receiver.actions.githubusercontent.com using dotnet
|
||||||
|
- Ping results-receiver.actions.githubusercontent.com using dotnet
|
||||||
|
- Make HTTP GET to https://results-receiver.actions.githubusercontent.com/health using dotnet, check response headers contains `X-GitHub-Request-Id`
|
||||||
|
|
||||||
## How to fix the issue?
|
## How to fix the issue?
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ If you are having trouble connecting, try these steps:
|
|||||||
- https://api.github.com/
|
- https://api.github.com/
|
||||||
- https://vstoken.actions.githubusercontent.com/_apis/health
|
- https://vstoken.actions.githubusercontent.com/_apis/health
|
||||||
- https://pipelines.actions.githubusercontent.com/_apis/health
|
- https://pipelines.actions.githubusercontent.com/_apis/health
|
||||||
|
- https://results-receiver.actions.githubusercontent.com/health
|
||||||
- For GHES/GHAE
|
- For GHES/GHAE
|
||||||
- https://myGHES.com/_services/vstoken/_apis/health
|
- https://myGHES.com/_services/vstoken/_apis/health
|
||||||
- https://myGHES.com/_services/pipelines/_apis/health
|
- https://myGHES.com/_services/pipelines/_apis/health
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
## Supported Distributions and Versions
|
## Supported Distributions and Versions
|
||||||
|
|
||||||
x64
|
x64
|
||||||
- Red Hat Enterprise Linux 7
|
- Red Hat Enterprise Linux 7+
|
||||||
- CentOS 7
|
- CentOS 7+
|
||||||
- Oracle Linux 7
|
- Oracle Linux 7+
|
||||||
- Fedora 29+
|
- Fedora 29+
|
||||||
- Debian 9+
|
- Debian 9+
|
||||||
- Ubuntu 16.04+
|
- Ubuntu 16.04+
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy as build
|
|||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.4.0
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.5.0
|
||||||
ARG DOCKER_VERSION=24.0.6
|
ARG DOCKER_VERSION=24.0.6
|
||||||
ARG BUILDX_VERSION=0.11.2
|
ARG BUILDX_VERSION=0.11.2
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy
|
|||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||||
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
||||||
|
ENV ImageOS=ubuntu22
|
||||||
|
|
||||||
RUN apt-get update -y \
|
RUN apt-get update -y \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
@@ -54,6 +55,7 @@ RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
|||||||
WORKDIR /home/runner
|
WORKDIR /home/runner
|
||||||
|
|
||||||
COPY --chown=runner:docker --from=build /actions-runner .
|
COPY --chown=runner:docker --from=build /actions-runner .
|
||||||
|
COPY --from=build /usr/local/lib/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker
|
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||||
|
|
||||||
Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken token, TaskAgentStatus status, string version, string os, string architecture);
|
Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken token, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BrokerServer : RunnerService, IBrokerServer
|
public sealed class BrokerServer : RunnerService, IBrokerServer
|
||||||
@@ -44,11 +44,11 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken cancellationToken, TaskAgentStatus status, string version, string os, string architecture)
|
public Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken cancellationToken, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
var jobMessage = RetryRequest<TaskAgentMessage>(
|
var jobMessage = RetryRequest<TaskAgentMessage>(
|
||||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(version, status, os, architecture, cancellationToken), cancellationToken);
|
async () => await _brokerHttpClient.GetRunnerMessageAsync(version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken);
|
||||||
|
|
||||||
return jobMessage;
|
return jobMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,6 +200,10 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_userAgents.Add(new ProductInfoHeaderValue("HttpProxyConfigured", bool.TrueString));
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common
|
|||||||
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
|
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
|
||||||
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
|
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
|
||||||
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
|
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
|
||||||
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, CancellationToken cancellationToken);
|
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken);
|
||||||
|
|
||||||
// job request
|
// job request
|
||||||
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
||||||
@@ -272,10 +272,10 @@ namespace GitHub.Runner.Common
|
|||||||
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
|
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, CancellationToken cancellationToken)
|
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection(RunnerConnectionType.MessageQueue);
|
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||||
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, os, architecture, cancellationToken: cancellationToken);
|
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, os, architecture, disableUpdate, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|||||||
@@ -73,7 +73,12 @@ namespace GitHub.Runner.Listener
|
|||||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_getMessagesTokenSource.Token, runnerStatus, BuildConstants.RunnerPackage.Version, VarUtil.OS, VarUtil.OSArchitecture);
|
message = await _brokerServer.GetRunnerMessageAsync(_getMessagesTokenSource.Token,
|
||||||
|
runnerStatus,
|
||||||
|
BuildConstants.RunnerPackage.Version,
|
||||||
|
VarUtil.OS,
|
||||||
|
VarUtil.OSArchitecture,
|
||||||
|
_settings.DisableUpdate);
|
||||||
|
|
||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ namespace GitHub.Runner.Listener.Check
|
|||||||
string githubApiUrl = null;
|
string githubApiUrl = null;
|
||||||
string actionsTokenServiceUrl = null;
|
string actionsTokenServiceUrl = null;
|
||||||
string actionsPipelinesServiceUrl = null;
|
string actionsPipelinesServiceUrl = null;
|
||||||
|
string resultsReceiverServiceUrl = null;
|
||||||
var urlBuilder = new UriBuilder(url);
|
var urlBuilder = new UriBuilder(url);
|
||||||
if (UrlUtil.IsHostedServer(urlBuilder))
|
if (UrlUtil.IsHostedServer(urlBuilder))
|
||||||
{
|
{
|
||||||
@@ -47,6 +48,7 @@ namespace GitHub.Runner.Listener.Check
|
|||||||
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
|
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
actionsTokenServiceUrl = "https://vstoken.actions.githubusercontent.com/_apis/health";
|
actionsTokenServiceUrl = "https://vstoken.actions.githubusercontent.com/_apis/health";
|
||||||
actionsPipelinesServiceUrl = "https://pipelines.actions.githubusercontent.com/_apis/health";
|
actionsPipelinesServiceUrl = "https://pipelines.actions.githubusercontent.com/_apis/health";
|
||||||
|
resultsReceiverServiceUrl = "https://results-receiver.actions.githubusercontent.com/health";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -56,13 +58,31 @@ namespace GitHub.Runner.Listener.Check
|
|||||||
actionsTokenServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
actionsTokenServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
urlBuilder.Path = "_services/pipelines/_apis/health";
|
urlBuilder.Path = "_services/pipelines/_apis/health";
|
||||||
actionsPipelinesServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
actionsPipelinesServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
|
resultsReceiverServiceUrl = string.Empty; // we don't have Results service in GHES yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var codeLoadUrlBuilder = new UriBuilder(url);
|
||||||
|
codeLoadUrlBuilder.Host = $"codeload.{codeLoadUrlBuilder.Host}";
|
||||||
|
codeLoadUrlBuilder.Path = "_ping";
|
||||||
|
|
||||||
// check github api
|
// check github api
|
||||||
checkTasks.Add(CheckUtil.CheckDns(githubApiUrl));
|
checkTasks.Add(CheckUtil.CheckDns(githubApiUrl));
|
||||||
checkTasks.Add(CheckUtil.CheckPing(githubApiUrl));
|
checkTasks.Add(CheckUtil.CheckPing(githubApiUrl));
|
||||||
checkTasks.Add(HostContext.CheckHttpsGetRequests(githubApiUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
|
checkTasks.Add(HostContext.CheckHttpsGetRequests(githubApiUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
|
||||||
|
|
||||||
|
// check github codeload
|
||||||
|
checkTasks.Add(CheckUtil.CheckDns(codeLoadUrlBuilder.Uri.AbsoluteUri));
|
||||||
|
checkTasks.Add(CheckUtil.CheckPing(codeLoadUrlBuilder.Uri.AbsoluteUri));
|
||||||
|
checkTasks.Add(HostContext.CheckHttpsGetRequests(codeLoadUrlBuilder.Uri.AbsoluteUri, pat, expectedHeader: "X-GitHub-Request-Id"));
|
||||||
|
|
||||||
|
// check results-receiver service
|
||||||
|
if (!string.IsNullOrEmpty(resultsReceiverServiceUrl))
|
||||||
|
{
|
||||||
|
checkTasks.Add(CheckUtil.CheckDns(resultsReceiverServiceUrl));
|
||||||
|
checkTasks.Add(CheckUtil.CheckPing(resultsReceiverServiceUrl));
|
||||||
|
checkTasks.Add(HostContext.CheckHttpsGetRequests(resultsReceiverServiceUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
|
||||||
|
}
|
||||||
|
|
||||||
// check actions token service
|
// check actions token service
|
||||||
checkTasks.Add(CheckUtil.CheckDns(actionsTokenServiceUrl));
|
checkTasks.Add(CheckUtil.CheckDns(actionsTokenServiceUrl));
|
||||||
checkTasks.Add(CheckUtil.CheckPing(actionsTokenServiceUrl));
|
checkTasks.Add(CheckUtil.CheckPing(actionsTokenServiceUrl));
|
||||||
|
|||||||
@@ -1134,6 +1134,15 @@ namespace GitHub.Runner.Listener
|
|||||||
jobRecord.ErrorCount++;
|
jobRecord.ErrorCount++;
|
||||||
jobRecord.Issues.Add(unhandledExceptionIssue);
|
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||||
|
|
||||||
|
if (message.Variables.TryGetValue("DistributedTask.MarkJobAsFailedOnWorkerCrash", out var markJobAsFailedOnWorkerCrash) &&
|
||||||
|
StringUtil.ConvertToBoolean(markJobAsFailedOnWorkerCrash?.Value))
|
||||||
|
{
|
||||||
|
Trace.Info("Mark the job as failed since the worker crashed");
|
||||||
|
jobRecord.Result = TaskResult.Failed;
|
||||||
|
// mark the job as completed so service will pickup the result
|
||||||
|
jobRecord.State = TimelineRecordState.Completed;
|
||||||
|
}
|
||||||
|
|
||||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -183,18 +183,15 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
||||||
{
|
{
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
|
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||||
|
runnerStatus = e.Status;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
_getMessagesTokenSource?.Cancel();
|
||||||
runnerStatus = e.Status;
|
}
|
||||||
try
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
_getMessagesTokenSource?.Cancel();
|
Trace.Info("_getMessagesTokenSource is already disposed.");
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
Trace.Info("_getMessagesTokenSource is already disposed.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +219,7 @@ namespace GitHub.Runner.Listener
|
|||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
VarUtil.OS,
|
VarUtil.OS,
|
||||||
VarUtil.OSArchitecture,
|
VarUtil.OSArchitecture,
|
||||||
|
_settings.DisableUpdate,
|
||||||
_getMessagesTokenSource.Token);
|
_getMessagesTokenSource.Token);
|
||||||
|
|
||||||
// Decrypt the message body if the session is using encryption
|
// Decrypt the message body if the session is using encryption
|
||||||
|
|||||||
@@ -457,22 +457,13 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
message = await getNextMessage; //get next message
|
message = await getNextMessage; //get next message
|
||||||
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
||||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||||
string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
if (autoUpdateInProgress == false)
|
if (autoUpdateInProgress == false)
|
||||||
{
|
{
|
||||||
autoUpdateInProgress = true;
|
autoUpdateInProgress = true;
|
||||||
AgentRefreshMessage runnerUpdateMessage = null;
|
AgentRefreshMessage runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
||||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
|
|
||||||
runnerUpdateMessage = new AgentRefreshMessage(brokerRunnerUpdateMessage.RunnerId, brokerRunnerUpdateMessage.TargetVersion, TimeSpan.FromSeconds(brokerRunnerUpdateMessage.TimeoutInSeconds));
|
|
||||||
}
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Can mock the update for testing
|
// Can mock the update for testing
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
||||||
@@ -503,6 +494,22 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (autoUpdateInProgress == false)
|
||||||
|
{
|
||||||
|
autoUpdateInProgress = true;
|
||||||
|
RunnerRefreshMessage brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
|
||||||
|
|
||||||
|
var selfUpdater = HostContext.GetService<ISelfUpdaterV2>();
|
||||||
|
selfUpdateTask = selfUpdater.SelfUpdate(brokerRunnerUpdateMessage, jobDispatcher, false, HostContext.RunnerShutdownToken);
|
||||||
|
Trace.Info("Refresh message received, kick-off selfupdate background process.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (string.Equals(message.MessageType, JobRequestMessageTypes.PipelineAgentJobRequest, StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(message.MessageType, JobRequestMessageTypes.PipelineAgentJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (autoUpdateInProgress || runOnceJobReceived)
|
if (autoUpdateInProgress || runOnceJobReceived)
|
||||||
|
|||||||
569
src/Runner.Listener/SelfUpdaterV2.cs
Normal file
569
src/Runner.Listener/SelfUpdaterV2.cs
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener
|
||||||
|
{
|
||||||
|
// This class is a fork of SelfUpdater.cs and is intended to only be used for the
|
||||||
|
// new self-update flow where the PackageMetadata is sent in the message directly.
|
||||||
|
// Forking the class prevents us from accidentally breaking the old flow while it's still in production
|
||||||
|
|
||||||
|
[ServiceLocator(Default = typeof(SelfUpdaterV2))]
|
||||||
|
public interface ISelfUpdaterV2 : IRunnerService
|
||||||
|
{
|
||||||
|
bool Busy { get; }
|
||||||
|
Task<bool> SelfUpdate(RunnerRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
|
||||||
|
}
|
||||||
|
public class SelfUpdaterV2 : RunnerService, ISelfUpdaterV2
|
||||||
|
{
|
||||||
|
private static string _platform = BuildConstants.RunnerPackage.PackageName;
|
||||||
|
private ITerminal _terminal;
|
||||||
|
private IRunnerServer _runnerServer;
|
||||||
|
private int _poolId;
|
||||||
|
private ulong _agentId;
|
||||||
|
|
||||||
|
private const int _numberOfOldVersionsToKeep = 1;
|
||||||
|
|
||||||
|
private readonly ConcurrentQueue<string> _updateTrace = new();
|
||||||
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
|
public override void Initialize(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
base.Initialize(hostContext);
|
||||||
|
|
||||||
|
_terminal = hostContext.GetService<ITerminal>();
|
||||||
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
|
var configStore = HostContext.GetService<IConfigurationStore>();
|
||||||
|
var settings = configStore.GetSettings();
|
||||||
|
_poolId = settings.PoolId;
|
||||||
|
_agentId = settings.AgentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SelfUpdate(RunnerRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
|
||||||
|
{
|
||||||
|
Busy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var totalUpdateTime = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
Trace.Info($"An update is available.");
|
||||||
|
_updateTrace.Enqueue($"RunnerPlatform: {updateMessage.OS}");
|
||||||
|
|
||||||
|
// Print console line that warn user not shutdown runner.
|
||||||
|
_terminal.WriteLine("Runner update in progress, do not shutdown runner.");
|
||||||
|
_terminal.WriteLine($"Downloading {updateMessage.TargetVersion} runner");
|
||||||
|
|
||||||
|
await DownloadLatestRunner(token, updateMessage.TargetVersion, updateMessage.DownloadUrl, updateMessage.SHA256Checksum, updateMessage.OS);
|
||||||
|
Trace.Info($"Download latest runner and unzip into runner root.");
|
||||||
|
|
||||||
|
// wait till all running job finish
|
||||||
|
_terminal.WriteLine("Waiting for current job finish running.");
|
||||||
|
|
||||||
|
await jobDispatcher.WaitAsync(token);
|
||||||
|
Trace.Info($"All running job has exited.");
|
||||||
|
|
||||||
|
// We need to keep runner backup around for macOS until we fixed https://github.com/actions/runner/issues/743
|
||||||
|
// delete runner backup
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
DeletePreviousVersionRunnerBackup(token, updateMessage.TargetVersion);
|
||||||
|
Trace.Info($"Delete old version runner backup.");
|
||||||
|
stopWatch.Stop();
|
||||||
|
// generate update script from template
|
||||||
|
_updateTrace.Enqueue($"DeleteRunnerBackupTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
_terminal.WriteLine("Generate and execute update script.");
|
||||||
|
|
||||||
|
string updateScript = GenerateUpdateScript(restartInteractiveRunner, updateMessage.TargetVersion);
|
||||||
|
Trace.Info($"Generate update script into: {updateScript}");
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// For L0, we will skip execute update script.
|
||||||
|
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT")))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
string flagFile = "update.finished";
|
||||||
|
IOUtil.DeleteFile(flagFile);
|
||||||
|
// kick off update script
|
||||||
|
Process invokeScript = new();
|
||||||
|
#if OS_WINDOWS
|
||||||
|
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
||||||
|
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
||||||
|
#elif (OS_OSX || OS_LINUX)
|
||||||
|
invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace);
|
||||||
|
invokeScript.StartInfo.Arguments = $"\"{updateScript}\"";
|
||||||
|
#endif
|
||||||
|
invokeScript.Start();
|
||||||
|
Trace.Info($"Update script start running");
|
||||||
|
}
|
||||||
|
|
||||||
|
totalUpdateTime.Stop();
|
||||||
|
|
||||||
|
_updateTrace.Enqueue($"TotalUpdateTime: {totalUpdateTime.ElapsedMilliseconds}ms");
|
||||||
|
_terminal.WriteLine("Runner will exit shortly for update, should be back online within 10 seconds.");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_updateTrace.Enqueue(ex.ToString());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_terminal.WriteLine("Runner update process finished.");
|
||||||
|
Busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// _work
|
||||||
|
/// \_update
|
||||||
|
/// \bin
|
||||||
|
/// \externals
|
||||||
|
/// \run.sh
|
||||||
|
/// \run.cmd
|
||||||
|
/// \package.zip //temp download .zip/.tar.gz
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task DownloadLatestRunner(CancellationToken token, string targetVersion, string packageDownloadUrl, string packageHashValue, string targetPlatform)
|
||||||
|
{
|
||||||
|
string latestRunnerDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.UpdateDirectory);
|
||||||
|
IOUtil.DeleteDirectory(latestRunnerDirectory, token);
|
||||||
|
Directory.CreateDirectory(latestRunnerDirectory);
|
||||||
|
|
||||||
|
string archiveFile = null;
|
||||||
|
|
||||||
|
// Only try trimmed package if sever sends them and we have calculated hash value of the current runtime/externals.
|
||||||
|
_updateTrace.Enqueue($"DownloadUrl: {packageDownloadUrl}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#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 (targetPlatform.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))
|
||||||
|
{
|
||||||
|
archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, targetPlatform, token);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// delete .zip file
|
||||||
|
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
|
||||||
|
{
|
||||||
|
Trace.Verbose("Deleting latest runner package zip: {0}", archiveFile);
|
||||||
|
IOUtil.DeleteFile(archiveFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//it is not critical if we fail to delete the .zip file
|
||||||
|
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await CopyLatestRunnerToRoot(latestRunnerDirectory, targetVersion, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> DownLoadRunner(string downloadDirectory, string packageDownloadUrl, string packageHashValue, string packagePlatform, 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 (packagePlatform.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(HostContext.CreateHttpClientHandler()))
|
||||||
|
{
|
||||||
|
Trace.Info($"Downloading {packageDownloadUrl}");
|
||||||
|
|
||||||
|
using (FileStream fs = new(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.Enqueue($"PackageDownloadTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
_updateTrace.Enqueue($"Attempts: {attempt}");
|
||||||
|
_updateTrace.Enqueue($"PackageSize: {downloadSize / 1024 / 1024}MB");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Trace.Info($"Runner download has been cancelled.");
|
||||||
|
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 {archiveFile}");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
Trace.Info($"Validated Runner Hash matches {archiveFile} : {packageHashValue}");
|
||||||
|
_updateTrace.Enqueue($"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' to extract archive file: {archiveFile}. return code: {exitCode}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"{archiveFile}");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
Trace.Info($"Finished getting latest runner package at: {extractDirectory}.");
|
||||||
|
_updateTrace.Enqueue($"PackageExtractTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task CopyLatestRunnerToRoot(string latestRunnerDirectory, string targetVersion, CancellationToken token)
|
||||||
|
{
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
// copy latest runner into runner root folder
|
||||||
|
// copy bin from _work/_update -> bin.version under root
|
||||||
|
string binVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.BinDirectory}.{targetVersion}");
|
||||||
|
Directory.CreateDirectory(binVersionDir);
|
||||||
|
Trace.Info($"Copy {Path.Combine(latestRunnerDirectory, Constants.Path.BinDirectory)} to {binVersionDir}.");
|
||||||
|
IOUtil.CopyDirectory(Path.Combine(latestRunnerDirectory, Constants.Path.BinDirectory), binVersionDir, token);
|
||||||
|
|
||||||
|
// copy externals from _work/_update -> externals.version under root
|
||||||
|
string externalsVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.ExternalsDirectory}.{targetVersion}");
|
||||||
|
Directory.CreateDirectory(externalsVersionDir);
|
||||||
|
Trace.Info($"Copy {Path.Combine(latestRunnerDirectory, Constants.Path.ExternalsDirectory)} to {externalsVersionDir}.");
|
||||||
|
IOUtil.CopyDirectory(Path.Combine(latestRunnerDirectory, Constants.Path.ExternalsDirectory), externalsVersionDir, token);
|
||||||
|
|
||||||
|
// copy and replace all .sh/.cmd files
|
||||||
|
Trace.Info($"Copy any remaining .sh/.cmd files into runner root.");
|
||||||
|
foreach (FileInfo file in new DirectoryInfo(latestRunnerDirectory).GetFiles() ?? new FileInfo[0])
|
||||||
|
{
|
||||||
|
string destination = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), file.Name);
|
||||||
|
|
||||||
|
// Removing the file instead of just trying to overwrite it works around permissions issues on linux.
|
||||||
|
// https://github.com/actions/runner/issues/981
|
||||||
|
Trace.Info($"Copy {file.FullName} to {destination}");
|
||||||
|
IOUtil.DeleteFile(destination);
|
||||||
|
file.CopyTo(destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
_updateTrace.Enqueue($"CopyRunnerToRootTime: {stopWatch.ElapsedMilliseconds}ms");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeletePreviousVersionRunnerBackup(CancellationToken token, string targetVersion)
|
||||||
|
{
|
||||||
|
// delete previous backup runner (back compat, can be remove after serval sprints)
|
||||||
|
// bin.bak.2.99.0
|
||||||
|
// externals.bak.2.99.0
|
||||||
|
foreach (string existBackUp in Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "*.bak.*"))
|
||||||
|
{
|
||||||
|
Trace.Info($"Delete existing runner backup at {existBackUp}.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(existBackUp, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is OperationCanceledException))
|
||||||
|
{
|
||||||
|
Trace.Error(ex);
|
||||||
|
Trace.Info($"Catch exception during delete backup folder {existBackUp}, ignore this error try delete the backup folder on next auto-update.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old bin.2.99.0 folder, only leave the current version and the latest download version
|
||||||
|
var allBinDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "bin.*");
|
||||||
|
if (allBinDirs.Length > _numberOfOldVersionsToKeep)
|
||||||
|
{
|
||||||
|
// there are more than {_numberOfOldVersionsToKeep} bin.version folder.
|
||||||
|
// delete older bin.version folders.
|
||||||
|
foreach (var oldBinDir in allBinDirs)
|
||||||
|
{
|
||||||
|
if (string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin"), StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin.{BuildConstants.RunnerPackage.Version}"), StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin.{targetVersion}"), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// skip for current runner version
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Delete runner bin folder's backup at {oldBinDir}.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(oldBinDir, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is OperationCanceledException))
|
||||||
|
{
|
||||||
|
Trace.Error(ex);
|
||||||
|
Trace.Info($"Catch exception during delete backup folder {oldBinDir}, ignore this error try delete the backup folder on next auto-update.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old externals.2.99.0 folder, only leave the current version and the latest download version
|
||||||
|
var allExternalsDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "externals.*");
|
||||||
|
if (allExternalsDirs.Length > _numberOfOldVersionsToKeep)
|
||||||
|
{
|
||||||
|
// there are more than {_numberOfOldVersionsToKeep} externals.version folder.
|
||||||
|
// delete older externals.version folders.
|
||||||
|
foreach (var oldExternalDir in allExternalsDirs)
|
||||||
|
{
|
||||||
|
if (string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals"), StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals.{BuildConstants.RunnerPackage.Version}"), StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals.{targetVersion}"), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// skip for current runner version
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Delete runner externals folder's backup at {oldExternalDir}.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(oldExternalDir, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is OperationCanceledException))
|
||||||
|
{
|
||||||
|
Trace.Error(ex);
|
||||||
|
Trace.Info($"Catch exception during delete backup folder {oldExternalDir}, ignore this error try delete the backup folder on next auto-update.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateUpdateScript(bool restartInteractiveRunner, string targetVersion)
|
||||||
|
{
|
||||||
|
int processId = Process.GetCurrentProcess().Id;
|
||||||
|
string updateLog = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), $"SelfUpdate-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}.log");
|
||||||
|
string runnerRoot = HostContext.GetDirectory(WellKnownDirectory.Root);
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
string templateName = "update.cmd.template";
|
||||||
|
#else
|
||||||
|
string templateName = "update.sh.template";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
string templatePath = Path.Combine(runnerRoot, $"bin.{targetVersion}", templateName);
|
||||||
|
string template = File.ReadAllText(templatePath);
|
||||||
|
|
||||||
|
template = template.Replace("_PROCESS_ID_", processId.ToString());
|
||||||
|
template = template.Replace("_RUNNER_PROCESS_NAME_", $"Runner.Listener{IOUtil.ExeExtension}");
|
||||||
|
template = template.Replace("_ROOT_FOLDER_", runnerRoot);
|
||||||
|
template = template.Replace("_EXIST_RUNNER_VERSION_", BuildConstants.RunnerPackage.Version);
|
||||||
|
template = template.Replace("_DOWNLOAD_RUNNER_VERSION_", targetVersion);
|
||||||
|
template = template.Replace("_UPDATE_LOG_", updateLog);
|
||||||
|
template = template.Replace("_RESTART_INTERACTIVE_RUNNER_", restartInteractiveRunner ? "1" : "0");
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
string scriptName = "_update.cmd";
|
||||||
|
#else
|
||||||
|
string scriptName = "_update.sh";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
string updateScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), scriptName);
|
||||||
|
if (File.Exists(updateScript))
|
||||||
|
{
|
||||||
|
IOUtil.DeleteFile(updateScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(updateScript, template);
|
||||||
|
return updateScript;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,11 +62,6 @@ namespace GitHub.Runner.Sdk
|
|||||||
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
|
|
||||||
{
|
|
||||||
settings.AllowAutoRedirectForBroker = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove Invariant from the list of accepted languages.
|
// Remove Invariant from the list of accepted languages.
|
||||||
//
|
//
|
||||||
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current
|
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ namespace GitHub.Services.Common
|
|||||||
// SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
|
// SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
|
||||||
|
|
||||||
var tmpResponse = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
var tmpResponse = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||||
if (Settings.AllowAutoRedirectForBroker && tmpResponse.StatusCode == HttpStatusCode.Redirect)
|
if (tmpResponse.StatusCode == HttpStatusCode.Redirect)
|
||||||
{
|
{
|
||||||
//Dispose of the previous response
|
//Dispose of the previous response
|
||||||
tmpResponse?.Dispose();
|
tmpResponse?.Dispose();
|
||||||
|
|||||||
@@ -110,16 +110,6 @@ namespace GitHub.Services.Common
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not HttpClientHandler should follow redirect on outgoing broker requests
|
|
||||||
/// This is special since this also sends token in the request, where as default AllowAutoRedirect does not
|
|
||||||
/// </summary>
|
|
||||||
public Boolean AllowAutoRedirectForBroker
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not compression should be used on outgoing requests.
|
/// Gets or sets a value indicating whether or not compression should be used on outgoing requests.
|
||||||
/// The default value is true.
|
/// The default value is true.
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
string runnerVersion = null,
|
string runnerVersion = null,
|
||||||
string os = null,
|
string os = null,
|
||||||
string architecture = null,
|
string architecture = null,
|
||||||
|
bool? disableUpdate = null,
|
||||||
object userState = null,
|
object userState = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -495,6 +496,11 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
queryParams.Add("architecture", architecture);
|
queryParams.Add("architecture", architecture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (disableUpdate != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("disableUpdate", disableUpdate.Value.ToString().ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
return SendAsync<TaskAgentMessage>(
|
return SendAsync<TaskAgentMessage>(
|
||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
|
||||||
@@ -15,35 +16,32 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RunnerRefreshMessage(
|
[DataMember(Name = "target_version")]
|
||||||
ulong runnerId,
|
|
||||||
String targetVersion,
|
|
||||||
int? timeoutInSeconds = null)
|
|
||||||
{
|
|
||||||
this.RunnerId = runnerId;
|
|
||||||
this.TimeoutInSeconds = timeoutInSeconds ?? TimeSpan.FromMinutes(60).Seconds;
|
|
||||||
this.TargetVersion = targetVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public ulong RunnerId
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public int TimeoutInSeconds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public String TargetVersion
|
public String TargetVersion
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember(Name = "download_url")]
|
||||||
|
public string DownloadUrl
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember(Name = "sha256_checksum")]
|
||||||
|
public string SHA256Checksum
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember(Name = "os")]
|
||||||
|
public string OS
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
TaskAgentStatus? status,
|
TaskAgentStatus? status,
|
||||||
string os = null,
|
string os = null,
|
||||||
string architecture = null,
|
string architecture = null,
|
||||||
|
bool? disableUpdate = null,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -87,6 +88,11 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
queryParams.Add("architecture", architecture);
|
queryParams.Add("architecture", architecture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (disableUpdate != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("disableUpdate", disableUpdate.Value.ToString().ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
var result = await SendAsync<TaskAgentMessage>(
|
var result = await SendAsync<TaskAgentMessage>(
|
||||||
new HttpMethod("GET"),
|
new HttpMethod("GET"),
|
||||||
requestUri: requestUri,
|
requestUri: requestUri,
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Setup(x => x.GetAgentMessageAsync(
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, CancellationToken cancellationToken) =>
|
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
return messages.Dequeue();
|
return messages.Dequeue();
|
||||||
@@ -208,7 +208,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
//Assert
|
//Assert
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Verify(x => x.GetAgentMessageAsync(
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(arMessages.Length));
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(arMessages.Length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Setup(x => x.GetAgentMessageAsync(
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
.Throws(new TaskAgentAccessTokenExpiredException("test"));
|
.Throws(new TaskAgentAccessTokenExpiredException("test"));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -311,7 +311,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
//Assert
|
//Assert
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Verify(x => x.GetAgentMessageAsync(
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Verify(x => x.DeleteAgentSessionAsync(
|
.Verify(x => x.DeleteAgentSessionAsync(
|
||||||
|
|||||||
241
src/Test/L0/Listener/SelfUpdaterV2L0.cs
Normal file
241
src/Test/L0/Listener/SelfUpdaterV2L0.cs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#if !(OS_WINDOWS && ARM64)
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Listener
|
||||||
|
{
|
||||||
|
public sealed class SelfUpdaterV2L0
|
||||||
|
{
|
||||||
|
private Mock<IRunnerServer> _runnerServer;
|
||||||
|
private Mock<ITerminal> _term;
|
||||||
|
private Mock<IConfigurationStore> _configStore;
|
||||||
|
private Mock<IJobDispatcher> _jobDispatcher;
|
||||||
|
private AgentRefreshMessage _refreshMessage = new(1, "2.999.0");
|
||||||
|
private List<TrimmedPackageMetadata> _trimmedPackages = new();
|
||||||
|
|
||||||
|
#if !OS_WINDOWS
|
||||||
|
private string _packageUrl = null;
|
||||||
|
#else
|
||||||
|
private string _packageUrl = null;
|
||||||
|
#endif
|
||||||
|
public SelfUpdaterV2L0()
|
||||||
|
{
|
||||||
|
_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 });
|
||||||
|
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchLatestRunner()
|
||||||
|
{
|
||||||
|
var latestVersion = "";
|
||||||
|
var httpClientHandler = new HttpClientHandler();
|
||||||
|
httpClientHandler.AllowAutoRedirect = false;
|
||||||
|
using (var client = new HttpClient(httpClientHandler))
|
||||||
|
{
|
||||||
|
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://github.com/actions/runner/releases/latest"));
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.Redirect)
|
||||||
|
{
|
||||||
|
var redirectUrl = response.Headers.Location.ToString();
|
||||||
|
Regex regex = new(@"/runner/releases/tag/v(?<version>\d+\.\d+\.\d+)");
|
||||||
|
var match = regex.Match(redirectUrl);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
latestVersion = match.Groups["version"].Value;
|
||||||
|
|
||||||
|
#if !OS_WINDOWS
|
||||||
|
_packageUrl = $"https://github.com/actions/runner/releases/download/v{latestVersion}/actions-runner-{BuildConstants.RunnerPackage.PackageName}-{latestVersion}.tar.gz";
|
||||||
|
#else
|
||||||
|
_packageUrl = $"https://github.com/actions/runner/releases/download/v{latestVersion}/actions-runner-{BuildConstants.RunnerPackage.PackageName}-{latestVersion}.zip";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("The latest runner version could not be determined so a download URL could not be generated for it. Please check the location header of the redirect response of 'https://github.com/actions/runner/releases/latest'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await FetchLatestRunner();
|
||||||
|
Assert.NotNull(_packageUrl);
|
||||||
|
Assert.NotNull(_trimmedPackages);
|
||||||
|
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..", "_layout", "bin")));
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(_packageUrl);
|
||||||
|
hc.GetTrace().Info(StringUtil.ConvertToJson(_trimmedPackages));
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdaterV2();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
|
var p1 = new ProcessInvokerWrapper();
|
||||||
|
p1.Initialize(hc);
|
||||||
|
var p2 = new ProcessInvokerWrapper();
|
||||||
|
p2.Initialize(hc);
|
||||||
|
var p3 = new ProcessInvokerWrapper();
|
||||||
|
p3.Initialize(hc);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p1);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p2);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var message = new RunnerRefreshMessage()
|
||||||
|
{
|
||||||
|
TargetVersion = "2.999.0",
|
||||||
|
OS = BuildConstants.RunnerPackage.PackageName,
|
||||||
|
DownloadUrl = _packageUrl
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync_DownloadRetry()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await FetchLatestRunner();
|
||||||
|
Assert.NotNull(_packageUrl);
|
||||||
|
Assert.NotNull(_trimmedPackages);
|
||||||
|
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..", "_layout", "bin")));
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(_packageUrl);
|
||||||
|
hc.GetTrace().Info(StringUtil.ConvertToJson(_trimmedPackages));
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdaterV2();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
|
var p1 = new ProcessInvokerWrapper();
|
||||||
|
p1.Initialize(hc);
|
||||||
|
var p2 = new ProcessInvokerWrapper();
|
||||||
|
p2.Initialize(hc);
|
||||||
|
var p3 = new ProcessInvokerWrapper();
|
||||||
|
p3.Initialize(hc);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p1);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p2);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
var message = new RunnerRefreshMessage()
|
||||||
|
{
|
||||||
|
TargetVersion = "2.999.0",
|
||||||
|
OS = BuildConstants.RunnerPackage.PackageName,
|
||||||
|
DownloadUrl = "https://github.com/actions/runner/notexists"
|
||||||
|
};
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<TaskCanceledException>(() => updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||||
|
Assert.Contains($"failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void TestSelfUpdateAsync_ValidateHash()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await FetchLatestRunner();
|
||||||
|
Assert.NotNull(_packageUrl);
|
||||||
|
Assert.NotNull(_trimmedPackages);
|
||||||
|
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..", "_layout", "bin")));
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info(_packageUrl);
|
||||||
|
hc.GetTrace().Info(StringUtil.ConvertToJson(_trimmedPackages));
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
var updater = new Runner.Listener.SelfUpdaterV2();
|
||||||
|
hc.SetSingleton<ITerminal>(_term.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
|
var p1 = new ProcessInvokerWrapper();
|
||||||
|
p1.Initialize(hc);
|
||||||
|
var p2 = new ProcessInvokerWrapper();
|
||||||
|
p2.Initialize(hc);
|
||||||
|
var p3 = new ProcessInvokerWrapper();
|
||||||
|
p3.Initialize(hc);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p1);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p2);
|
||||||
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
|
updater.Initialize(hc);
|
||||||
|
|
||||||
|
var message = new RunnerRefreshMessage()
|
||||||
|
{
|
||||||
|
TargetVersion = "2.999.0",
|
||||||
|
OS = BuildConstants.RunnerPackage.PackageName,
|
||||||
|
DownloadUrl = _packageUrl,
|
||||||
|
SHA256Checksum = "badhash"
|
||||||
|
};
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<Exception>(() => updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||||
|
Assert.Contains("did not match expected Runner Hash", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user