From b18bda773f094e53fd5d13d15e26aad6dfa79eb4 Mon Sep 17 00:00:00 2001 From: Cory Miller <13227161+cory-miller@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:48:23 -0400 Subject: [PATCH] Add generateServiceConfig option for configure command (#2226) For runners that are already configured but need to add systemd services after the fact, a new command option is added to generate the file only. Once generated, ./svc.sh install and other commands will be available. This also adds support for falling back to the tenant name in the Actions URL in cases when the GitHub URL is not provided, such as for our hosted larger runners. --- src/Runner.Common/ConfigurationStore.cs | 5 +- src/Runner.Common/Constants.cs | 1 + src/Runner.Listener/CommandSettings.cs | 6 +- .../Configuration/ConfigurationManager.cs | 21 ++++ .../Configuration/ConfigurationManagerL0.cs | 98 +++++++++++++++++++ 5 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/Runner.Common/ConfigurationStore.cs b/src/Runner.Common/ConfigurationStore.cs index b3cb26a86..7daa4d66f 100644 --- a/src/Runner.Common/ConfigurationStore.cs +++ b/src/Runner.Common/ConfigurationStore.cs @@ -77,14 +77,15 @@ namespace GitHub.Runner.Common Uri accountUri = new(this.ServerUrl); string repoOrOrgName = string.Empty; - if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase)) + if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(this.GitHubUrl)) { Uri gitHubUrl = new(this.GitHubUrl); // Use the "NWO part" from the GitHub URL path repoOrOrgName = gitHubUrl.AbsolutePath.Trim('/'); } - else + + if (string.IsNullOrEmpty(repoOrOrgName)) { repoOrOrgName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); } diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index 33ab3ce04..2c3a6e299 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -128,6 +128,7 @@ namespace GitHub.Runner.Common public static readonly string Check = "check"; public static readonly string Commit = "commit"; public static readonly string Ephemeral = "ephemeral"; + public static readonly string GenerateServiceConfig = "generateServiceConfig"; public static readonly string Help = "help"; public static readonly string Replace = "replace"; public static readonly string DisableUpdate = "disableupdate"; diff --git a/src/Runner.Listener/CommandSettings.cs b/src/Runner.Listener/CommandSettings.cs index 44039bb5c..4d00f1d45 100644 --- a/src/Runner.Listener/CommandSettings.cs +++ b/src/Runner.Listener/CommandSettings.cs @@ -34,6 +34,7 @@ namespace GitHub.Runner.Listener { Constants.Runner.CommandLine.Flags.DisableUpdate, Constants.Runner.CommandLine.Flags.Ephemeral, + Constants.Runner.CommandLine.Flags.GenerateServiceConfig, Constants.Runner.CommandLine.Flags.Replace, Constants.Runner.CommandLine.Flags.RunAsService, Constants.Runner.CommandLine.Flags.Unattended, @@ -79,11 +80,12 @@ namespace GitHub.Runner.Listener // Flags. public bool Check => TestFlag(Constants.Runner.CommandLine.Flags.Check); public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit); + public bool DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate); + 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 Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended); public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version); - public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral); - public bool DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate); // Keep this around since customers still relies on it public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once); diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 72b574873..25217f9be 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -81,6 +81,27 @@ namespace GitHub.Runner.Listener.Configuration _term.WriteLine("--------------------------------------------------------------------------------"); Trace.Info(nameof(ConfigureAsync)); + + if (command.GenerateServiceConfig) + { +#if OS_LINUX + if (!IsConfigured()) + { + throw new InvalidOperationException("--generateServiceConfig requires that the runner is already configured. For configuring a new runner as a service, run './config.sh'."); + } + + RunnerSettings settings = _store.GetSettings(); + + Trace.Info($"generate service config for runner: {settings.AgentId}"); + var controlManager = HostContext.GetService(); + controlManager.GenerateScripts(settings); + + return; +#else + throw new NotSupportedException("--generateServiceConfig is only supported on Linux."); +#endif + } + if (IsConfigured()) { throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first."); diff --git a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs index a3fdd5603..ca6e90b6b 100644 --- a/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs +++ b/src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs @@ -234,5 +234,103 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration _runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny(), It.Is(p => p == TaskAgentPoolType.Automation)), Times.Exactly(1)); } } + +#if OS_LINUX + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "ConfigurationManagement")] + public async Task ConfigureRunnerServiceFailsOnUnconfiguredRunners() + { + 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", + "--generateServiceConfig", + }); + trace.Info("Constructed"); + _store.Setup(x => x.IsConfigured()).Returns(false); + + trace.Info("Ensuring service generation mode fails when on un-configured runners"); + var ex = await Assert.ThrowsAsync(() => configManager.ConfigureAsync(command)); + + Assert.Contains("requires that the runner is already configured", ex.Message); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "ConfigurationManagement")] + public async Task ConfigureRunnerServiceCreatesService() + { + 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", + "--generateServiceConfig", + }); + trace.Info("Constructed"); + + _store.Setup(x => x.IsConfigured()).Returns(true); + + trace.Info("Ensuring service generation mode fails when on un-configured runners"); + await configManager.ConfigureAsync(command); + + _serviceControlManager.Verify(x => x.GenerateScripts(It.IsAny()), Times.Once); + } + } +#endif + +#if !OS_LINUX + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "ConfigurationManagement")] + public async Task ConfigureRunnerServiceFailsOnUnsupportedPlatforms() + { + 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", + "--generateServiceConfig", + }); + trace.Info("Constructed"); + _store.Setup(x => x.IsConfigured()).Returns(true); + + trace.Info("Ensuring service generation mode fails on unsupported runner platforms"); + var ex = await Assert.ThrowsAsync(() => configManager.ConfigureAsync(command)); + + Assert.Contains("only supported on Linux", ex.Message); + } + } +#endif } }