Skip to content

Commit 8eacbdc

Browse files
TingluoHuangthboop
andauthored
Runner config option to disable auto-update. (#1558)
* Runner config option to disable auto-update. * Update src/Runner.Listener/Configuration/ConfigurationManager.cs Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com> * Update src/Runner.Listener/Configuration/ConfigurationManager.cs Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com> * Update src/Runner.Listener/Configuration/ConfigurationManager.cs Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com> * Update src/Runner.Listener/Configuration/ConfigurationManager.cs Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com> * feedback. Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
1 parent 6b4a95c commit 8eacbdc

File tree

8 files changed

+109
-16
lines changed

8 files changed

+109
-16
lines changed

src/Runner.Common/ConfigurationStore.cs

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public sealed class RunnerSettings
3333
[DataMember(EmitDefaultValue = false)]
3434
public string PoolName { get; set; }
3535

36+
[DataMember(EmitDefaultValue = false)]
37+
public bool DisableUpdate { get; set; }
38+
3639
[DataMember(EmitDefaultValue = false)]
3740
public bool Ephemeral { get; set; }
3841

src/Runner.Common/Constants.cs

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public static class Flags
129129
public static readonly string Ephemeral = "ephemeral";
130130
public static readonly string Help = "help";
131131
public static readonly string Replace = "replace";
132+
public static readonly string DisableUpdate = "disableupdate";
132133
public static readonly string Once = "once"; // Keep this around since customers still relies on it
133134
public static readonly string RunAsService = "runasservice";
134135
public static readonly string Unattended = "unattended";

src/Runner.Listener/CommandSettings.cs

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public sealed class CommandSettings
2929
{
3030
Constants.Runner.CommandLine.Flags.Check,
3131
Constants.Runner.CommandLine.Flags.Commit,
32+
Constants.Runner.CommandLine.Flags.DisableUpdate,
3233
Constants.Runner.CommandLine.Flags.Ephemeral,
3334
Constants.Runner.CommandLine.Flags.Help,
3435
Constants.Runner.CommandLine.Flags.Once,
@@ -68,6 +69,7 @@ public sealed class CommandSettings
6869
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
6970
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
7071
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
72+
public bool DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate);
7173

7274
// Keep this around since customers still relies on it
7375
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);

src/Runner.Listener/Configuration/ConfigurationManager.cs

+29-4
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ public async Task ConfigureAsync(CommandSettings command)
196196
TaskAgent agent;
197197
while (true)
198198
{
199+
runnerSettings.DisableUpdate = command.DisableUpdate;
199200
runnerSettings.Ephemeral = command.Ephemeral;
200201
runnerSettings.AgentName = command.GetRunnerName();
201202

@@ -213,11 +214,22 @@ public async Task ConfigureAsync(CommandSettings command)
213214
if (command.GetReplace())
214215
{
215216
// Update existing agent with new PublicKey, agent version.
216-
agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral);
217+
agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate);
217218

218219
try
219220
{
220221
agent = await _runnerServer.ReplaceAgentAsync(runnerSettings.PoolId, agent);
222+
if (command.DisableUpdate &&
223+
command.DisableUpdate != agent.DisableUpdate)
224+
{
225+
throw new NotSupportedException("The GitHub server does not support configuring a self-hosted runner with 'DisableUpdate' flag.");
226+
}
227+
if (command.Ephemeral &&
228+
command.Ephemeral != agent.Ephemeral)
229+
{
230+
throw new NotSupportedException("The GitHub server does not support configuring a self-hosted runner with 'Ephemeral' flag.");
231+
}
232+
221233
_term.WriteSuccessMessage("Successfully replaced the runner");
222234
break;
223235
}
@@ -236,11 +248,22 @@ public async Task ConfigureAsync(CommandSettings command)
236248
else
237249
{
238250
// Create a new agent.
239-
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral);
251+
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate);
240252

241253
try
242254
{
243255
agent = await _runnerServer.AddAgentAsync(runnerSettings.PoolId, agent);
256+
if (command.DisableUpdate &&
257+
command.DisableUpdate != agent.DisableUpdate)
258+
{
259+
throw new NotSupportedException("The GitHub server does not support configuring a self-hosted runner with 'DisableUpdate' flag.");
260+
}
261+
if (command.Ephemeral &&
262+
command.Ephemeral != agent.Ephemeral)
263+
{
264+
throw new NotSupportedException("The GitHub server does not support configuring a self-hosted runner with 'Ephemeral' flag.");
265+
}
266+
244267
_term.WriteSuccessMessage("Runner successfully added");
245268
break;
246269
}
@@ -466,7 +489,7 @@ private ICredentialProvider GetCredentialProvider(CommandSettings command, strin
466489
}
467490

