diff --git a/.gitignore b/.gitignore index c790d3d..9ad6a74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin obj +.vscode project.lock.json \ No newline at end of file diff --git a/nautilus/CommandBase.cs b/nautilus/CommandBase.cs index 9a96f72..e87dc79 100644 --- a/nautilus/CommandBase.cs +++ b/nautilus/CommandBase.cs @@ -3,16 +3,19 @@ namespace Nautilus { public abstract class CommandBase - { - [Option("server", Required = true, HelpText = "Octopus server address (e.g. http://your-octopus-server/).")] + { + [Option('s', "server", Required = true, HelpText = "Octopus server address (e.g. http://your-octopus-server/).")] public string OctopusServerAddress { get; set; } - [Option("apiKey", Required = true, HelpText = "Octopus API key.")] + [Option('k', "apikey", Required = true, HelpText = "Octopus API key.")] public string OctopusApiKey { get; set; } + + public int Run() + { + var octopus = new OctopusProxy(OctopusServerAddress, OctopusApiKey); + return Run(octopus); + } - [Option("machine", Required = false, HelpText = "The target machine name.")] - public string MachineName { get; set; } - - public abstract int Run(); + protected abstract int Run(OctopusProxy octopus); } } diff --git a/nautilus/DeployCommand.cs b/nautilus/DeployCommand.cs index 8300b3c..801d03c 100644 --- a/nautilus/DeployCommand.cs +++ b/nautilus/DeployCommand.cs @@ -2,30 +2,32 @@ using System.Collections.Generic; using System.Linq; using CommandLine; +using Octopus.Client.Model; namespace Nautilus { [Verb("deploy", HelpText = "Creates deployments for the latest release of all projects related to the target machine by role and environment.")] public class DeployCommand : CommandBase { - public override int Run() - { - var octopus = new OctopusProxy(OctopusServerAddress, OctopusApiKey); - + [Option('n', "name", Required = false, HelpText = "The target machine name.")] + public string MachineName { get; set; } + + protected override int Run(OctopusProxy octopus) + { var machineName = MachineName ?? Environment.MachineName; - Console.WriteLine($"Preparing to deploy to target machine ({machineName})..."); + Console.WriteLine($"Preparing to deploy to target machine ({machineName})"); var machine = octopus.GetMachine(machineName); if (machine == null) { - Console.WriteLine($"Error: The target machine ({machineName}) is not registered with Octopus ({OctopusServerAddress})."); + Console.WriteLine($"Error: The target machine ({machineName}) is not registered with Octopus ({OctopusServerAddress})"); return 1; } Console.WriteLine($"{machine.Id} {machine.Name} {String.Join(",", machine.Roles)} {String.Join(",", machine.EnvironmentIds)}"); - Console.WriteLine($"Finding projects with the target roles ({String.Join(",", machine.Roles)})..."); + Console.WriteLine($"Finding projects with the target roles ({String.Join(",", machine.Roles)})"); var projectIds = new List(); var projects = octopus.GetProjects(); @@ -33,7 +35,7 @@ public override int Run() { var deploymentProcess = octopus.GetDeploymentProcess(project.DeploymentProcessId); - if (deploymentProcess.HasAnyRole(machine.Roles)) + if (HasAnyRole(deploymentProcess, machine.Roles)) { projectIds.Add(project.Id); Console.WriteLine($"{project.Id} {project.Name}"); @@ -48,7 +50,7 @@ public override int Run() var dashboard = octopus.GetDynamicDashboard(projectIds, machine.EnvironmentIds); - Console.WriteLine($"Creating deployments for target environments ({String.Join(",", machine.EnvironmentIds)})..."); + Console.WriteLine($"Creating deployments for target environments ({String.Join(",", machine.EnvironmentIds)})"); foreach(var item in dashboard.Items) { @@ -61,5 +63,23 @@ public override int Run() return 0; } + + private static bool HasAnyRole(DeploymentProcessResource deploymentProcess, IEnumerable roles) + { + foreach (var step in deploymentProcess.Steps) + { + PropertyValueResource targetRolesProperty; + if (step.Properties.TryGetValue("Octopus.Action.TargetRoles", out targetRolesProperty)) + { + var targetRoles = targetRolesProperty.Value.Split(','); + if (roles.Intersect(targetRoles).Any()) + { + return true; + } + } + } + + return false; + } } } \ No newline at end of file diff --git a/nautilus/Helpers.cs b/nautilus/Helpers.cs deleted file mode 100644 index 14ab699..0000000 --- a/nautilus/Helpers.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Octopus.Client.Model; - -namespace Nautilus -{ - public static class Helpers - { - public static bool HasAnyRole(this DeploymentProcessResource deploymentProcess, IEnumerable roles) - { - foreach (var step in deploymentProcess.Steps) - { - PropertyValueResource targetRolesProperty; - if (step.Properties.TryGetValue("Octopus.Action.TargetRoles", out targetRolesProperty)) - { - var targetRoles = targetRolesProperty.Value.Split(','); - if (roles.Intersect(targetRoles).Any()) - { - return true; - } - } - } - - return false; - } - } -} diff --git a/nautilus/InstallCommand.cs b/nautilus/InstallCommand.cs index eded34b..b06c90c 100644 --- a/nautilus/InstallCommand.cs +++ b/nautilus/InstallCommand.cs @@ -1,12 +1,72 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; using CommandLine; namespace Nautilus { - [Verb("install", HelpText = "Installs an Octopus tenticle on the target machine.")] + [Verb("install", HelpText = "Installs an Octopus tenticle on the local machine.")] public class InstallCommand : CommandBase { - public override int Run() - { + protected override int Run(OctopusProxy octopus) + { + var systemInfo = octopus.GetSystemInfo(); + var downloadVersion = systemInfo.Version; + if (Environment.Is64BitOperatingSystem) + { + downloadVersion += "-x64"; + } + var downloadUrl = $"http://download.octopusdeploy.com/octopus/Octopus.Tentacle.{downloadVersion}.msi"; + var filePath = $"{Path.GetTempPath()}Octopus.Tentacle.{downloadVersion}.msi"; + + Console.WriteLine($"Downloading installer from {downloadUrl} to {filePath}"); + using (var webClient = new WebClient()) + { + webClient.DownloadFile(downloadUrl, filePath); + } + + Console.WriteLine($"Installing tentacle from {filePath}"); + var processStartInfo = new ProcessStartInfo + { + FileName = "msiexec", + Arguments = $"/i \"{filePath}\" /quiet", + UseShellExecute = true + }; + using (var process = Process.Start(processStartInfo)) + { + var timeout = 120000; + if (!process.WaitForExit(timeout)) + { + Console.WriteLine($"Error: Operation timed out ({timeout} milliseconds) while waiting for tentacle installation to complete"); + return 1; + } + + if (process.ExitCode != 0) + { + Console.WriteLine($"Tentacle installation failed and exited with code {process.ExitCode}"); + return 1; + } + } + + Console.WriteLine("Tentacle installation completed successfully"); + + Console.WriteLine("Configuring tentacle"); + + //todo: configure + +// cd "C:\Program Files\Octopus Deploy\Tentacle" + + +// Tentacle.exe create-instance --instance "Tentacle" --config "C:\Octopus\Tentacle.config" --console +// Tentacle.exe new-certificate --instance "Tentacle" --if-blank --console +// Tentacle.exe configure --instance "Tentacle" --reset-trust --console +// Tentacle.exe configure --instance "Tentacle" --home "C:\Octopus" --app "C:\Octopus\Applications" --port "10933" --console +// Tentacle.exe configure --instance "Tentacle" --trust "YOUR_OCTOPUS_THUMBPRINT" --console +// "netsh" advfirewall firewall add rule "name=Octopus Deploy Tentacle" dir=in action=allow protocol=TCP localport=10933 +// Tentacle.exe register-with --instance "Tentacle" --server "http://YOUR_OCTOPUS" --apiKey="API-YOUR_API_KEY" --role "web-server" --environment "Staging" --comms-style TentaclePassive --console +// Tentacle.exe service --instance "Tentacle" --install --start --console + return 0; } } diff --git a/nautilus/OctopusProxy.cs b/nautilus/OctopusProxy.cs index f438b5d..5529933 100644 --- a/nautilus/OctopusProxy.cs +++ b/nautilus/OctopusProxy.cs @@ -3,6 +3,7 @@ using System.Linq; using Octopus.Client; using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; namespace Nautilus { @@ -15,10 +16,51 @@ public OctopusProxy(string serverAddress, string apiKey) _repository = new OctopusRepository(new OctopusServerEndpoint(serverAddress, apiKey)); } + + public SystemInfoResource GetSystemInfo() + { + var serverStatus = _repository.ServerStatus.GetServerStatus(); + return _repository.ServerStatus.GetSystemInfo(serverStatus); + } + + public CertificateResource GetGlobalCertificate() + { + return _repository.Certificates.Get("certificate-global"); + } public MachineResource GetMachine(string name) { - return _repository.Machines.FindOne(m => m.Name == name); + return _repository.Machines.FindByName(name); + } + + public MachineResource CreateMachine(string name, string thumbprint, string hostname, int port, IEnumerable environmentNames, IEnumerable roles) + { + var machine = new MachineResource(); + + machine.Name = name; + + var environments = _repository.Environments.FindByNames(environmentNames); + foreach (var environment in environments) + { + machine.EnvironmentIds.Add(environment.Id); + } + + foreach (var role in roles) + { + machine.Roles.Add(role); + } + + var endpoint = new ListeningTentacleEndpointResource(); + endpoint.Uri = $"https://{hostname}:{port}"; + endpoint.Thumbprint = thumbprint; + machine.Endpoint = endpoint; + + return _repository.Machines.Create(machine); + } + + public void DeleteMachine(MachineResource machine) + { + _repository.Machines.Delete(machine); } public IEnumerable GetProjects() diff --git a/nautilus/Program.cs b/nautilus/Program.cs index 822c143..0db83d9 100644 --- a/nautilus/Program.cs +++ b/nautilus/Program.cs @@ -1,11 +1,19 @@ -using CommandLine; +using System.Net; +using CommandLine; namespace Nautilus { public class Program { public static int Main(string[] args) - { + { + // Enable TLS 1.2 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Ssl3 + | SecurityProtocolType.Tls + | SecurityProtocolType.Tls11 + | SecurityProtocolType.Tls12; + return CommandLine.Parser.Default.ParseArguments(args) .MapResult( (DeployCommand command) => command.Run(), diff --git a/nautilus/RegisterCommand.cs b/nautilus/RegisterCommand.cs index fe6a79c..d7eeb08 100644 --- a/nautilus/RegisterCommand.cs +++ b/nautilus/RegisterCommand.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using CommandLine; namespace Nautilus @@ -5,8 +7,49 @@ namespace Nautilus [Verb("register", HelpText = "Registers the target machine with Octopus.")] public class RegisterCommand : CommandBase { - public override int Run() - { + [Option('n', "name", Required = false, HelpText = "The target machine name.")] + public string MachineName { get; set; } + + [Option('t', "thumbprint", Required = false, HelpText = "The Octopus server certificate thumbprint. Defaults to global certificate.")] + public string Thumbprint { get; set; } + + [Option('h', "host", Required = false, HelpText = "The target machine tentacle host name.")] + public string HostName { get; set; } + + [Option('p', "port", Required = false, HelpText = "The target machine tentacle port.")] + public int? Port { get; set; } + + [Option('e', "environments", Required = true, HelpText = "The environment names of the target machine.")] + public IList Environments { get; set; } + + [Option('r', "roles", Required = true, HelpText = "The roles of the target machine.")] + public IList Roles { get; set; } + + protected override int Run(OctopusProxy octopus) + { + var machineName = MachineName ?? Environment.MachineName; + + var machine = octopus.GetMachine(machineName); + if (machine != null) + { + Console.WriteLine($"The target machine ({machineName}) is already registered with Octopus ({OctopusServerAddress})"); + return 0; + } + + var hostName = HostName ?? Environment.MachineName; + var port = Port ?? 10933; + + var thumbprint = Thumbprint; + if (thumbprint == null) + { + var certicate = octopus.GetGlobalCertificate(); + thumbprint = certicate.Thumbprint; + } + + machine = octopus.CreateMachine(machineName, thumbprint, hostName, port, Environments, Roles); + + Console.WriteLine($"Target machine ({machine.Name}) registered successfully"); + return 0; } } diff --git a/nautilus/UnregisterCommand.cs b/nautilus/UnregisterCommand.cs index b33cba2..325abb2 100644 --- a/nautilus/UnregisterCommand.cs +++ b/nautilus/UnregisterCommand.cs @@ -1,3 +1,4 @@ +using System; using CommandLine; namespace Nautilus @@ -5,8 +6,24 @@ namespace Nautilus [Verb("unregister", HelpText = "Unregisters the target machine from Octopus.")] public class UnregisterCommand : CommandBase { - public override int Run() + [Option('n', "name", Required = false, HelpText = "The target machine name.")] + public string MachineName { get; set; } + + protected override int Run(OctopusProxy octopus) { + var machineName = MachineName ?? Environment.MachineName; + + var machine = octopus.GetMachine(machineName); + if (machine == null) + { + Console.WriteLine($"The target machine ({machineName}) is not registered with Octopus ({OctopusServerAddress})"); + return 0; + } + + octopus.DeleteMachine(machine); + + Console.WriteLine($"Target machine ({machine.Name}) unregistered successfully"); + return 0; } }