diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index d14e3e88d..95b3039d0 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -132,6 +132,7 @@ namespace GitHub.Runner.Common 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 cce711696..1bc548378 100644 --- a/src/Runner.Listener/CommandSettings.cs +++ b/src/Runner.Listener/CommandSettings.cs @@ -29,8 +29,8 @@ namespace GitHub.Runner.Listener 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 @@ namespace GitHub.Runner.Listener 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 @@ namespace GitHub.Runner.Listener 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 @@ namespace GitHub.Runner.Listener { 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 0c835504a..eb3bbdbb3 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -259,7 +259,7 @@ namespace GitHub.Runner.Listener.Configuration 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 { @@ -293,7 +293,7 @@ namespace GitHub.Runner.Listener.Configuration 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 { @@ -554,7 +554,7 @@ namespace GitHub.Runner.Listener.Configuration } - 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 @@ -571,9 +571,16 @@ namespace GitHub.Runner.Listener.Configuration 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) { @@ -583,7 +590,7 @@ namespace GitHub.Runner.Listener.Configuration 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) { @@ -598,9 +605,16 @@ namespace GitHub.Runner.Listener.Configuration 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 e71ba9cb5..c367f8852 100644 --- a/src/Runner.Listener/Runner.cs +++ b/src/Runner.Listener/Runner.cs @@ -683,7 +683,8 @@ Config Options: --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 129183900..628e2caba 100644 --- a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs +++ b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs @@ -206,6 +206,107 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration } } + [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")]