468491

469-
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral)
492+
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
470493
{
471494
ArgUtil.NotNull(agent, nameof(agent));
472495
agent.Authorization = new TaskAgentAuthorization
@@ -478,6 +501,7 @@ private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey,
478501
agent.Version = BuildConstants.RunnerPackage.Version;
479502
agent.OSDescription = RuntimeInformation.OSDescription;
480503
agent.Ephemeral = ephemeral;
504+
agent.DisableUpdate = disableUpdate;
481505
agent.MaxParallelism = 1;
482506

483507
agent.Labels.Clear();
@@ -494,7 +518,7 @@ private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey,
494518
return agent;
495519
}
496520

497-
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral)
521+
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
498522
{
499523
TaskAgent agent = new TaskAgent(agentName)
500524
{
@@ -506,6 +530,7 @@ private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet
506530
Version = BuildConstants.RunnerPackage.Version,
507531
OSDescription = RuntimeInformation.OSDescription,
508532
Ephemeral = ephemeral,
533+
DisableUpdate = disableUpdate
509534
};
510535

511536
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));

src/Runner.Listener/Runner.cs

+1
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ private void PrintUsage(CommandSettings command)
540540
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
541541
--replace Replace any existing runner with the same name (default false)
542542
--pat GitHub personal access token used for checking network connectivity when executing `.{separator}run.{ext} --check`
543+
--disableupdate Disable self-hosted runner automatic update to the latest released version`
543544
--ephemeral Configure the runner to only take one job and then let the service un-configure the runner after the job finishes (default false)");
544545

545546
#if OS_WINDOWS

src/Runner.Worker/JobRunner.cs

+57-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
using GitHub.DistributedTask.WebApi;
2-
using Pipelines = GitHub.DistributedTask.Pipelines;
3-
using GitHub.Runner.Common.Util;
4-
using GitHub.Services.Common;
5-
using GitHub.Services.WebApi;
6-
using System;
1+
using System;
72
using System.Collections.Generic;
83
using System.IO;
94
using System.Linq;
5+
using System.Net.Http;
106
using System.Text;
117
using System.Threading;
128
using System.Threading.Tasks;
13-
using System.Net.Http;
9+
using GitHub.DistributedTask.WebApi;
1410
using GitHub.Runner.Common;
11+
using GitHub.Runner.Common.Util;
1512
using GitHub.Runner.Sdk;
13+
using GitHub.Services.Common;
14+
using GitHub.Services.WebApi;
15+
using Pipelines = GitHub.DistributedTask.Pipelines;
1616

1717
namespace GitHub.Runner.Worker
1818
{
@@ -25,6 +25,7 @@ public interface IJobRunner : IRunnerService
2525
public sealed class JobRunner : RunnerService, IJobRunner
2626
{
2727
private IJobServerQueue _jobServerQueue;
28+
private RunnerSettings _runnerSettings;
2829
private ITempDirectoryManager _tempDirectoryManager;
2930

3031
public async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken)
@@ -108,8 +109,8 @@ public async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message,
108109
jobContext.SetRunnerContext("os", VarUtil.OS);
109110
jobContext.SetRunnerContext("arch", VarUtil.OSArchitecture);
110111

111-
var runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
112-
jobContext.SetRunnerContext("name", runnerSettings.AgentName);
112+
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
113+
jobContext.SetRunnerContext("name", _runnerSettings.AgentName);
113114

114115
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
115116
Directory.CreateDirectory(toolsDirectory);
@@ -209,6 +210,53 @@ private async Task<TaskResult> CompleteJobAsync(IJobServer jobServer, IExecution
209210
jobContext.Debug($"Finishing: {message.JobDisplayName}");
210211
TaskResult result = jobContext.Complete(taskResult);
211212

213+
if (_runnerSettings.DisableUpdate == true)
214+
{
215+
try
216+
{
217+
var currentVersion = new PackageVersion(BuildConstants.RunnerPackage.Version);
218+
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
219+
VssCredentials serverCredential = VssUtil.GetVssCredential(systemConnection);
220+
221+
var runnerServer = HostContext.GetService<IRunnerServer>();
222+
await runnerServer.ConnectAsync(systemConnection.Url, serverCredential);
223+
var serverPackages = await runnerServer.GetPackagesAsync("agent", BuildConstants.RunnerPackage.PackageName, 5, includeToken: false, cancellationToken: CancellationToken.None);
224+
if (serverPackages.Count > 0)
225+
{
226+
serverPackages = serverPackages.OrderByDescending(x => x.Version).ToList();
227+
Trace.Info($"Newer packages {StringUtil.ConvertToJson(serverPackages.Select(x => x.Version.ToString()))}");
228+
229+
var warnOnFailedJob = false; // any minor/patch version behind.
230+
var warnOnOldRunnerVersion = false; // >= 2 minor version behind
231+
if (serverPackages.Any(x => x.Version.CompareTo(currentVersion) > 0))
232+
{
233+
Trace.Info($"Current runner version {currentVersion} is behind the latest runner version {serverPackages[0].Version}.");
234+
warnOnFailedJob = true;
235+
}
236+
237+
if (serverPackages.Where(x => x.Version.Major == currentVersion.Major && x.Version.Minor > currentVersion.Minor).Count() > 1)
238+
{
239+
Trace.Info($"Current runner version {currentVersion} is way behind the latest runner version {serverPackages[0].Version}.");
240+
warnOnOldRunnerVersion = true;
241+
}
242+
243+
if (result == TaskResult.Failed && warnOnFailedJob)
244+
{
245+
jobContext.Warning($"This job failure may be caused by using an out of date self-hosted runner. You are currently using runner version {currentVersion}. Please update to the latest version {serverPackages[0].Version}");
246+
}
247+
else if (warnOnOldRunnerVersion)
248+
{
249+
jobContext.Warning($"This self-hosted runner is currently using runner version {currentVersion}. This version is out of date. Please update to the latest version {serverPackages[0].Version}");
250+
}
251+
}
252+
}
253+
catch (Exception ex)
254+
{
255+
// Ignore any error since suggest runner update is best effort.
256+
Trace.Error($"Caught exception during runner version check: {ex}");
257+
}
258+
}
259+
212260
try
213261
{
214262
await ShutdownQueue(throwOnFailure: true);

src/Sdk/DTWebApi/WebApi/TaskAgentReference.cs

+11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ protected TaskAgentReference(TaskAgentReference referenceToBeCloned)
2525
this.ProvisioningState = referenceToBeCloned.ProvisioningState;
2626
this.AccessPoint = referenceToBeCloned.AccessPoint;
2727
this.Ephemeral = referenceToBeCloned.Ephemeral;
28+
this.DisableUpdate = referenceToBeCloned.DisableUpdate;
2829

2930
if (referenceToBeCloned.m_links != null)
3031
{
@@ -92,6 +93,16 @@ public bool? Ephemeral
9293
set;
9394
}
9495

96+
/// <summary>
97+
/// Whether or not this agent should auto-update to latest version.
98+
/// </summary>
99+
[DataMember(EmitDefaultValue = false)]
100+
public bool? DisableUpdate
101+
{
102+
get;
103+
set;
104+
}
105+
95106
/// <summary>
96107
/// Whether or not the agent is online.
97108
/// </summary>

src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public ConfigurationManagerL0()
6666
_serviceControlManager = new Mock<ILinuxServiceControlManager>();
6767
#endif
6868

69-
var expectedAgent = new TaskAgent(_expectedAgentName) { Id = 1 };
69+
var expectedAgent = new TaskAgent(_expectedAgentName) { Id = 1, Ephemeral = true, DisableUpdate = true };
7070
expectedAgent.Authorization = new TaskAgentAuthorization
7171
{
7272
ClientId = Guid.NewGuid(),
@@ -154,7 +154,7 @@ public async Task CanEnsureConfigure()
154154
tc,
155155
new[]
156156
{
157-
"configure",
157+
"configure",
158158
"--url", _expectedServerUrl,
159159
"--name", _expectedAgentName,
160160
"--runnergroup", _secondRunnerGroupName,
@@ -163,6 +163,8 @@ public async Task CanEnsureConfigure()
163163
"--token", _expectedToken,
164164
"--labels", userLabels,
165165
"--ephemeral",
166+
"--disableupdate",
167+
"--unattended",
166168
});
167169
trace.Info("Constructed.");
168170
_store.Setup(x => x.IsConfigured()).Returns(false);
@@ -185,7 +187,7 @@ public async Task CanEnsureConfigure()
185187
// validate GetAgentPoolsAsync gets called twice with automation pool type
186188
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
187189

188-
var expectedLabels = new List<string>() { "self-hosted", VarUtil.OS, VarUtil.OSArchitecture};
190+
var expectedLabels = new List<string>() { "self-hosted", VarUtil.OS, VarUtil.OSArchitecture };
189191
expectedLabels.AddRange(userLabels.Split(",").ToList());
190192

191193
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once);

0 commit comments

Comments
 (0)