diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index 86599ada0fa..2974683b52b 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace GitHub.Runner.Common { @@ -132,6 +132,7 @@ public static class Flags public static readonly string GenerateServiceConfig = "generateServiceConfig"; public static readonly string Help = "help"; public static readonly string Local = "local"; + public static readonly string NoDefaultLabels = "no-default-labels"; public static readonly string Replace = "replace"; public static readonly string DisableUpdate = "disableupdate"; public static readonly string Once = "once"; // Keep this around since customers still relies on it diff --git a/src/Runner.Listener/CommandSettings.cs b/src/Runner.Listener/CommandSettings.cs index cce711696cb..1bc54837864 100644 --- a/src/Runner.Listener/CommandSettings.cs +++ b/src/Runner.Listener/CommandSettings.cs @@ -29,8 +29,8 @@ public sealed class CommandSettings private readonly Dictionary validOptions = new() { // Valid configure flags and args - [Constants.Runner.CommandLine.Commands.Configure] = - new string[] + [Constants.Runner.CommandLine.Commands.Configure] = + new string[] { Constants.Runner.CommandLine.Flags.DisableUpdate, Constants.Runner.CommandLine.Flags.Ephemeral, @@ -38,6 +38,7 @@ public sealed class CommandSettings Constants.Runner.CommandLine.Flags.Replace, Constants.Runner.CommandLine.Flags.RunAsService, Constants.Runner.CommandLine.Flags.Unattended, + Constants.Runner.CommandLine.Flags.NoDefaultLabels, Constants.Runner.CommandLine.Args.Auth, Constants.Runner.CommandLine.Args.Labels, Constants.Runner.CommandLine.Args.MonitorSocketAddress, @@ -85,6 +86,7 @@ public sealed class CommandSettings public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral); public bool GenerateServiceConfig => TestFlag(Constants.Runner.CommandLine.Flags.GenerateServiceConfig); public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help); + public bool NoDefaultLabels => TestFlag(Constants.Runner.CommandLine.Flags.NoDefaultLabels); public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended); public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version); public bool RemoveLocalConfig => TestFlag(Constants.Runner.CommandLine.Flags.Local); @@ -182,7 +184,7 @@ public string GetCommandName() { command = Constants.Runner.CommandLine.Commands.Warmup; } - + return command; } diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 392eb0e3c3b..b40ac43ac8d 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -236,7 +236,7 @@ public async Task ConfigureAsync(CommandSettings command) if (command.GetReplace()) { // Update existing agent with new PublicKey, agent version. - agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate); + agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate, command.NoDefaultLabels); try { @@ -270,7 +270,7 @@ public async Task ConfigureAsync(CommandSettings command) else { // Create a new agent. - agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate); + agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral, command.DisableUpdate, command.NoDefaultLabels); try { @@ -511,7 +511,7 @@ private ICredentialProvider GetCredentialProvider(CommandSettings command, strin } - private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet userLabels, bool ephemeral, bool disableUpdate) + private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet userLabels, bool ephemeral, bool disableUpdate, bool noDefaultLabels) { ArgUtil.NotNull(agent, nameof(agent)); agent.Authorization = new TaskAgentAuthorization @@ -528,9 +528,16 @@ private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, agent.Labels.Clear(); - agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System)); - agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System)); - agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System)); + if (!noDefaultLabels) + { + agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System)); + agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System)); + agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System)); + } + else if (userLabels.Count == 0) + { + throw new NotSupportedException("Disabling default labels via --no-default-labels without specifying --labels is not supported"); + } foreach (var userLabel in userLabels) { @@ -540,7 +547,7 @@ private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, return agent; } - private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet userLabels, bool ephemeral, bool disableUpdate) + private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet userLabels, bool ephemeral, bool disableUpdate, bool noDefaultLabels) { TaskAgent agent = new(agentName) { @@ -555,9 +562,16 @@ private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet DisableUpdate = disableUpdate }; - agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System)); - agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System)); - agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System)); + if (!noDefaultLabels) + { + agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System)); + agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System)); + agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System)); + } + else if (userLabels.Count == 0) + { + throw new NotSupportedException("Disabling default labels via --no-default-labels without specifying --labels is not supported"); + } foreach (var userLabel in userLabels) { diff --git a/src/Runner.Listener/Runner.cs b/src/Runner.Listener/Runner.cs index c28e5a9f7c4..24d7afa078b 100644 --- a/src/Runner.Listener/Runner.cs +++ b/src/Runner.Listener/Runner.cs @@ -136,7 +136,7 @@ public async Task ExecuteCommand(CommandSettings command) if (command.Remove) { // only remove local config files and exit - if(command.RemoveLocalConfig) + if (command.RemoveLocalConfig) { configManager.DeleteLocalRunnerConfig(); return Constants.Runner.ReturnCode.Success; @@ -652,7 +652,8 @@ private void PrintUsage(CommandSettings command) --token string Registration token. Required if unattended --name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"}) --runnergroup string Name of the runner group to add this runner to (defaults to the default runner group) - --labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}' + --labels string Custom labels that will be added to the runner. This option is mandatory if --no-default-labels is used. + --no-default-labels Disables adding the default labels: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}' --local Removes the runner config files from your local machine. Used as an option to the remove command --work string Relative runner work directory (default {Constants.Path.WorkDirectory}) --replace Replace any existing runner with the same name (default false) diff --git a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs index ca6e90b6bf7..f4ba5781f21 100644 --- a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs +++ b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs @@ -192,6 +192,107 @@ public async Task CanEnsureConfigure() } } + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "ConfigurationManagement")] + public async Task ConfigureErrorDefaultLabelsDisabledWithNoCustomLabels() + { + using (TestHostContext tc = CreateTestContext()) + { + Tracing trace = tc.GetTrace(); + + trace.Info("Creating config manager"); + IConfigurationManager configManager = new ConfigurationManager(); + configManager.Initialize(tc); + + trace.Info("Preparing command line arguments"); + var command = new CommandSettings( + tc, + new[] + { + "configure", + "--url", _expectedServerUrl, + "--name", _expectedAgentName, + "--runnergroup", _secondRunnerGroupName, + "--work", _expectedWorkFolder, + "--auth", _expectedAuthType, + "--token", _expectedToken, + "--no-default-labels", + "--ephemeral", + "--disableupdate", + "--unattended", + }); + trace.Info("Constructed."); + _store.Setup(x => x.IsConfigured()).Returns(false); + _configMgrAgentSettings = null; + + trace.Info("Ensuring configure fails if default labels are disabled and no custom labels are set"); + var ex = await Assert.ThrowsAsync(() => configManager.ConfigureAsync(command)); + + Assert.Contains("--no-default-labels without specifying --labels is not supported", ex.Message); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "ConfigurationManagement")] + public async Task ConfigureDefaultLabelsDisabledWithCustomLabels() + { + using (TestHostContext tc = CreateTestContext()) + { + Tracing trace = tc.GetTrace(); + + trace.Info("Creating config manager"); + IConfigurationManager configManager = new ConfigurationManager(); + configManager.Initialize(tc); + + var userLabels = "userlabel1,userlabel2"; + + trace.Info("Preparing command line arguments"); + var command = new CommandSettings( + tc, + new[] + { + "configure", + "--url", _expectedServerUrl, + "--name", _expectedAgentName, + "--runnergroup", _secondRunnerGroupName, + "--work", _expectedWorkFolder, + "--auth", _expectedAuthType, + "--token", _expectedToken, + "--labels", userLabels, + "--no-default-labels", + "--ephemeral", + "--disableupdate", + "--unattended", + }); + trace.Info("Constructed."); + _store.Setup(x => x.IsConfigured()).Returns(false); + _configMgrAgentSettings = null; + + trace.Info("Ensuring all the required parameters are available in the command line parameter"); + await configManager.ConfigureAsync(command); + + _store.Setup(x => x.IsConfigured()).Returns(true); + + trace.Info("Configured, verifying all the parameter value"); + var s = configManager.LoadSettings(); + Assert.NotNull(s); + Assert.True(s.ServerUrl.Equals(_expectedServerUrl)); + Assert.True(s.AgentName.Equals(_expectedAgentName)); + Assert.True(s.PoolId.Equals(_secondRunnerGroupId)); + Assert.True(s.WorkFolder.Equals(_expectedWorkFolder)); + Assert.True(s.Ephemeral.Equals(true)); + + // validate GetAgentPoolsAsync gets called twice with automation pool type + _runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny(), It.Is(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2)); + + var expectedLabels = userLabels.Split(",").ToList(); + + _runnerServer.Verify(x => x.AddAgentAsync(It.IsAny(), It.Is(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once); + } + } + [Fact] [Trait("Level", "L0")] [Trait("Category", "ConfigurationManagement")]