diff --git a/source/Octo.Tests/Commands/CleanWorkerPoolCommandFixture.cs b/source/Octo.Tests/Commands/CleanWorkerPoolCommandFixture.cs new file mode 100644 index 000000000..3574635c9 --- /dev/null +++ b/source/Octo.Tests/Commands/CleanWorkerPoolCommandFixture.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json; +using NSubstitute; +using NUnit.Framework; +using Octopus.Cli.Commands.WorkerPools; +using Octopus.Cli.Infrastructure; +using Octopus.Client.Model; + +namespace Octopus.Cli.Tests.Commands +{ + [TestFixture] + public class CleanWorkerPoolCommandFixture : ApiCommandFixtureBase + { + [SetUp] + public void SetUp() + { + cleanPoolCommand = new CleanWorkerPoolCommand(RepositoryFactory, FileSystem, ClientFactory, CommandOutputProvider); + Repository.Client.RootDocument.Version = "2018.6.0"; + } + + CleanWorkerPoolCommand cleanPoolCommand; + + [Test] + public void ShouldCleanPool() + { + CommandLineArgs.Add("-workerpool=SomePool"); + CommandLineArgs.Add("-health-status=Unhealthy"); + + Repository.WorkerPools.FindByName("SomePool").Returns( + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"} + ); + + var workerList = MakeWorkerList(2, + new List + { + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001") + }); + + Repository.Workers.FindMany(Arg.Any>()).Returns(workerList); + + cleanPoolCommand.Execute(CommandLineArgs.ToArray()).GetAwaiter().GetResult(); + + LogLines.Should().Contain(string.Format("Found {0} machines in {1} with the status {2}", workerList.Count, "SomePool", MachineModelHealthStatus.Unhealthy.ToString())); + + LogLines.Should().Contain(string.Format("Deleting {0} {1} (ID: {2})", workerList[0].Name, workerList[0].Status, workerList[0].Id)); + Repository.Workers.Received().Delete(workerList[0]); + + LogLines.Should().Contain(string.Format("Deleting {0} {1} (ID: {2})", workerList[1].Name, workerList[1].Status, workerList[1].Id)); + Repository.Workers.Received().Delete(workerList[1]); + } + + [Test] + public void ShouldRemoveMachinesBelongingToMultiplePoolsInsteadOfDeleting() + { + CommandLineArgs.Add("-workerpool=SomePool"); + CommandLineArgs.Add("-health-status=Unhealthy"); + + Repository.WorkerPools.FindByName("SomePool").Returns( + new WorkerPoolResource { Name = "SomePool", Id = "WorkerPools-001"} + ); + + var workerList = MakeWorkerList(2, + new List + { + new ReferenceCollection(new List { "WorkerPools-001", "WorkerPools-002" }), + new ReferenceCollection("WorkerPools-001") + }); + + Repository.Workers.FindMany(Arg.Any>()).Returns(workerList); + + cleanPoolCommand.Execute(CommandLineArgs.ToArray()).GetAwaiter().GetResult(); + + LogLines.Should().Contain(string.Format("Found {0} machines in {1} with the status {2}", workerList.Count, "SomePool", MachineModelHealthStatus.Unhealthy.ToString())); + LogLines.Should().Contain("Note: Some of these machines belong to multiple pools. Instead of being deleted, these machines will be removed from the SomePool pool."); + + LogLines.Should().Contain($"Removing {workerList[0].Name} {workerList[0].Status} (ID: {workerList[0].Id}) from SomePool"); + Assert.That(workerList[0].WorkerPoolIds.Count, Is.EqualTo(1), "The machine should have been removed from the SomePool pool."); + Repository.Workers.Received().Modify(workerList[0]); + + LogLines.Should().Contain(string.Format("Deleting {0} {1} (ID: {2})", workerList[1].Name, workerList[1].Status, workerList[1].Id)); + Repository.Workers.Received().Delete(workerList[1]); + } + + [Test] + public void ShouldNotCleanPoolWithMissingPoolArgs() + { + Func exec = () => cleanPoolCommand.Execute(CommandLineArgs.ToArray()); + exec.ShouldThrow() + .WithMessage("Please specify a worker pool name using the parameter: --workerpool=XYZ"); + } + + [Test] + public void ShouldNotCleanPoolWithMissingStatusArgs() + { + CommandLineArgs.Add("-workerpool=SomePool"); + Func exec = () => cleanPoolCommand.Execute(CommandLineArgs.ToArray()); + exec.ShouldThrow() + .WithMessage("Please specify a status using the parameter: --health-status"); + } + + [Test] + public void ShouldNotCleanIfPoolNotFound() + { + CommandLineArgs.Add("-workerpool=SomePool"); + CommandLineArgs.Add("-health-status=Unhealthy"); + + Func exec = () => cleanPoolCommand.Execute(CommandLineArgs.ToArray()); + exec.ShouldThrow() + .WithMessage("Could not find the specified worker pool; either it does not exist or you lack permissions to view it."); + } + + + [Test] + public async Task JsonOutput_ShouldBeWellFormed() + { + Repository.WorkerPools.FindByName("SomePool").Returns( + new WorkerPoolResource { Name = "SomePool", Id = "WorkerPools-001" }); + + CommandLineArgs.Add("--outputFormat=json"); + CommandLineArgs.Add($"--workerpool=SomePool"); + CommandLineArgs.Add("-health-status=Unhealthy"); + + var workerList = MakeWorkerList(2, + new List + { + new ReferenceCollection(new List { "WorkerPools-001", "WorkerPools-002" }), + new ReferenceCollection("WorkerPools-001") + }); + + Repository.Workers.FindMany(Arg.Any>()).Returns(workerList); + + await cleanPoolCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + string logoutput = LogOutput.ToString(); + Console.WriteLine(logoutput); + JsonConvert.DeserializeObject(logoutput); + Regex.Matches(logoutput, CleanWorkerPoolCommand.MachineAction.Deleted.ToString()).Count.Should() + .Be(1, "should only have one deleted machine"); + Regex.Matches(logoutput, CleanWorkerPoolCommand.MachineAction.RemovedFromPool.ToString()).Count.Should() + .Be(1, "should only have one machine removed from the environment"); + logoutput.Should().Contain(workerList[0].Name); + logoutput.Should().Contain(workerList[0].Id); + logoutput.Should().Contain(workerList[1].Name); + logoutput.Should().Contain(workerList[1].Id); + } + + + private List MakeWorkerList(int numWorkers, List pools) + { + var result = new List(); + for (int i = 0; i < numWorkers; i++) + { + result.Add( + new WorkerResource + { + Name = Guid.NewGuid().ToString(), + Id = "Machines-00" + i, + HealthStatus = MachineModelHealthStatus.Unhealthy, + WorkerPoolIds = pools[i] + }); + } + + return result; + } + } +} \ No newline at end of file diff --git a/source/Octo.Tests/Commands/CreateWorkerPoolCommandFixture.cs b/source/Octo.Tests/Commands/CreateWorkerPoolCommandFixture.cs new file mode 100644 index 000000000..952f97d79 --- /dev/null +++ b/source/Octo.Tests/Commands/CreateWorkerPoolCommandFixture.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json; +using NSubstitute; +using NUnit.Framework; +using Octopus.Cli.Commands.WorkerPool; +using Octopus.Cli.Tests.Commands; +using Octopus.Client.Model; + +namespace Octo.Tests.Commands +{ + public class CreateWorkerPoolCommandFixture : ApiCommandFixtureBase + { + private CreateWorkerPoolCommand createWorkerPoolCommand; + + [SetUp] + public void Setup() + { + createWorkerPoolCommand = new CreateWorkerPoolCommand(RepositoryFactory, FileSystem, ClientFactory, CommandOutputProvider); + } + + [Test] + public async Task DefaultOutput_CreateNewWorkerPool() + { + var newPool = Guid.NewGuid().ToString(); + CommandLineArgs.Add($"--name={newPool}"); + + Repository.WorkerPools.FindByName(Arg.Any()).Returns((WorkerPoolResource)null); + Repository.WorkerPools.Create(Arg.Any()) + .Returns(new WorkerPoolResource { Id = Guid.NewGuid().ToString(), Name = newPool }); + + await createWorkerPoolCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain($"Creating worker pool: {newPool}"); + } + + [Test] + public async Task JsonOutput_CreateNewWorkerPool() + { + var newPool = Guid.NewGuid().ToString(); + CommandLineArgs.Add($"--name={newPool}"); + CommandLineArgs.Add("--outputFormat=json"); + + Repository.WorkerPools.FindByName(Arg.Any()).Returns((WorkerPoolResource)null); + Repository.WorkerPools.Create(Arg.Any()) + .Returns(new WorkerPoolResource { Id = Guid.NewGuid().ToString(), Name = newPool}); + + await createWorkerPoolCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + var logoutput = LogOutput.ToString(); + Console.WriteLine(logoutput); + JsonConvert.DeserializeObject(logoutput); + logoutput.Should().Contain(newPool); + } + + } +} \ No newline at end of file diff --git a/source/Octo.Tests/Commands/ListWorkerPoolsCommandFixture.cs b/source/Octo.Tests/Commands/ListWorkerPoolsCommandFixture.cs new file mode 100644 index 000000000..4bef7d457 --- /dev/null +++ b/source/Octo.Tests/Commands/ListWorkerPoolsCommandFixture.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json; +using NSubstitute; +using NUnit.Framework; +using Octopus.Cli.Commands.WorkerPool; +using Octopus.Client.Model; + +namespace Octopus.Cli.Tests.Commands +{ + [TestFixture] + public class ListWorkerPoolsCommandFixture: ApiCommandFixtureBase + { + ListWorkerPoolsCommand listWorkerPoolsCommand; + + [SetUp] + public void SetUp() + { + listWorkerPoolsCommand = new ListWorkerPoolsCommand(RepositoryFactory, FileSystem, ClientFactory, CommandOutputProvider); + } + + [Test] + public async Task ShouldGetListOfWorkerPools() + { + SetupPools(); + + await listWorkerPoolsCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain("WorkerPools: 2"); + LogLines.Should().Contain(" - default (ID: defaultid)"); + LogLines.Should().Contain(" - windows (ID: windowsid)"); + } + + [Test] + public async Task JsonFormat_ShouldBeWellFormed() + { + SetupPools(); + + CommandLineArgs.Add("--outputFormat=json"); + await listWorkerPoolsCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + var logoutput = LogOutput.ToString(); + JsonConvert.DeserializeObject(logoutput); + logoutput.Should().Contain("defaultid"); + logoutput.Should().Contain("windowsid"); + } + + private void SetupPools() + { + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource() {Name = "default", Id = "defaultid"}, + new WorkerPoolResource() {Name = "windows", Id = "windowsid"} + }); + } + + } +} \ No newline at end of file diff --git a/source/Octo.Tests/Commands/ListWorkersCommandFixture.cs b/source/Octo.Tests/Commands/ListWorkersCommandFixture.cs new file mode 100644 index 000000000..d712639ee --- /dev/null +++ b/source/Octo.Tests/Commands/ListWorkersCommandFixture.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json; +using NSubstitute; +using NUnit.Framework; +using Octopus.Cli.Commands.Machine; +using Octopus.Cli.Infrastructure; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Cli.Tests.Commands +{ + [TestFixture] + public class ListWorkersCommandFixture : ApiCommandFixtureBase + { + const string MachineLogFormat = " - {0} {1} (ID: {2}) in {3}"; + + [SetUp] + public void SetUp() + { + listWorkersCommand = new ListWorkersCommand(RepositoryFactory, FileSystem, ClientFactory, CommandOutputProvider); + } + + ListWorkersCommand listWorkersCommand; + + [Test] + public async Task ShouldGetListOfWorkersWithPoolAndStatusArgs() + { + CommandLineArgs.Add("-workerpool=SomePool"); + CommandLineArgs.Add("-status=Offline"); + + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"} + }); + + var workerList = MakeWorkerList(3, + new List + { + MachineModelStatus.Online, + MachineModelStatus.Offline, + MachineModelStatus.Offline + }, + new List + { + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001") + }); + + Repository.Workers.FindMany(Arg.Any>()).Returns(workerList); + + await listWorkersCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain("Workers: 2"); + LogLines.Should().NotContain(string.Format(MachineLogFormat, workerList[0].Name, workerList[0].Status.ToString(), workerList[0].Id, "SomePool")); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[1].Name, workerList[1].Status.ToString(), workerList[1].Id, "SomePool")); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[2].Name, workerList[2].Status.ToString(), workerList[2].Id, "SomePool")); + } + + [Test] + public async Task ShouldGetListOfWorkersWithPoolArgs() + { + CommandLineArgs.Add("-workerpool=SomePool"); + + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"} + }); + + var workerList = MakeWorkerList(1, + new List + { + MachineModelStatus.Online + }, + new List + { + new ReferenceCollection("WorkerPools-001") + }); + Repository.Workers.FindMany(Arg.Any>()).Returns(workerList); + + await listWorkersCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain("Workers: 1"); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[0].Name, workerList[0].Status.ToString(), workerList[0].Id, "SomePool")); + } + + [Test] + public async Task ShouldGetListOfWorkerWithNoArgs() + { + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"}, + new WorkerPoolResource {Name = "SomeOtherPool", Id = "WorkerPools-002"} + }); + + var workerList = MakeWorkerList(3, + new List + { + MachineModelStatus.Online, + MachineModelStatus.Offline, + MachineModelStatus.Offline + }, + new List + { + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-002"), + new ReferenceCollection("WorkerPools-002") + }); + Repository.Workers.FindAll().Returns(workerList); + + await listWorkersCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain("Workers: 3"); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[0].Name, workerList[0].Status.ToString(), workerList[0].Id, "SomePool")); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[1].Name, workerList[1].Status.ToString(), workerList[1].Id, "SomeOtherPool")); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[2].Name, workerList[2].Status.ToString(), workerList[2].Id, "SomeOtherPool")); + } + + [Test] + public async Task ShouldGetListOfWorkersWithOfflineStatusArgs() + { + CommandLineArgs.Add("-status=Offline"); + + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"} + }); + + var workerList = MakeWorkerList(3, + new List + { + MachineModelStatus.Online, + MachineModelStatus.Online, + MachineModelStatus.Offline + }, + new List + { + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001") + }); + + Repository.Workers.FindAll().Returns(workerList); + + await listWorkersCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain("Workers: 1"); + LogLines.Should().NotContain(string.Format(MachineLogFormat, workerList[0].Name, workerList[0].Status.ToString(), workerList[0].Id, "SomePool")); + LogLines.Should().NotContain(string.Format(MachineLogFormat, workerList[1].Name, workerList[1].Status.ToString(), workerList[1].Id, "SomePool")); + LogLines.Should().Contain(string.Format(MachineLogFormat, workerList[2].Name, workerList[2].Status.ToString(), workerList[2].Id, "SomePool")); + } + + [Test] + public async Task ShouldSupportStateFilters() + { + CommandLineArgs.Add("--health-status=Healthy"); + CommandLineArgs.Add("--calamari-outdated=false"); + CommandLineArgs.Add("--tentacle-outdated=true"); + CommandLineArgs.Add("--disabled=true"); + Repository.Client.RootDocument.Version = "3.4.0"; + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"} + }); + + Repository.Workers.FindAll().Returns(new List + { + new WorkerResource { + Name = "PC0123", + Id = "Machines-001", + HealthStatus = MachineModelHealthStatus.Unavailable, + WorkerPoolIds = new ReferenceCollection("WorkerPools-001") + }, + new WorkerResource { + Name = "PC01466", + Id = "Machines-002", + HealthStatus = MachineModelHealthStatus.Healthy, + IsDisabled = true, + HasLatestCalamari = true, + Endpoint = new ListeningTentacleEndpointResource() {TentacleVersionDetails = new TentacleDetailsResource { UpgradeSuggested = true } }, + WorkerPoolIds = new ReferenceCollection("WorkerPools-001") + }, + new WorkerResource { + Name = "PC01467", + Id = "Machines-003", + HealthStatus = MachineModelHealthStatus.Healthy, + IsDisabled = true, + HasLatestCalamari = true, + Endpoint = new ListeningTentacleEndpointResource() {TentacleVersionDetails = new TentacleDetailsResource { UpgradeSuggested = false } }, + WorkerPoolIds = new ReferenceCollection("WorkerPools-001") + }, + new WorkerResource { + Name = "PC01468", + Id = "Machines-004", + HealthStatus = MachineModelHealthStatus.Healthy, + IsDisabled = true, + WorkerPoolIds = new ReferenceCollection("WorkerPools-001"), + HasLatestCalamari = false + }, + new WorkerResource { + Name = "PC01999", + Id = "Machines-005", + HealthStatus = MachineModelHealthStatus.Healthy, + IsDisabled = false, + WorkerPoolIds = new ReferenceCollection("WorkerPools-001")} + }); + + await listWorkersCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + LogLines.Should().Contain("Workers: 1"); + LogLines.Should().Contain(string.Format(MachineLogFormat, "PC01466", "Healthy - Disabled", "Machines-002", "SomePool")); + } + + + [Test] + public async Task JsonFormat_ShouldBeWellFormed() + { + CommandLineArgs.Add("--outputFormat=json"); + CommandLineArgs.Add("-status=Online"); + + Repository.WorkerPools.FindAll().Returns(new List + { + new WorkerPoolResource {Name = "SomePool", Id = "WorkerPools-001"} + }); + + var workerList = MakeWorkerList(3, + new List + { + MachineModelStatus.Online, + MachineModelStatus.Online, + MachineModelStatus.Offline + }, + new List + { + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001"), + new ReferenceCollection("WorkerPools-001") + }); + + Repository.Workers.FindAll().Returns(workerList); + + await listWorkersCommand.Execute(CommandLineArgs.ToArray()).ConfigureAwait(false); + + var logoutput = LogOutput.ToString(); + JsonConvert.DeserializeObject(logoutput); + logoutput.Should().Contain(workerList[0].Name); + logoutput.Should().Contain(workerList[1].Name); + logoutput.Should().NotContain(workerList[2].Name); + } + + + private List MakeWorkerList(int numWorkers, List statuses, + List pools) + { + var result = new List(); + for (int i = 0; i < numWorkers; i++) + { + result.Add( + new WorkerResource + { + Name = Guid.NewGuid().ToString(), + Id = "Machines-00" + i, + Status = statuses[i], + WorkerPoolIds = pools[i] + }); + } + + return result; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Cli/Commands/HealthStatusProvider.cs b/source/Octopus.Cli/Commands/HealthStatusProvider.cs index 818bdc5c1..22822ea0a 100644 --- a/source/Octopus.Cli/Commands/HealthStatusProvider.cs +++ b/source/Octopus.Cli/Commands/HealthStatusProvider.cs @@ -11,7 +11,7 @@ namespace Octopus.Cli.Commands { /// - /// This class exists to provide backwards compataility to the pre 3.4.0 changes to machine state. + /// This class exists to provide backwards compataility to the pre 3.4.0 changes to machine state. /// As of 3.4.0 the enum has been marked as obselete to be replaced with /// public class HealthStatusProvider @@ -61,7 +61,7 @@ void ValidateOptions() $"The following health status value is unknown: {string.Join(", ", missingHealthStatuses)}. Please choose from {string.Join(", ", HealthStatusNames)}"); } - public string GetStatus(MachineResource machineResource) + public string GetStatus(MachineBasedResource machineResource) { if (IsHealthStatusPendingDeprication) { @@ -76,14 +76,14 @@ public string GetStatus(MachineResource machineResource) private bool IsHealthStatusPendingDeprication { get; } - public IEnumerable Filter(IEnumerable machines) + public IEnumerable Filter(IEnumerable machines) where TMachineResource : MachineBasedResource { machines = FilterByProvidedStatus(machines); machines = FilterByProvidedHealthStatus(machines); return machines; } - IEnumerable FilterByProvidedStatus(IEnumerable machines) + IEnumerable FilterByProvidedStatus(IEnumerable machines) where TMachineResource : MachineBasedResource { var statusFilter = new List(); if (statuses.Count > 0) @@ -102,7 +102,7 @@ IEnumerable FilterByProvidedStatus(IEnumerable : machines; } - IEnumerable FilterByProvidedHealthStatus(IEnumerable machines) + IEnumerable FilterByProvidedHealthStatus(IEnumerable machines) where TMachineResource : MachineBasedResource { var statusFilter = new List(); if (healthStatuses.Count > 0) diff --git a/source/Octopus.Cli/Commands/Machine/ListWorkersCommand.cs b/source/Octopus.Cli/Commands/Machine/ListWorkersCommand.cs new file mode 100644 index 000000000..f63f11d51 --- /dev/null +++ b/source/Octopus.Cli/Commands/Machine/ListWorkersCommand.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Cli.Infrastructure; +using Octopus.Cli.Repositories; +using Octopus.Cli.Util; +using Octopus.Client; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Cli.Commands.Machine +{ + [Command("list-workers", Description = "Lists all workers")] + public class ListWorkersCommand : ApiCommand, ISupportFormattedOutput + { + readonly HashSet pools = new HashSet(StringComparer.OrdinalIgnoreCase); + readonly HashSet statuses = new HashSet(StringComparer.OrdinalIgnoreCase); + readonly HashSet healthStatuses = new HashSet(StringComparer.OrdinalIgnoreCase); + private HealthStatusProvider provider; + List workerpoolResources; + IEnumerable workerpoolWorkers; + private bool? isDisabled; + private bool? isCalamariOutdated; + private bool? isTentacleOutdated; + + public ListWorkersCommand(IOctopusAsyncRepositoryFactory repositoryFactory, IOctopusFileSystem fileSystem, IOctopusClientFactory clientFactory, ICommandOutputProvider commandOutputProvider) + : base(clientFactory, repositoryFactory, fileSystem, commandOutputProvider) + { + var options = Options.For("Listing Workers"); + options.Add("workerpool=", "Name of a worker pool to filter by. Can be specified many times.", v => pools.Add(v)); + options.Add("status=", $"[Optional] Status of Machines filter by ({string.Join(", ", HealthStatusProvider.StatusNames)}). Can be specified many times.", v => statuses.Add(v)); + options.Add("health-status=|healthstatus=", $"[Optional] Health status of Machines filter by ({string.Join(", ", HealthStatusProvider.HealthStatusNames)}). Can be specified many times.", v => healthStatuses.Add(v)); + options.Add("disabled=", "[Optional] Disabled status filter of Machine.", v => SetFlagState(v, ref isDisabled)); + options.Add("calamari-outdated=", "[Optional] State of Calamari to filter. By default ignores Calamari state.", v => SetFlagState(v, ref isCalamariOutdated)); + options.Add("tentacle-outdated=", "[Optional] State of Tentacle version to filter. By default ignores Tentacle state", v => SetFlagState(v, ref isTentacleOutdated)); + } + + public async Task Request() + { + provider = new HealthStatusProvider(Repository, statuses, healthStatuses, commandOutputProvider); + + workerpoolResources = await GetPools().ConfigureAwait(false); + + workerpoolWorkers = await FilterByWorkerPools(workerpoolResources).ConfigureAwait(false); + workerpoolWorkers = FilterByState(workerpoolWorkers, provider); + } + + public void PrintDefaultOutput() + { + LogFilteredMachines(workerpoolWorkers, provider, workerpoolResources); + } + + public void PrintJsonOutput() + { + commandOutputProvider.Json(workerpoolWorkers.Select(machine => new + { + machine.Id, + machine.Name, + Status = provider.GetStatus(machine), + WorkerPools = machine.WorkerPoolIds.Select(id => workerpoolResources.First(e => e.Id == id).Name) + .ToArray() + })); + } + + private void LogFilteredMachines(IEnumerable poolMachines, HealthStatusProvider provider, List poolResources) + { + var orderedMachines = poolMachines.OrderBy(m => m.Name).ToList(); + commandOutputProvider.Information("Workers: {Count}", orderedMachines.Count); + foreach (var machine in orderedMachines) + { + commandOutputProvider.Information(" - {Machine:l} {Status:l} (ID: {MachineId:l}) in {WorkerPool:l}", machine.Name, provider.GetStatus(machine), machine.Id, + string.Join(" and ", machine.WorkerPoolIds.Select(id => poolResources.First(e => e.Id == id).Name))); + } + } + + private Task> GetPools() + { + commandOutputProvider.Debug("Loading pools..."); + return Repository.WorkerPools.FindAll(); + } + + private IEnumerable FilterByState(IEnumerable poolMachines, HealthStatusProvider provider) + { + poolMachines = provider.Filter(poolMachines); + + if (isDisabled.HasValue) + { + poolMachines = poolMachines.Where(m => m.IsDisabled == isDisabled.Value); + } + if (isCalamariOutdated.HasValue) + { + poolMachines = poolMachines.Where(m => m.HasLatestCalamari == !isCalamariOutdated.Value); + } + if (isTentacleOutdated.HasValue) + { + poolMachines = + poolMachines.Where( + m => + (m.Endpoint as ListeningTentacleEndpointResource)?.TentacleVersionDetails.UpgradeSuggested == + isTentacleOutdated.Value); + } + return poolMachines; + } + + private Task> FilterByWorkerPools(List poolResources) + { + var poolsToInclude = poolResources.Where(e => pools.Contains(e.Name, StringComparer.OrdinalIgnoreCase)).ToList(); + var missingPools = pools.Except(poolsToInclude.Select(e => e.Name), StringComparer.OrdinalIgnoreCase).ToList(); + if (missingPools.Any()) + throw new CouldNotFindException("pools(s) named", string.Join(", ", missingPools)); + + + var poolsFilter = poolsToInclude.Select(p => p.Id).ToList(); + + commandOutputProvider.Debug("Loading workers..."); + if (poolsFilter.Count > 0) + { + commandOutputProvider.Debug("Loading machines from {WorkerPools:l}...", string.Join(", ", poolsToInclude.Select(e => e.Name))); + return + Repository.Workers.FindMany( + x => { return x.WorkerPoolIds.Any(poolId => poolsFilter.Contains(poolId)); }); + } + else + { + commandOutputProvider.Debug("Loading workers from all pools..."); + return Repository.Workers.FindAll(); + } + } + } +} \ No newline at end of file diff --git a/source/Octopus.Cli/Commands/WorkerPool/CleanWorkerPoolCommand.cs b/source/Octopus.Cli/Commands/WorkerPool/CleanWorkerPoolCommand.cs new file mode 100644 index 000000000..de3c97699 --- /dev/null +++ b/source/Octopus.Cli/Commands/WorkerPool/CleanWorkerPoolCommand.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Cli.Infrastructure; +using Octopus.Cli.Repositories; +using Octopus.Cli.Util; +using Octopus.Client; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Cli.Commands.WorkerPools +{ + [Command("clean-workerpool", Description = "Cleans all Offline Workers from a WorkerPool")] + public class CleanWorkerPoolCommand : ApiCommand, ISupportFormattedOutput + { + string poolName; + readonly HashSet healthStatuses = new HashSet(StringComparer.OrdinalIgnoreCase); + private bool? isDisabled; + private bool? isCalamariOutdated; + private bool? isTentacleOutdated; + WorkerPoolResource workerPoolResource; + IEnumerable machines; + List commandResults = new List(); + + + public CleanWorkerPoolCommand(IOctopusAsyncRepositoryFactory repositoryFactory, IOctopusFileSystem fileSystem, IOctopusClientFactory clientFactory, ICommandOutputProvider commandOutputProvider) + : base(clientFactory, repositoryFactory, fileSystem, commandOutputProvider) + { + var options = Options.For("WorkerPool Cleanup"); + options.Add("workerpool=", "Name of a worker pool to clean up.", v => poolName = v); + options.Add("health-status=", $"Health status of Workers to clean up ({string.Join(", ", HealthStatusProvider.HealthStatusNames)}). Can be specified many times.", v => healthStatuses.Add(v)); + options.Add("disabled=", "[Optional] Disabled status filter of Worker to clean up.", v => SetFlagState(v, ref isDisabled)); + options.Add("calamari-outdated=", "[Optional] State of Calamari to clean up. By default ignores Calamari state.", v => SetFlagState(v, ref isCalamariOutdated)); + options.Add("tentacle-outdated=", "[Optional] State of Tentacle version to clean up. By default ignores Tentacle state", v => SetFlagState(v, ref isTentacleOutdated)); + } + + public async Task Request() + { + if (string.IsNullOrWhiteSpace(poolName)) + throw new CommandException("Please specify a worker pool name using the parameter: --workerpool=XYZ"); + if (!healthStatuses.Any()) + throw new CommandException("Please specify a status using the parameter: --health-status"); + + workerPoolResource = await GetWorkerPool().ConfigureAwait(false); + + machines = await FilterByWorkerPool(workerPoolResource).ConfigureAwait(false); + machines = FilterByState(machines); + + await CleanUpPool(machines.ToList(), workerPoolResource); + } + + private async Task CleanUpPool(List filteredMachines, WorkerPoolResource poolResource) + { + commandOutputProvider.Information("Found {MachineCount} machines in {WorkerPool:l} with the status {Status:l}", filteredMachines.Count, poolResource.Name, GetStateFilterDescription()); + + if (filteredMachines.Any(m => m.WorkerPoolIds.Count > 1)) + { + commandOutputProvider.Information("Note: Some of these machines belong to multiple pools. Instead of being deleted, these machines will be removed from the {WorkerPool:l} pool.", poolResource.Name); + } + + foreach (var machine in filteredMachines) + { + MachineResult result = new MachineResult + { + Machine = machine + }; + // If the machine belongs to more than one pool, we should remove the machine from the pool rather than delete it altogether. + if (machine.WorkerPoolIds.Count > 1) + { + commandOutputProvider.Information("Removing {Machine:l} {Status} (ID: {Id:l}) from {WorkerPool:l}", machine.Name, machine.Status, machine.Id, + poolResource.Name); + machine.WorkerPoolIds.Remove(poolResource.Id); + await Repository.Workers.Modify(machine).ConfigureAwait(false); + result.Action = MachineAction.RemovedFromPool; + } + else + { + commandOutputProvider.Information("Deleting {Machine:l} {Status} (ID: {Id:l})", machine.Name, machine.Status, machine.Id); + await Repository.Workers.Delete(machine).ConfigureAwait(false); + result.Action = MachineAction.Deleted; + } + + this.commandResults.Add(result); + } + } + + private IEnumerable FilterByState(IEnumerable workers) + { + var provider = new HealthStatusProvider(Repository, new HashSet(StringComparer.OrdinalIgnoreCase), healthStatuses, commandOutputProvider); + workers = provider.Filter(workers); + + if (isDisabled.HasValue) + { + workers = workers.Where(m => m.IsDisabled == isDisabled.Value); + } + if (isCalamariOutdated.HasValue) + { + workers = workers.Where(m => m.HasLatestCalamari == !isCalamariOutdated.Value); + } + if (isTentacleOutdated.HasValue) + { + workers = workers.Where(m => (m.Endpoint as ListeningTentacleEndpointResource)?.TentacleVersionDetails.UpgradeSuggested == isTentacleOutdated.Value); + } + return workers; + } + + private string GetStateFilterDescription() + { + var description = string.Join(",", healthStatuses); + + if (isDisabled.HasValue) + { + description += isDisabled.Value ? "and disabled" : "and not disabled"; + } + + if (isCalamariOutdated.HasValue) + { + description += $" and its Calamari version {(isCalamariOutdated.Value ? "" : "not")}out of date"; + } + + if (isTentacleOutdated.HasValue) + { + description += $" and its Tentacle version {(isTentacleOutdated.Value ? "" : "not")}out of date"; + } + + return description; + } + + private Task> FilterByWorkerPool(WorkerPoolResource poolResource) + { + commandOutputProvider.Debug("Loading workers..."); + return Repository.Workers.FindMany(x => x.WorkerPoolIds.Any(poolId => poolId == poolResource.Id)); + } + + private async Task GetWorkerPool() + { + commandOutputProvider.Debug("Loading worker pools..."); + var poolResource = await Repository.WorkerPools.FindByName(poolName).ConfigureAwait(false); + if (poolResource == null) + { + throw new CouldNotFindException("the specified worker pool"); + } + return poolResource; + } + + public void PrintDefaultOutput() + { + + } + + public void PrintJsonOutput() + { + commandOutputProvider.Json(commandResults.Select(x =>new + { + Machine = new { x.Machine.Id,x.Machine.Name, x.Machine.Status }, + Environment = x.Action == MachineAction.RemovedFromPool ? new { workerPoolResource.Id, workerPoolResource.Name } : null, + Action = x.Action.ToString() + })); + } + + public enum MachineAction + { + RemovedFromPool, + Deleted + } + + private class MachineResult + { + public MachineBasedResource Machine { get; set; } + public MachineAction Action { get; set; } + } + } +} \ No newline at end of file diff --git a/source/Octopus.Cli/Commands/WorkerPool/CreateWorkerPoolCommand.cs b/source/Octopus.Cli/Commands/WorkerPool/CreateWorkerPoolCommand.cs new file mode 100644 index 000000000..dd4fd6896 --- /dev/null +++ b/source/Octopus.Cli/Commands/WorkerPool/CreateWorkerPoolCommand.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Octopus.Cli.Infrastructure; +using Octopus.Cli.Repositories; +using Octopus.Cli.Util; +using Octopus.Client; +using Octopus.Client.Model; + +namespace Octopus.Cli.Commands.WorkerPool +{ + [Command("create-workerpool", Description = "Creates a pool for workers")] + public class CreateWorkerPoolCommand : ApiCommand, ISupportFormattedOutput + { + WorkerPoolResource pool; + + public CreateWorkerPoolCommand(IOctopusAsyncRepositoryFactory repositoryFactory, IOctopusFileSystem fileSystem, IOctopusClientFactory clientFactory, ICommandOutputProvider commandOutputProvider) + : base(clientFactory, repositoryFactory, fileSystem, commandOutputProvider) + { + var options = Options.For("WorkerPool creation"); + options.Add("name=", "The name of the worker pool", v => WorkerPoolName = v); + options.Add("ignoreIfExists", "If the pool already exists, an error will be returned. Set this flag to ignore the error.", v => IgnoreIfExists = true); + } + + public string WorkerPoolName { get; set; } + public bool IgnoreIfExists { get; set; } + + public async Task Request() + { + if (string.IsNullOrWhiteSpace(WorkerPoolName)) throw new CommandException("Please specify a worker pool name using the parameter: --name=XYZ"); + + pool = await Repository.WorkerPools.FindByName(WorkerPoolName).ConfigureAwait(false); + if (pool != null) + { + if (IgnoreIfExists) + { + commandOutputProvider.Information("The worker pool {WorkerPool:l} (ID {Id:l}) already exists", pool.Name, pool.Id); + return; + } + + throw new CommandException("The worker pool " + pool.Name + " (ID " + pool.Id + ") already exists"); + } + + commandOutputProvider.Information("Creating worker pool: {WorkerPool:l}", WorkerPoolName); + pool = await Repository.WorkerPools.Create(new WorkerPoolResource {Name = WorkerPoolName}).ConfigureAwait(false); + } + + public void PrintDefaultOutput() + { + commandOutputProvider.Information("WorkerPool created. ID: {Id:l}", pool.Id); + } + + public void PrintJsonOutput() + { + commandOutputProvider.Json(new + { + pool.Id, + pool.Name, + }); + } + } +} \ No newline at end of file diff --git a/source/Octopus.Cli/Commands/WorkerPool/ListWorkerPoolsCommand.cs b/source/Octopus.Cli/Commands/WorkerPool/ListWorkerPoolsCommand.cs new file mode 100644 index 000000000..92484884f --- /dev/null +++ b/source/Octopus.Cli/Commands/WorkerPool/ListWorkerPoolsCommand.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Cli.Infrastructure; +using Octopus.Cli.Repositories; +using Octopus.Cli.Util; +using Octopus.Client; +using Octopus.Client.Model; + +namespace Octopus.Cli.Commands.WorkerPool +{ + [Command("list-workerpools", Description = "List worker pools")] + public class ListWorkerPoolsCommand : ApiCommand, ISupportFormattedOutput + { + List pools; + + public ListWorkerPoolsCommand(IOctopusAsyncRepositoryFactory repositoryFactory, IOctopusFileSystem fileSystem, IOctopusClientFactory clientFactory, ICommandOutputProvider commandOutputProvider) + : base(clientFactory, repositoryFactory, fileSystem, commandOutputProvider) + { + } + + public async Task Request() + { + pools = await Repository.WorkerPools.FindAll().ConfigureAwait(false); + } + + public void PrintDefaultOutput() + { + commandOutputProvider.Information("WorkerPools: {Count}", pools.Count); + + foreach (var pool in pools) + { + commandOutputProvider.Information(" - {WorkerPools:l} (ID: {Id:l})", pool.Name, pool.Id); + } + } + + public void PrintJsonOutput() + { + commandOutputProvider.Json( + pools.Select(pool => new + { + pool.Id, + pool.Name + })); + } + } +} \ No newline at end of file diff --git a/source/Octopus.Cli/Exporters/ProjectExporter.cs b/source/Octopus.Cli/Exporters/ProjectExporter.cs index c07ba8f83..3bb4624ce 100644 --- a/source/Octopus.Cli/Exporters/ProjectExporter.cs +++ b/source/Octopus.Cli/Exporters/ProjectExporter.cs @@ -125,6 +125,28 @@ protected override async Task Export(Dictionary parameters) } } + Log.Debug("Finding all worker pools for deployment process..."); + var workerPools = new List(); + foreach (var step in deploymentProcess.Steps) + { + foreach (var action in step.Actions) + { + if (!string.IsNullOrWhiteSpace(action.WorkerPoolId)) + { + Log.Debug("Finding worker pool for action {ActionName:l}", action.Name); + var pool = await Repository.WorkerPools.Get(action.WorkerPoolId).ConfigureAwait(false); + + if (pool == null) + throw new CouldNotFindException("Worker pool feed for step", step.Name); + + if (workerPools.All(wp => wp.Id != pool.Id)) + { + workerPools.Add(new ReferenceDataItem(pool.Id, pool.Name)); + } + } + } + } + var libraryVariableSets = new List(); foreach (var libraryVariableSetId in project.IncludedLibraryVariableSetIds) { @@ -158,6 +180,7 @@ protected override async Task Export(Dictionary parameters) Lifecycle = lifecycle != null ? new ReferenceDataItem(lifecycle.Id, lifecycle.Name) : null, Channels = channels.ToList(), ChannelLifecycles = channelLifecycles, + WorkerPools = workerPools }; var metadata = new ExportMetadata diff --git a/source/Octopus.Cli/Importers/ProjectImporter.cs b/source/Octopus.Cli/Importers/ProjectImporter.cs index 31793938c..1c5142bd0 100644 --- a/source/Octopus.Cli/Importers/ProjectImporter.cs +++ b/source/Octopus.Cli/Importers/ProjectImporter.cs @@ -33,6 +33,7 @@ class ValidatedImportSettings : BaseValidatedImportSettings public IDictionary LibraryVariableSets { get; set; } public DeploymentProcessResource DeploymentProcess { get; set; } public IDictionary Environments { get; set; } + public IDictionary WorkerPools { get; set; } public IDictionary Machines { get; set; } public IDictionary Feeds { get; set; } public IDictionary Templates { get; set; } @@ -72,6 +73,7 @@ protected override async Task Validate(Dictionary paramDic var projectGroup = importedObject.ProjectGroup; var channels = importedObject.Channels; var channelLifecycles = importedObject.ChannelLifecycles; + var workerPools = importedObject.WorkerPools; var scopeValuesUsed = GetScopeValuesUsed(variableSet.Variables, deploymentProcess.Steps, variableSet.ScopeValues); @@ -82,8 +84,10 @@ protected override async Task Validate(Dictionary paramDic var libraryVariableSetChecksTask = CheckLibraryVariableSets(libVariableSets).ConfigureAwait(false); var projectGroupChecksTask = CheckProjectGroup(projectGroup).ConfigureAwait(false); var channelLifecycleChecksTask = CheckChannelLifecycles(channelLifecycles).ConfigureAwait(false); + var workerPoolChecksTask = CheckWorkerPoolsExist(workerPools).ConfigureAwait(false); var environmentChecks = await environmentChecksTask; + var workerPoolChecks = await workerPoolChecksTask; var machineChecks = await machineChecksTask; var feedChecks = await feedChecksTask; var templateChecks = await templateChecksTask; @@ -95,6 +99,7 @@ protected override async Task Validate(Dictionary paramDic errorList.AddRange( environmentChecks.MissingDependencyErrors + .Concat(workerPoolChecks.MissingDependencyErrors) .Concat(machineChecks.MissingDependencyErrors) .Concat(feedChecks.MissingDependencyErrors) .Concat(templateChecks.MissingDependencyErrors) @@ -109,6 +114,7 @@ protected override async Task Validate(Dictionary paramDic ProjectGroupId = projectGroupChecks.FoundDependencies.Values.FirstOrDefault()?.Id, LibraryVariableSets = libraryVariableSetChecks.FoundDependencies, Environments = environmentChecks.FoundDependencies, + WorkerPools = workerPoolChecks.FoundDependencies, Feeds = feedChecks.FoundDependencies, Templates = templateChecks.FoundDependencies, Machines = machineChecks.FoundDependencies, @@ -144,7 +150,7 @@ protected override async Task Import(Dictionary paramDictionary) var oldActionChannels = validatedImportSettings.DeploymentProcess.Steps.SelectMany(s => s.Actions).ToDictionary(x => x.Id, x => x.Channels.Clone()); - var importeDeploymentProcess = await ImportDeploymentProcess(validatedImportSettings.DeploymentProcess, importedProject, validatedImportSettings.Environments, validatedImportSettings.Feeds, validatedImportSettings.Templates).ConfigureAwait(false); + var importeDeploymentProcess = await ImportDeploymentProcess(validatedImportSettings.DeploymentProcess, importedProject, validatedImportSettings.Environments, validatedImportSettings.WorkerPools, validatedImportSettings.Feeds, validatedImportSettings.Templates).ConfigureAwait(false); var importedChannels = (await ImportProjectChannels(validatedImportSettings.Channels.ToList(), importedProject, validatedImportSettings.ChannelLifecycles).ConfigureAwait(false)) @@ -232,7 +238,7 @@ protected Dictionary> GetScopeValuesUsed(ILi { {ScopeField.Environment, new List()}, {ScopeField.Machine, new List()}, - {ScopeField.Channel, new List()}, + {ScopeField.Channel, new List()} }; foreach (var variable in variables) @@ -303,7 +309,7 @@ protected Dictionary> GetScopeValuesUsed(ILi return usedScopeValues; } - + async Task ImportVariableSets(VariableSetResource variableSet, ProjectResource importedProject, IDictionary environments, @@ -416,6 +422,7 @@ IList UpdateVariables(VariableSetResource variableSet, IDictio async Task ImportDeploymentProcess(DeploymentProcessResource deploymentProcess, ProjectResource importedProject, IDictionary environments, + IDictionary workerPools, IDictionary nugetFeeds, IDictionary actionTemplates) { @@ -450,6 +457,12 @@ async Task ImportDeploymentProcess(DeploymentProcessR action.Environments.Clear(); action.Environments.AddRange(newEnvironmentIds); + if (!string.IsNullOrWhiteSpace(action.WorkerPoolId)) + { + Log.Debug("Updating ID of Worker Pool"); + action.WorkerPoolId = workerPools[action.WorkerPoolId].Id; + } + // Make sure source channels are clear, will be added later action.Channels.Clear(); } @@ -624,6 +637,18 @@ protected async Task> CheckEnvironmentsEx return dependencies; } + protected async Task> CheckWorkerPoolsExist(List poolList) + { + Log.Debug("Checking that all worker pools exist"); + var dependencies = new CheckedReferences(); + foreach (var p in poolList) + { + var pool = await Repository.WorkerPools.FindByName(p.Name).ConfigureAwait(false); + dependencies.Register(p.Name, p.Id, pool); + } + return dependencies; + } + protected async Task> CheckChannelLifecycles(List channelLifecycles) { Log.Debug("Checking that all channel lifecycles exist"); diff --git a/source/Octopus.Cli/Model/ProjectExport.cs b/source/Octopus.Cli/Model/ProjectExport.cs index f9257512f..b90f2d54e 100644 --- a/source/Octopus.Cli/Model/ProjectExport.cs +++ b/source/Octopus.Cli/Model/ProjectExport.cs @@ -14,6 +14,7 @@ public class ProjectExport public List LibraryVariableSets { get; set; } public ReferenceDataItem Lifecycle { get; set; } public List Channels { get; set; } - public List ChannelLifecycles { get; set; } + public List ChannelLifecycles { get; set; } + public List WorkerPools { get; set; } } } \ No newline at end of file diff --git a/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj b/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj index af54be30b..957befcec 100644 --- a/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj +++ b/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj @@ -32,7 +32,7 @@ - + diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt index a10ccdbe3..b5fe57e6e 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt @@ -88,6 +88,8 @@ Octopus.Client Octopus.Client.Repositories.Async.IUserRolesRepository UserRoles { get; } Octopus.Client.Repositories.Async.IUserRepository Users { get; } Octopus.Client.Repositories.Async.IVariableSetRepository VariableSets { get; } + Octopus.Client.Repositories.Async.IWorkerPoolRepository WorkerPools { get; } + Octopus.Client.Repositories.Async.IWorkerRepository Workers { get; } } interface IOctopusClientFactory { @@ -173,6 +175,8 @@ Octopus.Client Octopus.Client.Repositories.Async.IUserRolesRepository UserRoles { get; } Octopus.Client.Repositories.Async.IUserRepository Users { get; } Octopus.Client.Repositories.Async.IVariableSetRepository VariableSets { get; } + Octopus.Client.Repositories.Async.IWorkerPoolRepository WorkerPools { get; } + Octopus.Client.Repositories.Async.IWorkerRepository Workers { get; } } class OctopusClientFactory Octopus.Client.IOctopusClientFactory @@ -534,6 +538,27 @@ Octopus.Client.Editors.Async Octopus.Client.Editors.Async.VariableTemplateContainerEditor AddOrUpdateVariableTemplate(String, String, IDictionary) Octopus.Client.Editors.Async.VariableTemplateContainerEditor AddOrUpdateVariableTemplate(String, String, IDictionary, String, String) } + class WorkerEditor + Octopus.Client.Editors.Async.IResourceEditor + Octopus.Client.Editors.Async.IResourceBuilder + { + .ctor(Octopus.Client.Repositories.Async.IWorkerRepository) + Octopus.Client.Model.WorkerResource Instance { get; } + Task CreateOrModify(String, Octopus.Client.Model.Endpoints.EndpointResource, Octopus.Client.Model.WorkerPoolResource[]) + Octopus.Client.Editors.Async.WorkerEditor Customize(Action) + Task Save() + } + class WorkerPoolEditor + Octopus.Client.Editors.Async.IResourceEditor + Octopus.Client.Editors.Async.IResourceBuilder + { + .ctor(Octopus.Client.Repositories.Async.IWorkerPoolRepository) + Octopus.Client.Model.WorkerPoolResource Instance { get; } + Task CreateOrModify(String) + Task CreateOrModify(String, String) + Octopus.Client.Editors.Async.WorkerPoolEditor Customize(Action) + Task Save() + } } Octopus.Client.Exceptions { @@ -917,7 +942,9 @@ Octopus.Client.Model static System.String EnvironmentId static System.String MachineIds static System.String MachineTimeout + static System.String RestrictedTo static System.String Timeout + static System.String WorkerpoolId } abstract class Arguments { @@ -933,6 +960,8 @@ Octopus.Client.Model { static System.String EnvironmentId static System.String MachineIds + static System.String RestrictedTo + static System.String WorkerpoolId } abstract class Arguments { @@ -1078,12 +1107,15 @@ Octopus.Client.Model abstract class Health { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds static System.String MachineTimeout + static System.String RestrictedTo static System.String Timeout + static System.String WorkerpoolId } } abstract class Migration @@ -1110,6 +1142,13 @@ Octopus.Client.Model { static System.String Name } + abstract class TaskRestrictedTo + { + static System.String DeploymentTargets + static System.String Policies + static System.String Unrestricted + static System.String Workers + } abstract class TestAzureAccount { static System.String Name @@ -1137,10 +1176,13 @@ Octopus.Client.Model abstract class Upgrade { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds + static System.String RestrictedTo + static System.String WorkerpoolId } } } @@ -1479,6 +1521,7 @@ Octopus.Client.Model String Name { get; set; } IDictionary Properties { get; } Octopus.Client.Model.ReferenceCollection TenantTags { get; } + String WorkerPoolId { get; set; } Octopus.Client.Model.DeploymentActionResource ClearAllConditions() Octopus.Client.Model.DeploymentActionResource ForChannels(Octopus.Client.Model.ChannelResource[]) Octopus.Client.Model.DeploymentActionResource ForEnvironments(Octopus.Client.Model.EnvironmentResource[]) @@ -1699,30 +1742,20 @@ Octopus.Client.Model Boolean UseGuidedFailure { get; set; } } class EnvironmentsSummaryResource + Octopus.Client.Model.SummaryResourcesCombined { .ctor() List EnvironmentSummaries { get; set; } - Dictionary MachineEndpointSummaries { get; set; } - Dictionary MachineHealthStatusSummaries { get; set; } - String[] MachineIdsForCalamariUpgrade { get; set; } Dictionary MachineTenantSummaries { get; set; } Dictionary MachineTenantTagSummaries { get; set; } - Boolean TentacleUpgradesRequired { get; set; } - Int32 TotalDisabledMachines { get; set; } - Int32 TotalMachines { get; set; } } class EnvironmentSummaryResource + Octopus.Client.Model.SummaryResource { .ctor() Octopus.Client.Model.EnvironmentResource Environment { get; set; } - Dictionary MachineEndpointSummaries { get; set; } - Dictionary MachineHealthStatusSummaries { get; set; } - String[] MachineIdsForCalamariUpgrade { get; set; } Dictionary MachineTenantSummaries { get; set; } Dictionary MachineTenantTagSummaries { get; set; } - Boolean TentacleUpgradesRequired { get; set; } - Int32 TotalDisabledMachines { get; set; } - Int32 TotalMachines { get; set; } } class EventNotificationSubscription { @@ -1801,6 +1834,7 @@ Octopus.Client.Model { .ctor() Boolean IsBuiltInRepoSyncEnabled { get; set; } + Boolean IsBuiltInWorkerEnabled { get; set; } Boolean IsCommunityActionTemplatesEnabled { get; set; } Boolean IsMultiTenancyEnabled { get; set; } } @@ -1852,12 +1886,15 @@ Octopus.Client.Model abstract class Health { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds static System.String MachineTimeout + static System.String RestrictedTo static System.String Timeout + static System.String WorkerpoolId } } HealthCheckErrorHandling @@ -2023,6 +2060,24 @@ Octopus.Client.Model .ctor() Boolean UsingSecureConnection { get; set; } } + abstract class MachineBasedResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Model.Resource + { + Octopus.Client.Model.Endpoints.EndpointResource Endpoint { get; set; } + Boolean HasLatestCalamari { get; set; } + Octopus.Client.Model.MachineModelHealthStatus HealthStatus { get; set; } + Boolean IsDisabled { get; set; } + Boolean IsInProcess { get; set; } + String MachinePolicyId { get; set; } + String Name { get; set; } + Octopus.Client.Model.MachineModelStatus Status { get; set; } + String StatusSummary { get; set; } + String Thumbprint { get; set; } + String Uri { get; set; } + } class MachineCleanupPolicy { .ctor() @@ -2109,25 +2164,14 @@ Octopus.Client.Model Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource Octopus.Client.Extensibility.INamedResource - Octopus.Client.Model.Resource + Octopus.Client.Model.MachineBasedResource { .ctor() - Octopus.Client.Model.Endpoints.EndpointResource Endpoint { get; set; } Octopus.Client.Model.ReferenceCollection EnvironmentIds { get; set; } - Boolean HasLatestCalamari { get; set; } - Octopus.Client.Model.MachineModelHealthStatus HealthStatus { get; set; } - Boolean IsDisabled { get; set; } - Boolean IsInProcess { get; set; } - String MachinePolicyId { get; set; } - String Name { get; set; } Octopus.Client.Model.ReferenceCollection Roles { get; set; } - Octopus.Client.Model.MachineModelStatus Status { get; set; } - String StatusSummary { get; set; } Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } Octopus.Client.Model.ReferenceCollection TenantIds { get; set; } Octopus.Client.Model.ReferenceCollection TenantTags { get; set; } - String Thumbprint { get; set; } - String Uri { get; set; } Octopus.Client.Model.MachineResource AddOrUpdateEnvironments(Octopus.Client.Model.EnvironmentResource[]) Octopus.Client.Model.MachineResource AddOrUpdateRoles(String[]) Octopus.Client.Model.MachineResource AddOrUpdateTenants(Octopus.Client.Model.TenantResource[]) @@ -3042,6 +3086,26 @@ Octopus.Client.Model { Event = 0 } + class SummaryResource + { + .ctor() + Dictionary MachineEndpointSummaries { get; set; } + Dictionary MachineHealthStatusSummaries { get; set; } + String[] MachineIdsForCalamariUpgrade { get; set; } + Boolean TentacleUpgradesRequired { get; set; } + Int32 TotalDisabledMachines { get; set; } + Int32 TotalMachines { get; set; } + } + class SummaryResourcesCombined + { + .ctor() + Dictionary MachineEndpointSummaries { get; set; } + Dictionary MachineHealthStatusSummaries { get; set; } + String[] MachineIdsForCalamariUpgrade { get; set; } + Boolean TentacleUpgradesRequired { get; set; } + Int32 TotalDisabledMachines { get; set; } + Int32 TotalMachines { get; set; } + } class SupportsRestrictionAttribute Attribute { @@ -3157,6 +3221,13 @@ Octopus.Client.Model Nullable StartTime { get; set; } Octopus.Client.Model.TaskState State { get; set; } } + abstract class TaskRestrictedTo + { + static System.String DeploymentTargets + static System.String Policies + static System.String Unrestricted + static System.String Workers + } TaskState { Queued = 1 @@ -3296,10 +3367,13 @@ Octopus.Client.Model abstract class Upgrade { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds + static System.String RestrictedTo + static System.String WorkerpoolId } } class UserPermissionRestriction @@ -3460,6 +3534,42 @@ Octopus.Client.Model String ReferrerPolicy { get; set; } Octopus.Client.Model.XOptionsResource XOptions { get; set; } } + class WorkerPoolResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Model.Resource + { + .ctor() + String Description { get; set; } + Boolean IsDefault { get; set; } + String Name { get; set; } + Int32 SortOrder { get; set; } + } + class WorkerPoolsSummaryResource + Octopus.Client.Model.SummaryResourcesCombined + { + .ctor() + List WorkerPoolSummaries { get; set; } + } + class WorkerPoolSummaryResource + Octopus.Client.Model.SummaryResource + { + .ctor() + Octopus.Client.Model.WorkerPoolResource WorkerPool { get; set; } + } + class WorkerResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Model.MachineBasedResource + { + .ctor() + Octopus.Client.Model.ReferenceCollection WorkerPoolIds { get; set; } + Octopus.Client.Model.WorkerResource AddOrUpdateWorkerPools(Octopus.Client.Model.WorkerPoolResource[]) + Octopus.Client.Model.WorkerResource ClearWorkerPools() + Octopus.Client.Model.WorkerResource RemoveWorkerPool(Octopus.Client.Model.WorkerPoolResource) + } class X509Certificate { .ctor() @@ -4251,18 +4361,22 @@ Octopus.Client.Model.Versioning Octopus.Client.Operations { interface IRegisterMachineOperation + Octopus.Client.Operations.IRegisterMachineOperationBase + { + String[] EnvironmentNames { get; set; } + String[] Roles { get; set; } + Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } + String[] Tenants { get; set; } + String[] TenantTags { get; set; } + } + interface IRegisterMachineOperationBase { Boolean AllowOverwrite { get; set; } Octopus.Client.Model.CommunicationStyle CommunicationStyle { get; set; } - String[] EnvironmentNames { get; set; } String MachineName { get; set; } String MachinePolicy { get; set; } String ProxyName { get; set; } - String[] Roles { get; set; } Uri SubscriptionId { get; set; } - Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } - String[] Tenants { get; set; } - String[] TenantTags { get; set; } String TentacleHostname { get; set; } Int32 TentaclePort { get; set; } String TentacleThumbprint { get; set; } @@ -4270,22 +4384,35 @@ Octopus.Client.Operations Task ExecuteAsync(Octopus.Client.OctopusAsyncRepository) Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) } + interface IRegisterWorkerOperation + Octopus.Client.Operations.IRegisterMachineOperationBase + { + String[] WorkerPoolNames { get; set; } + } class RegisterMachineOperation + Octopus.Client.Operations.IRegisterMachineOperationBase Octopus.Client.Operations.IRegisterMachineOperation + Octopus.Client.Operations.RegisterMachineOperationBase { .ctor() + .ctor(Octopus.Client.IOctopusClientFactory) + String[] EnvironmentNames { get; set; } + String[] Roles { get; set; } + Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } + String[] Tenants { get; set; } + String[] TenantTags { get; set; } + Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) + } + abstract class RegisterMachineOperationBase + Octopus.Client.Operations.IRegisterMachineOperationBase + { .ctor(Octopus.Client.IOctopusClientFactory) Boolean AllowOverwrite { get; set; } Octopus.Client.Model.CommunicationStyle CommunicationStyle { get; set; } - String[] EnvironmentNames { get; set; } String MachineName { get; set; } String MachinePolicy { get; set; } String ProxyName { get; set; } - String[] Roles { get; set; } Uri SubscriptionId { get; set; } - Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } - String[] Tenants { get; set; } - String[] TenantTags { get; set; } String TentacleHostname { get; set; } Int32 TentaclePort { get; set; } String TentacleThumbprint { get; set; } @@ -4293,6 +4420,16 @@ Octopus.Client.Operations Task ExecuteAsync(Octopus.Client.OctopusAsyncRepository) Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) } + class RegisterWorkerOperation + Octopus.Client.Operations.IRegisterMachineOperationBase + Octopus.Client.Operations.IRegisterWorkerOperation + Octopus.Client.Operations.RegisterMachineOperationBase + { + .ctor() + .ctor(Octopus.Client.IOctopusClientFactory) + String[] WorkerPoolNames { get; set; } + Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) + } } Octopus.Client.Repositories.Async { @@ -4700,8 +4837,8 @@ Octopus.Client.Repositories.Async Task ExecuteBackup(String) Task ExecuteCalamariUpdate(String, String[]) Task ExecuteCommunityActionTemplatesSynchronisation(String) - Task ExecuteHealthCheck(String, Int32, Int32, String, String[]) - Task ExecuteTentacleUpgrade(String, String, String[]) + Task ExecuteHealthCheck(String, Int32, Int32, String, String[], String, String, String[]) + Task ExecuteTentacleUpgrade(String, String, String[], String, String, String[]) Task> GetAllActive(Int32) Task GetDetails(Octopus.Client.Model.TaskResource, Nullable, Nullable) Task> GetQueuedBehindTasks(Octopus.Client.Model.TaskResource) @@ -4779,6 +4916,35 @@ Octopus.Client.Repositories.Async { Task GetVariableNames(String, String[]) } + interface IWorkerPoolRepository + Octopus.Client.Repositories.Async.IFindByName + Octopus.Client.Repositories.Async.IPaginate + Octopus.Client.Repositories.Async.IGet + Octopus.Client.Repositories.Async.ICreate + Octopus.Client.Repositories.Async.IModify + Octopus.Client.Repositories.Async.IDelete + Octopus.Client.Repositories.Async.IGetAll + { + Task CreateOrModify(String) + Task CreateOrModify(String, String) + Task> GetMachines(Octopus.Client.Model.WorkerPoolResource, Nullable, Nullable, String, Nullable, String, String) + Task Sort(String[]) + Task Summary(String, String, String, Nullable, String, String, Nullable) + } + interface IWorkerRepository + Octopus.Client.Repositories.Async.IFindByName + Octopus.Client.Repositories.Async.IPaginate + Octopus.Client.Repositories.Async.IGet + Octopus.Client.Repositories.Async.ICreate + Octopus.Client.Repositories.Async.IModify + Octopus.Client.Repositories.Async.IDelete + { + Task CreateOrModify(String, Octopus.Client.Model.Endpoints.EndpointResource, Octopus.Client.Model.WorkerPoolResource[]) + Task Discover(String, Int32, Nullable) + Task> FindByThumbprint(String) + Task GetConnectionStatus(Octopus.Client.Model.WorkerResource) + Task> List(Int32, Nullable, String, String, String, Nullable, String, String, String) + } } Octopus.Client.Serialization { diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt index c066f6d82..082c0ee5f 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt @@ -95,6 +95,8 @@ Octopus.Client Octopus.Client.Repositories.Async.IUserRolesRepository UserRoles { get; } Octopus.Client.Repositories.Async.IUserRepository Users { get; } Octopus.Client.Repositories.Async.IVariableSetRepository VariableSets { get; } + Octopus.Client.Repositories.Async.IWorkerPoolRepository WorkerPools { get; } + Octopus.Client.Repositories.Async.IWorkerRepository Workers { get; } } interface IOctopusClient IDisposable @@ -173,6 +175,8 @@ Octopus.Client Octopus.Client.Repositories.IUserRolesRepository UserRoles { get; } Octopus.Client.Repositories.IUserRepository Users { get; } Octopus.Client.Repositories.IVariableSetRepository VariableSets { get; } + Octopus.Client.Repositories.IWorkerPoolRepository WorkerPools { get; } + Octopus.Client.Repositories.IWorkerRepository Workers { get; } } class OctopusAsyncClient Octopus.Client.IOctopusAsyncClient @@ -254,6 +258,8 @@ Octopus.Client Octopus.Client.Repositories.Async.IUserRolesRepository UserRoles { get; } Octopus.Client.Repositories.Async.IUserRepository Users { get; } Octopus.Client.Repositories.Async.IVariableSetRepository VariableSets { get; } + Octopus.Client.Repositories.Async.IWorkerPoolRepository WorkerPools { get; } + Octopus.Client.Repositories.Async.IWorkerRepository Workers { get; } } class OctopusClient Octopus.Client.IHttpOctopusClient @@ -352,6 +358,8 @@ Octopus.Client Octopus.Client.Repositories.IUserRolesRepository UserRoles { get; } Octopus.Client.Repositories.IUserRepository Users { get; } Octopus.Client.Repositories.IVariableSetRepository VariableSets { get; } + Octopus.Client.Repositories.IWorkerPoolRepository WorkerPools { get; } + Octopus.Client.Repositories.IWorkerRepository Workers { get; } } abstract class OctopusRepositoryExtensions { @@ -697,6 +705,27 @@ Octopus.Client.Editors Octopus.Client.Editors.VariableTemplateContainerEditor AddOrUpdateVariableTemplate(String, String, IDictionary) Octopus.Client.Editors.VariableTemplateContainerEditor AddOrUpdateVariableTemplate(String, String, IDictionary, String, String) } + class WorkerEditor + Octopus.Client.Editors.IResourceEditor + Octopus.Client.Editors.IResourceBuilder + { + .ctor(Octopus.Client.Repositories.IWorkerRepository) + Octopus.Client.Model.WorkerResource Instance { get; } + Octopus.Client.Editors.WorkerEditor CreateOrModify(String, Octopus.Client.Model.Endpoints.EndpointResource, Octopus.Client.Model.WorkerPoolResource[]) + Octopus.Client.Editors.WorkerEditor Customize(Action) + Octopus.Client.Editors.WorkerEditor Save() + } + class WorkerPoolEditor + Octopus.Client.Editors.IResourceEditor + Octopus.Client.Editors.IResourceBuilder + { + .ctor(Octopus.Client.Repositories.IWorkerPoolRepository) + Octopus.Client.Model.WorkerPoolResource Instance { get; } + Octopus.Client.Editors.WorkerPoolEditor CreateOrModify(String) + Octopus.Client.Editors.WorkerPoolEditor CreateOrModify(String, String) + Octopus.Client.Editors.WorkerPoolEditor Customize(Action) + Octopus.Client.Editors.WorkerPoolEditor Save() + } } Octopus.Client.Editors.Async { @@ -991,6 +1020,27 @@ Octopus.Client.Editors.Async Octopus.Client.Editors.Async.VariableTemplateContainerEditor AddOrUpdateVariableTemplate(String, String, IDictionary) Octopus.Client.Editors.Async.VariableTemplateContainerEditor AddOrUpdateVariableTemplate(String, String, IDictionary, String, String) } + class WorkerEditor + Octopus.Client.Editors.Async.IResourceEditor + Octopus.Client.Editors.Async.IResourceBuilder + { + .ctor(Octopus.Client.Repositories.Async.IWorkerRepository) + Octopus.Client.Model.WorkerResource Instance { get; } + Task CreateOrModify(String, Octopus.Client.Model.Endpoints.EndpointResource, Octopus.Client.Model.WorkerPoolResource[]) + Octopus.Client.Editors.Async.WorkerEditor Customize(Action) + Task Save() + } + class WorkerPoolEditor + Octopus.Client.Editors.Async.IResourceEditor + Octopus.Client.Editors.Async.IResourceBuilder + { + .ctor(Octopus.Client.Repositories.Async.IWorkerPoolRepository) + Octopus.Client.Model.WorkerPoolResource Instance { get; } + Task CreateOrModify(String) + Task CreateOrModify(String, String) + Octopus.Client.Editors.Async.WorkerPoolEditor Customize(Action) + Task Save() + } } Octopus.Client.Exceptions { @@ -1387,7 +1437,9 @@ Octopus.Client.Model static System.String EnvironmentId static System.String MachineIds static System.String MachineTimeout + static System.String RestrictedTo static System.String Timeout + static System.String WorkerpoolId } abstract class Arguments { @@ -1403,6 +1455,8 @@ Octopus.Client.Model { static System.String EnvironmentId static System.String MachineIds + static System.String RestrictedTo + static System.String WorkerpoolId } abstract class Arguments { @@ -1548,12 +1602,15 @@ Octopus.Client.Model abstract class Health { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds static System.String MachineTimeout + static System.String RestrictedTo static System.String Timeout + static System.String WorkerpoolId } } abstract class Migration @@ -1580,6 +1637,13 @@ Octopus.Client.Model { static System.String Name } + abstract class TaskRestrictedTo + { + static System.String DeploymentTargets + static System.String Policies + static System.String Unrestricted + static System.String Workers + } abstract class TestAzureAccount { static System.String Name @@ -1607,10 +1671,13 @@ Octopus.Client.Model abstract class Upgrade { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds + static System.String RestrictedTo + static System.String WorkerpoolId } } } @@ -1949,6 +2016,7 @@ Octopus.Client.Model String Name { get; set; } IDictionary Properties { get; } Octopus.Client.Model.ReferenceCollection TenantTags { get; } + String WorkerPoolId { get; set; } Octopus.Client.Model.DeploymentActionResource ClearAllConditions() Octopus.Client.Model.DeploymentActionResource ForChannels(Octopus.Client.Model.ChannelResource[]) Octopus.Client.Model.DeploymentActionResource ForEnvironments(Octopus.Client.Model.EnvironmentResource[]) @@ -2169,30 +2237,20 @@ Octopus.Client.Model Boolean UseGuidedFailure { get; set; } } class EnvironmentsSummaryResource + Octopus.Client.Model.SummaryResourcesCombined { .ctor() List EnvironmentSummaries { get; set; } - Dictionary MachineEndpointSummaries { get; set; } - Dictionary MachineHealthStatusSummaries { get; set; } - String[] MachineIdsForCalamariUpgrade { get; set; } Dictionary MachineTenantSummaries { get; set; } Dictionary MachineTenantTagSummaries { get; set; } - Boolean TentacleUpgradesRequired { get; set; } - Int32 TotalDisabledMachines { get; set; } - Int32 TotalMachines { get; set; } } class EnvironmentSummaryResource + Octopus.Client.Model.SummaryResource { .ctor() Octopus.Client.Model.EnvironmentResource Environment { get; set; } - Dictionary MachineEndpointSummaries { get; set; } - Dictionary MachineHealthStatusSummaries { get; set; } - String[] MachineIdsForCalamariUpgrade { get; set; } Dictionary MachineTenantSummaries { get; set; } Dictionary MachineTenantTagSummaries { get; set; } - Boolean TentacleUpgradesRequired { get; set; } - Int32 TotalDisabledMachines { get; set; } - Int32 TotalMachines { get; set; } } class EventNotificationSubscription { @@ -2271,6 +2329,7 @@ Octopus.Client.Model { .ctor() Boolean IsBuiltInRepoSyncEnabled { get; set; } + Boolean IsBuiltInWorkerEnabled { get; set; } Boolean IsCommunityActionTemplatesEnabled { get; set; } Boolean IsMultiTenancyEnabled { get; set; } } @@ -2322,12 +2381,15 @@ Octopus.Client.Model abstract class Health { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds static System.String MachineTimeout + static System.String RestrictedTo static System.String Timeout + static System.String WorkerpoolId } } HealthCheckErrorHandling @@ -2493,6 +2555,24 @@ Octopus.Client.Model .ctor() Boolean UsingSecureConnection { get; set; } } + abstract class MachineBasedResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Model.Resource + { + Octopus.Client.Model.Endpoints.EndpointResource Endpoint { get; set; } + Boolean HasLatestCalamari { get; set; } + Octopus.Client.Model.MachineModelHealthStatus HealthStatus { get; set; } + Boolean IsDisabled { get; set; } + Boolean IsInProcess { get; set; } + String MachinePolicyId { get; set; } + String Name { get; set; } + Octopus.Client.Model.MachineModelStatus Status { get; set; } + String StatusSummary { get; set; } + String Thumbprint { get; set; } + String Uri { get; set; } + } class MachineCleanupPolicy { .ctor() @@ -2579,25 +2659,14 @@ Octopus.Client.Model Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource Octopus.Client.Extensibility.INamedResource - Octopus.Client.Model.Resource + Octopus.Client.Model.MachineBasedResource { .ctor() - Octopus.Client.Model.Endpoints.EndpointResource Endpoint { get; set; } Octopus.Client.Model.ReferenceCollection EnvironmentIds { get; set; } - Boolean HasLatestCalamari { get; set; } - Octopus.Client.Model.MachineModelHealthStatus HealthStatus { get; set; } - Boolean IsDisabled { get; set; } - Boolean IsInProcess { get; set; } - String MachinePolicyId { get; set; } - String Name { get; set; } Octopus.Client.Model.ReferenceCollection Roles { get; set; } - Octopus.Client.Model.MachineModelStatus Status { get; set; } - String StatusSummary { get; set; } Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } Octopus.Client.Model.ReferenceCollection TenantIds { get; set; } Octopus.Client.Model.ReferenceCollection TenantTags { get; set; } - String Thumbprint { get; set; } - String Uri { get; set; } Octopus.Client.Model.MachineResource AddOrUpdateEnvironments(Octopus.Client.Model.EnvironmentResource[]) Octopus.Client.Model.MachineResource AddOrUpdateRoles(String[]) Octopus.Client.Model.MachineResource AddOrUpdateTenants(Octopus.Client.Model.TenantResource[]) @@ -3517,6 +3586,26 @@ Octopus.Client.Model { Event = 0 } + class SummaryResource + { + .ctor() + Dictionary MachineEndpointSummaries { get; set; } + Dictionary MachineHealthStatusSummaries { get; set; } + String[] MachineIdsForCalamariUpgrade { get; set; } + Boolean TentacleUpgradesRequired { get; set; } + Int32 TotalDisabledMachines { get; set; } + Int32 TotalMachines { get; set; } + } + class SummaryResourcesCombined + { + .ctor() + Dictionary MachineEndpointSummaries { get; set; } + Dictionary MachineHealthStatusSummaries { get; set; } + String[] MachineIdsForCalamariUpgrade { get; set; } + Boolean TentacleUpgradesRequired { get; set; } + Int32 TotalDisabledMachines { get; set; } + Int32 TotalMachines { get; set; } + } class SupportsRestrictionAttribute _Attribute Attribute @@ -3633,6 +3722,13 @@ Octopus.Client.Model Nullable StartTime { get; set; } Octopus.Client.Model.TaskState State { get; set; } } + abstract class TaskRestrictedTo + { + static System.String DeploymentTargets + static System.String Policies + static System.String Unrestricted + static System.String Workers + } TaskState { Queued = 1 @@ -3774,10 +3870,13 @@ Octopus.Client.Model abstract class Upgrade { static System.String Name + static String[] CanBeRestrictedTo() abstract class Arguments { static System.String EnvironmentId static System.String MachineIds + static System.String RestrictedTo + static System.String WorkerpoolId } } class UserPermissionRestriction @@ -3938,6 +4037,42 @@ Octopus.Client.Model String ReferrerPolicy { get; set; } Octopus.Client.Model.XOptionsResource XOptions { get; set; } } + class WorkerPoolResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Model.Resource + { + .ctor() + String Description { get; set; } + Boolean IsDefault { get; set; } + String Name { get; set; } + Int32 SortOrder { get; set; } + } + class WorkerPoolsSummaryResource + Octopus.Client.Model.SummaryResourcesCombined + { + .ctor() + List WorkerPoolSummaries { get; set; } + } + class WorkerPoolSummaryResource + Octopus.Client.Model.SummaryResource + { + .ctor() + Octopus.Client.Model.WorkerPoolResource WorkerPool { get; set; } + } + class WorkerResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Model.MachineBasedResource + { + .ctor() + Octopus.Client.Model.ReferenceCollection WorkerPoolIds { get; set; } + Octopus.Client.Model.WorkerResource AddOrUpdateWorkerPools(Octopus.Client.Model.WorkerPoolResource[]) + Octopus.Client.Model.WorkerResource ClearWorkerPools() + Octopus.Client.Model.WorkerResource RemoveWorkerPool(Octopus.Client.Model.WorkerPoolResource) + } class X509Certificate { .ctor() @@ -4730,18 +4865,22 @@ Octopus.Client.Model.Versioning Octopus.Client.Operations { interface IRegisterMachineOperation + Octopus.Client.Operations.IRegisterMachineOperationBase + { + String[] EnvironmentNames { get; set; } + String[] Roles { get; set; } + Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } + String[] Tenants { get; set; } + String[] TenantTags { get; set; } + } + interface IRegisterMachineOperationBase { Boolean AllowOverwrite { get; set; } Octopus.Client.Model.CommunicationStyle CommunicationStyle { get; set; } - String[] EnvironmentNames { get; set; } String MachineName { get; set; } String MachinePolicy { get; set; } String ProxyName { get; set; } - String[] Roles { get; set; } Uri SubscriptionId { get; set; } - Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } - String[] Tenants { get; set; } - String[] TenantTags { get; set; } String TentacleHostname { get; set; } Int32 TentaclePort { get; set; } String TentacleThumbprint { get; set; } @@ -4752,22 +4891,36 @@ Octopus.Client.Operations Task ExecuteAsync(Octopus.Client.OctopusAsyncRepository) Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) } + interface IRegisterWorkerOperation + Octopus.Client.Operations.IRegisterMachineOperationBase + { + String[] WorkerPoolNames { get; set; } + } class RegisterMachineOperation + Octopus.Client.Operations.IRegisterMachineOperationBase Octopus.Client.Operations.IRegisterMachineOperation + Octopus.Client.Operations.RegisterMachineOperationBase { .ctor() + .ctor(Octopus.Client.IOctopusClientFactory) + String[] EnvironmentNames { get; set; } + String[] Roles { get; set; } + Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } + String[] Tenants { get; set; } + String[] TenantTags { get; set; } + void Execute(Octopus.Client.IOctopusRepository) + Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) + } + abstract class RegisterMachineOperationBase + Octopus.Client.Operations.IRegisterMachineOperationBase + { .ctor(Octopus.Client.IOctopusClientFactory) Boolean AllowOverwrite { get; set; } Octopus.Client.Model.CommunicationStyle CommunicationStyle { get; set; } - String[] EnvironmentNames { get; set; } String MachineName { get; set; } String MachinePolicy { get; set; } String ProxyName { get; set; } - String[] Roles { get; set; } Uri SubscriptionId { get; set; } - Octopus.Client.Model.TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } - String[] Tenants { get; set; } - String[] TenantTags { get; set; } String TentacleHostname { get; set; } Int32 TentaclePort { get; set; } String TentacleThumbprint { get; set; } @@ -4778,6 +4931,17 @@ Octopus.Client.Operations Task ExecuteAsync(Octopus.Client.OctopusAsyncRepository) Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) } + class RegisterWorkerOperation + Octopus.Client.Operations.IRegisterMachineOperationBase + Octopus.Client.Operations.IRegisterWorkerOperation + Octopus.Client.Operations.RegisterMachineOperationBase + { + .ctor() + .ctor(Octopus.Client.IOctopusClientFactory) + String[] WorkerPoolNames { get; set; } + void Execute(Octopus.Client.IOctopusRepository) + Task ExecuteAsync(Octopus.Client.IOctopusAsyncRepository) + } } Octopus.Client.Repositories { @@ -5185,8 +5349,8 @@ Octopus.Client.Repositories Octopus.Client.Model.TaskResource ExecuteBackup(String) Octopus.Client.Model.TaskResource ExecuteCalamariUpdate(String, String[]) Octopus.Client.Model.TaskResource ExecuteCommunityActionTemplatesSynchronisation(String) - Octopus.Client.Model.TaskResource ExecuteHealthCheck(String, Int32, Int32, String, String[]) - Octopus.Client.Model.TaskResource ExecuteTentacleUpgrade(String, String, String[]) + Octopus.Client.Model.TaskResource ExecuteHealthCheck(String, Int32, Int32, String, String[], String, String, String[]) + Octopus.Client.Model.TaskResource ExecuteTentacleUpgrade(String, String, String[], String, String, String[]) List GetAllActive(Int32) Octopus.Client.Model.TaskDetailsResource GetDetails(Octopus.Client.Model.TaskResource, Nullable, Nullable) IReadOnlyList GetQueuedBehindTasks(Octopus.Client.Model.TaskResource) @@ -5263,6 +5427,35 @@ Octopus.Client.Repositories { String[] GetVariableNames(String, String[]) } + interface IWorkerPoolRepository + Octopus.Client.Repositories.IFindByName + Octopus.Client.Repositories.IPaginate + Octopus.Client.Repositories.IGet + Octopus.Client.Repositories.ICreate + Octopus.Client.Repositories.IModify + Octopus.Client.Repositories.IDelete + Octopus.Client.Repositories.IGetAll + { + Octopus.Client.Editors.WorkerPoolEditor CreateOrModify(String) + Octopus.Client.Editors.WorkerPoolEditor CreateOrModify(String, String) + List GetMachines(Octopus.Client.Model.WorkerPoolResource, Nullable, Nullable, String, Nullable, String, String) + void Sort(String[]) + Octopus.Client.Model.WorkerPoolsSummaryResource Summary(String, String, String, Nullable, String, String, Nullable) + } + interface IWorkerRepository + Octopus.Client.Repositories.IFindByName + Octopus.Client.Repositories.IPaginate + Octopus.Client.Repositories.IGet + Octopus.Client.Repositories.ICreate + Octopus.Client.Repositories.IModify + Octopus.Client.Repositories.IDelete + { + Octopus.Client.Editors.WorkerEditor CreateOrModify(String, Octopus.Client.Model.Endpoints.EndpointResource, Octopus.Client.Model.WorkerPoolResource[]) + Octopus.Client.Model.WorkerResource Discover(String, Int32, Nullable) + List FindByThumbprint(String) + Octopus.Client.Model.MachineConnectionStatus GetConnectionStatus(Octopus.Client.Model.WorkerResource) + Octopus.Client.Model.ResourceCollection List(Int32, Nullable, String, String, String, Nullable, String, String, String) + } } Octopus.Client.Repositories.Async { @@ -5670,8 +5863,8 @@ Octopus.Client.Repositories.Async Task ExecuteBackup(String) Task ExecuteCalamariUpdate(String, String[]) Task ExecuteCommunityActionTemplatesSynchronisation(String) - Task ExecuteHealthCheck(String, Int32, Int32, String, String[]) - Task ExecuteTentacleUpgrade(String, String, String[]) + Task ExecuteHealthCheck(String, Int32, Int32, String, String[], String, String, String[]) + Task ExecuteTentacleUpgrade(String, String, String[], String, String, String[]) Task> GetAllActive(Int32) Task GetDetails(Octopus.Client.Model.TaskResource, Nullable, Nullable) Task> GetQueuedBehindTasks(Octopus.Client.Model.TaskResource) @@ -5749,6 +5942,35 @@ Octopus.Client.Repositories.Async { Task GetVariableNames(String, String[]) } + interface IWorkerPoolRepository + Octopus.Client.Repositories.Async.IFindByName + Octopus.Client.Repositories.Async.IPaginate + Octopus.Client.Repositories.Async.IGet + Octopus.Client.Repositories.Async.ICreate + Octopus.Client.Repositories.Async.IModify + Octopus.Client.Repositories.Async.IDelete + Octopus.Client.Repositories.Async.IGetAll + { + Task CreateOrModify(String) + Task CreateOrModify(String, String) + Task> GetMachines(Octopus.Client.Model.WorkerPoolResource, Nullable, Nullable, String, Nullable, String, String) + Task Sort(String[]) + Task Summary(String, String, String, Nullable, String, String, Nullable) + } + interface IWorkerRepository + Octopus.Client.Repositories.Async.IFindByName + Octopus.Client.Repositories.Async.IPaginate + Octopus.Client.Repositories.Async.IGet + Octopus.Client.Repositories.Async.ICreate + Octopus.Client.Repositories.Async.IModify + Octopus.Client.Repositories.Async.IDelete + { + Task CreateOrModify(String, Octopus.Client.Model.Endpoints.EndpointResource, Octopus.Client.Model.WorkerPoolResource[]) + Task Discover(String, Int32, Nullable) + Task> FindByThumbprint(String) + Task GetConnectionStatus(Octopus.Client.Model.WorkerResource) + Task> List(Int32, Nullable, String, String, String, Nullable, String, String, String) + } } Octopus.Client.Serialization { diff --git a/source/Octopus.Client/Editors/Async/WorkerEditor.cs b/source/Octopus.Client/Editors/Async/WorkerEditor.cs new file mode 100644 index 000000000..6eae3fb0e --- /dev/null +++ b/source/Octopus.Client/Editors/Async/WorkerEditor.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; +using Octopus.Client.Repositories.Async; + +namespace Octopus.Client.Editors.Async +{ + public class WorkerEditor : IResourceEditor + { + private readonly IWorkerRepository repository; + + public WorkerEditor(IWorkerRepository repository) + { + this.repository = repository; + } + + public WorkerResource Instance { get; private set; } + + public async Task CreateOrModify( + string name, + EndpointResource endpoint, + WorkerPoolResource[] workerpools) + { + var existing = await repository.FindByName(name).ConfigureAwait(false); + if (existing == null) + { + Instance = await repository.Create(new WorkerResource + { + Name = name, + Endpoint = endpoint, + WorkerPoolIds = new ReferenceCollection(workerpools.Select(e => e.Id)) + }).ConfigureAwait(false); + } + else + { + existing.Name = name; + existing.Endpoint = endpoint; + existing.WorkerPoolIds.ReplaceAll(workerpools.Select(e => e.Id)); + + Instance = await repository.Modify(existing).ConfigureAwait(false); + } + + return this; + } + + public WorkerEditor Customize(Action customize) + { + customize?.Invoke(Instance); + return this; + } + + public async Task Save() + { + Instance = await repository.Modify(Instance).ConfigureAwait(false); + return this; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Editors/Async/WorkerPoolEditor.cs b/source/Octopus.Client/Editors/Async/WorkerPoolEditor.cs new file mode 100644 index 000000000..6a827c0b7 --- /dev/null +++ b/source/Octopus.Client/Editors/Async/WorkerPoolEditor.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading.Tasks; +using Octopus.Client.Model; +using Octopus.Client.Repositories.Async; + +namespace Octopus.Client.Editors.Async +{ + public class WorkerPoolEditor : IResourceEditor + { + private readonly IWorkerPoolRepository repository; + + public WorkerPoolEditor(IWorkerPoolRepository repository) + { + this.repository = repository; + } + + public WorkerPoolResource Instance { get; private set; } + + public async Task CreateOrModify(string name) + { + var existing = await repository.FindByName(name).ConfigureAwait(false); + if (existing == null) + { + Instance = await repository.Create(new WorkerPoolResource + { + Name = name, + }).ConfigureAwait(false); + } + else + { + existing.Name = name; + + Instance = await repository.Modify(existing).ConfigureAwait(false); + } + + return this; + } + + public async Task CreateOrModify(string name, string description) + { + var existing = await repository.FindByName(name).ConfigureAwait(false); + if (existing == null) + { + Instance = await repository.Create(new WorkerPoolResource + { + Name = name, + Description = description + }).ConfigureAwait(false); + } + else + { + existing.Name = name; + existing.Description = description; + + Instance = await repository.Modify(existing).ConfigureAwait(false); + } + + return this; + } + + public WorkerPoolEditor Customize(Action customize) + { + customize?.Invoke(Instance); + return this; + } + + public async Task Save() + { + Instance = await repository.Modify(Instance).ConfigureAwait(false); + return this; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Editors/WorkerEditor.cs b/source/Octopus.Client/Editors/WorkerEditor.cs new file mode 100644 index 000000000..75d0a0fb9 --- /dev/null +++ b/source/Octopus.Client/Editors/WorkerEditor.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; +using Octopus.Client.Repositories; + +namespace Octopus.Client.Editors +{ + public class WorkerEditor : IResourceEditor + { + private readonly IWorkerRepository repository; + + public WorkerEditor(IWorkerRepository repository) + { + this.repository = repository; + } + + public WorkerResource Instance { get; private set; } + + public WorkerEditor CreateOrModify( + string name, + EndpointResource endpoint, + WorkerPoolResource[] pools) + { + var existing = repository.FindByName(name); + if (existing == null) + { + Instance = repository.Create(new WorkerResource + { + Name = name, + Endpoint = endpoint, + WorkerPoolIds = new ReferenceCollection(pools.Select(e => e.Id)) + }); + } + else + { + existing.Name = name; + existing.Endpoint = endpoint; + existing.WorkerPoolIds.ReplaceAll(pools.Select(e => e.Id)); + + Instance = repository.Modify(existing); + } + + return this; + } + + public WorkerEditor Customize(Action customize) + { + customize?.Invoke(Instance); + return this; + } + + public WorkerEditor Save() + { + Instance = repository.Modify(Instance); + return this; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Editors/WorkerPoolEditor.cs b/source/Octopus.Client/Editors/WorkerPoolEditor.cs new file mode 100644 index 000000000..094b15725 --- /dev/null +++ b/source/Octopus.Client/Editors/WorkerPoolEditor.cs @@ -0,0 +1,72 @@ +using System; +using Octopus.Client.Model; +using Octopus.Client.Repositories; + +namespace Octopus.Client.Editors +{ + public class WorkerPoolEditor : IResourceEditor + { + private readonly IWorkerPoolRepository repository; + + public WorkerPoolEditor(IWorkerPoolRepository repository) + { + this.repository = repository; + } + + public WorkerPoolResource Instance { get; private set; } + + public WorkerPoolEditor CreateOrModify(string name) + { + var existing = repository.FindByName(name); + if (existing == null) + { + Instance = repository.Create(new WorkerPoolResource + { + Name = name, + }); + } + else + { + existing.Name = name; + + Instance = repository.Modify(existing); + } + + return this; + } + + public WorkerPoolEditor CreateOrModify(string name, string description) + { + var existing = repository.FindByName(name); + if (existing == null) + { + Instance = repository.Create(new WorkerPoolResource + { + Name = name, + Description = description + }); + } + else + { + existing.Name = name; + existing.Description = description; + + Instance = repository.Modify(existing); + } + + return this; + } + + public WorkerPoolEditor Customize(Action customize) + { + customize?.Invoke(Instance); + return this; + } + + public WorkerPoolEditor Save() + { + Instance = repository.Modify(Instance); + return this; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/IOctopusAsyncRepository.cs b/source/Octopus.Client/IOctopusAsyncRepository.cs index cc45ea93d..fdc4a7c3e 100644 --- a/source/Octopus.Client/IOctopusAsyncRepository.cs +++ b/source/Octopus.Client/IOctopusAsyncRepository.cs @@ -59,5 +59,7 @@ public interface IOctopusAsyncRepository IUserRepository Users { get; } IUserRolesRepository UserRoles { get; } IVariableSetRepository VariableSets { get; } + IWorkerPoolRepository WorkerPools { get; } + IWorkerRepository Workers { get; } } } \ No newline at end of file diff --git a/source/Octopus.Client/IOctopusRepository.cs b/source/Octopus.Client/IOctopusRepository.cs index e7fd7883e..81205d1e4 100644 --- a/source/Octopus.Client/IOctopusRepository.cs +++ b/source/Octopus.Client/IOctopusRepository.cs @@ -60,6 +60,8 @@ public interface IOctopusRepository IUserRepository Users { get; } IUserRolesRepository UserRoles { get; } IVariableSetRepository VariableSets { get; } + IWorkerPoolRepository WorkerPools { get; } + IWorkerRepository Workers { get; } } } #endif \ No newline at end of file diff --git a/source/Octopus.Client/Model/BuiltInTasks.cs b/source/Octopus.Client/Model/BuiltInTasks.cs index a3a167398..50841b05f 100644 --- a/source/Octopus.Client/Model/BuiltInTasks.cs +++ b/source/Octopus.Client/Model/BuiltInTasks.cs @@ -39,14 +39,35 @@ public static class Arguments } } + public static class TaskRestrictedTo + { + public static readonly string DeploymentTargets = "DeploymentTargets"; + public static readonly string Workers = "Workers"; + public static readonly string Policies = "Policies"; + public static readonly string Unrestricted = "Unlimited"; + } + public static class Health { public const string Name = "Health"; + public static string[] CanBeRestrictedTo() + { + return new string[] + { + TaskRestrictedTo.DeploymentTargets, + TaskRestrictedTo.Workers, + TaskRestrictedTo.Policies, + TaskRestrictedTo.Unrestricted + }; + } + public static class Arguments { public static string EnvironmentId = "EnvironmentId"; + public static string WorkerpoolId = "WorkerpoolId"; public static string MachineIds = "MachineIds"; + public static string RestrictedTo = "RestrictedTo"; public static string Timeout = "Timeout"; public static string MachineTimeout = "MachineTimeout"; } @@ -92,10 +113,22 @@ public static class Upgrade { public const string Name = "Upgrade"; + public static string[] CanBeRestrictedTo() + { + return new string[] + { + TaskRestrictedTo.DeploymentTargets, + TaskRestrictedTo.Workers, + TaskRestrictedTo.Unrestricted + }; + } + public static class Arguments { public static string EnvironmentId = "EnvironmentId"; + public static string WorkerpoolId = "WorkerpoolId"; public static string MachineIds = "MachineIds"; + public static string RestrictedTo = "RestrictedTo"; } } diff --git a/source/Octopus.Client/Model/DeploymentActionResource.cs b/source/Octopus.Client/Model/DeploymentActionResource.cs index c2362cdcf..1dc43f573 100644 --- a/source/Octopus.Client/Model/DeploymentActionResource.cs +++ b/source/Octopus.Client/Model/DeploymentActionResource.cs @@ -10,8 +10,9 @@ public class DeploymentActionResource : Resource public string Name { get; set; } public string ActionType { get; set; } public bool IsDisabled { get; set; } - public bool CanBeUsedForProjectVersioning { get; set; } - + public string WorkerPoolId { get; set; } + public bool CanBeUsedForProjectVersioning { get; set; } + /// /// If true, this action cannot be skipped when deploying /// diff --git a/source/Octopus.Client/Model/EnvironmentSummaryResource.cs b/source/Octopus.Client/Model/EnvironmentSummaryResource.cs index c9d2f41a6..9d16327f1 100644 --- a/source/Octopus.Client/Model/EnvironmentSummaryResource.cs +++ b/source/Octopus.Client/Model/EnvironmentSummaryResource.cs @@ -2,16 +2,10 @@ namespace Octopus.Client.Model { - public class EnvironmentSummaryResource + public class EnvironmentSummaryResource : SummaryResource { public EnvironmentResource Environment { get; set; } - public int TotalMachines { get; set; } - public int TotalDisabledMachines { get; set; } - public Dictionary MachineHealthStatusSummaries { get; set; } - public Dictionary MachineEndpointSummaries { get; set; } public Dictionary MachineTenantSummaries { get; set; } public Dictionary MachineTenantTagSummaries { get; set; } - public bool TentacleUpgradesRequired { get; set; } - public string[] MachineIdsForCalamariUpgrade { get; set; } } } diff --git a/source/Octopus.Client/Model/EnvironmentsSummaryResource.cs b/source/Octopus.Client/Model/EnvironmentsSummaryResource.cs index cefb485d9..31631c78b 100644 --- a/source/Octopus.Client/Model/EnvironmentsSummaryResource.cs +++ b/source/Octopus.Client/Model/EnvironmentsSummaryResource.cs @@ -2,16 +2,10 @@ namespace Octopus.Client.Model { - public class EnvironmentsSummaryResource + public class EnvironmentsSummaryResource : SummaryResourcesCombined { - public int TotalMachines { get; set; } - public int TotalDisabledMachines { get; set; } - public Dictionary MachineHealthStatusSummaries { get; set; } - public Dictionary MachineEndpointSummaries { get; set; } public Dictionary MachineTenantSummaries { get; set; } public Dictionary MachineTenantTagSummaries { get; set; } public List EnvironmentSummaries { get; set; } - public bool TentacleUpgradesRequired { get; set; } - public string[] MachineIdsForCalamariUpgrade { get; set; } } } diff --git a/source/Octopus.Client/Model/FeaturesConfigurationResource.cs b/source/Octopus.Client/Model/FeaturesConfigurationResource.cs index 4ac9bca15..a613ad7a9 100644 --- a/source/Octopus.Client/Model/FeaturesConfigurationResource.cs +++ b/source/Octopus.Client/Model/FeaturesConfigurationResource.cs @@ -7,6 +7,9 @@ public class FeaturesConfigurationResource : Resource [Writeable] public bool IsMultiTenancyEnabled { get; set; } + [Writeable] + public bool IsBuiltInWorkerEnabled { get; set; } = true; + [Writeable] public bool IsCommunityActionTemplatesEnabled { get; set; } diff --git a/source/Octopus.Client/Model/MachineBasedResource.cs b/source/Octopus.Client/Model/MachineBasedResource.cs new file mode 100644 index 000000000..7126c81dd --- /dev/null +++ b/source/Octopus.Client/Model/MachineBasedResource.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Octopus.Client.Extensibility; +using Octopus.Client.Extensibility.Attributes; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Client.Model +{ + public abstract class MachineBasedResource : Resource, INamedResource + { + + + [Trim] + [Writeable] + public string Name { get; set; } + + /// + /// Obsoleted as Server 3.4 + /// + [Trim] + [Writeable] + public string Thumbprint { get; set; } + + /// + /// Obsoleted as Server 3.4 + /// + [Trim] + [Writeable] + public string Uri { get; set; } + + [Writeable] + public bool IsDisabled { get; set; } + + [Writeable] + public string MachinePolicyId { get; set; } + + /// + /// Obsoleted as Server 3.4 + /// + public MachineModelStatus Status { get; set; } + + public MachineModelHealthStatus HealthStatus { get; set; } + public bool HasLatestCalamari { get; set; } + public string StatusSummary { get; set; } + + public bool IsInProcess { get; set; } + + [Writeable] + public EndpointResource Endpoint { get; set; } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Model/MachineResource.cs b/source/Octopus.Client/Model/MachineResource.cs index 80f3f73fa..0eb1fe3be 100644 --- a/source/Octopus.Client/Model/MachineResource.cs +++ b/source/Octopus.Client/Model/MachineResource.cs @@ -1,11 +1,9 @@ using System.Linq; -using Octopus.Client.Extensibility; using Octopus.Client.Extensibility.Attributes; -using Octopus.Client.Model.Endpoints; namespace Octopus.Client.Model { - public class MachineResource : Resource, INamedResource + public class MachineResource : MachineBasedResource { public MachineResource() { @@ -15,36 +13,12 @@ public MachineResource() TenantIds = new ReferenceCollection(); } - [Trim] - [Writeable] - public string Name { get; set; } - - /// - /// Obsoleted as Server 3.4 - /// - [Trim] - [Writeable] - public string Thumbprint { get; set; } - - /// - /// Obsoleted as Server 3.4 - /// - [Trim] - [Writeable] - public string Uri { get; set; } - - [Writeable] - public bool IsDisabled { get; set; } - [Writeable] public ReferenceCollection EnvironmentIds { get; set; } [Writeable] public ReferenceCollection Roles { get; set; } - [Writeable] - public string MachinePolicyId { get; set; } - // Nullable backing-field is to support backwards-compatibility TenantedDeploymentMode? tenantedDeploymentParticipation; @@ -52,14 +26,14 @@ public MachineResource() public TenantedDeploymentMode TenantedDeploymentParticipation { set => tenantedDeploymentParticipation = value; - + get { if (tenantedDeploymentParticipation.HasValue) return tenantedDeploymentParticipation.Value; // Responses from server versions before TenantedDeploymentParticipation was implemented will default - // to pre-existing behaviour + // to pre-existing behaviour return TenantIds.Any() || TenantTags.Any() ? TenantedDeploymentMode.Tenanted : TenantedDeploymentMode.Untenanted; @@ -72,21 +46,6 @@ public TenantedDeploymentMode TenantedDeploymentParticipation [Writeable] public ReferenceCollection TenantTags { get; set; } - - /// - /// Obsoleted as Server 3.4 - /// - public MachineModelStatus Status { get; set; } - - public MachineModelHealthStatus HealthStatus { get; set; } - public bool HasLatestCalamari { get; set; } - public string StatusSummary { get; set; } - - public bool IsInProcess { get; set; } - - [Writeable] - public EndpointResource Endpoint { get; set; } - public MachineResource AddOrUpdateEnvironments(params EnvironmentResource[] environments) { foreach (var environment in environments) diff --git a/source/Octopus.Client/Model/SummaryResource.cs b/source/Octopus.Client/Model/SummaryResource.cs new file mode 100644 index 000000000..d2206f70a --- /dev/null +++ b/source/Octopus.Client/Model/SummaryResource.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Octopus.Client.Model +{ + public class SummaryResource + { + public int TotalMachines { get; set; } + public int TotalDisabledMachines { get; set; } + public Dictionary MachineHealthStatusSummaries { get; set; } + public Dictionary MachineEndpointSummaries { get; set; } + public bool TentacleUpgradesRequired { get; set; } + public string[] MachineIdsForCalamariUpgrade { get; set; } + } +} diff --git a/source/Octopus.Client/Model/SummaryResourcesCombined.cs b/source/Octopus.Client/Model/SummaryResourcesCombined.cs new file mode 100644 index 000000000..7ed038ca1 --- /dev/null +++ b/source/Octopus.Client/Model/SummaryResourcesCombined.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Octopus.Client.Model +{ + public class SummaryResourcesCombined + { + public int TotalMachines { get; set; } + public int TotalDisabledMachines { get; set; } + public Dictionary MachineHealthStatusSummaries { get; set; } + public Dictionary MachineEndpointSummaries { get; set; } + public bool TentacleUpgradesRequired { get; set; } + public string[] MachineIdsForCalamariUpgrade { get; set; } + } +} diff --git a/source/Octopus.Client/Model/WorkerPoolResource.cs b/source/Octopus.Client/Model/WorkerPoolResource.cs new file mode 100644 index 000000000..372bf8356 --- /dev/null +++ b/source/Octopus.Client/Model/WorkerPoolResource.cs @@ -0,0 +1,41 @@ +using Octopus.Client.Extensibility; +using Octopus.Client.Extensibility.Attributes; + +namespace Octopus.Client.Model +{ + /// + /// Represents a pool of workers. WorkerPools are user-defined and map to pools of machines that + /// can do work as part of a deployment: for example, running scripts and deploying to Azure. + /// + public class WorkerPoolResource : Resource, INamedResource + { + /// + /// Gets or sets the name of this pool. This should be short, preferably 5-20 characters. + /// + [Writeable] + [Trim] + public string Name { get; set; } + + /// + /// Gets or sets a short description that can be used to explain the purpose of + /// the pool to other users. This field may contain markdown. + /// + [Writeable] + [Trim] + public string Description { get; set; } + + /// + /// Is this the default pool. The default pool is used for steps that don't specify a worker pool. + /// The default pool, if empty, uses the builtin worker to run steps. + /// + [Writeable] + public bool IsDefault { get; set; } + + /// + /// Gets or sets a number indicating the priority of this pool in sort order. WorkerPools with + /// a lower sort order will appear in the UI before items with a higher sort order. + /// + [Writeable] + public int SortOrder { get; set; } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Model/WorkerPoolSummaryResource.cs b/source/Octopus.Client/Model/WorkerPoolSummaryResource.cs new file mode 100644 index 000000000..648b0c512 --- /dev/null +++ b/source/Octopus.Client/Model/WorkerPoolSummaryResource.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Octopus.Client.Model +{ + public class WorkerPoolSummaryResource : SummaryResource + { + public WorkerPoolResource WorkerPool { get; set; } + } +} diff --git a/source/Octopus.Client/Model/WorkerPoolsSummaryResource.cs b/source/Octopus.Client/Model/WorkerPoolsSummaryResource.cs new file mode 100644 index 000000000..2fadb1ca4 --- /dev/null +++ b/source/Octopus.Client/Model/WorkerPoolsSummaryResource.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Octopus.Client.Model +{ + public class WorkerPoolsSummaryResource : SummaryResourcesCombined + { + public List WorkerPoolSummaries { get; set; } + } +} diff --git a/source/Octopus.Client/Model/WorkerResource.cs b/source/Octopus.Client/Model/WorkerResource.cs new file mode 100644 index 000000000..8552f8d99 --- /dev/null +++ b/source/Octopus.Client/Model/WorkerResource.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Octopus.Client.Extensibility; +using Octopus.Client.Extensibility.Attributes; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Client.Model +{ + public class WorkerResource : MachineBasedResource + { + public WorkerResource() + { + WorkerPoolIds = new ReferenceCollection(); + } + + [Writeable] + public ReferenceCollection WorkerPoolIds { get; set; } + + public WorkerResource AddOrUpdateWorkerPools(params WorkerPoolResource[] pools) + { + foreach (var pool in pools) + { + WorkerPoolIds.Add(pool.Id); + } + return this; + } + + public WorkerResource RemoveWorkerPool(WorkerPoolResource pool) + { + WorkerPoolIds.Remove(pool.Id); + return this; + } + + public WorkerResource ClearWorkerPools() + { + WorkerPoolIds.Clear(); + return this; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/OctopusAsyncRepository.cs b/source/Octopus.Client/OctopusAsyncRepository.cs index c511441bc..0e1ae235f 100644 --- a/source/Octopus.Client/OctopusAsyncRepository.cs +++ b/source/Octopus.Client/OctopusAsyncRepository.cs @@ -74,6 +74,8 @@ public OctopusAsyncRepository(IOctopusAsyncClient client) UserRoles = new UserRolesRepository(client); Users = new UserRepository(client); VariableSets = new VariableSetRepository(client); + Workers = new WorkerRepository(client); + WorkerPools = new WorkerPoolRepository(client); } public IOctopusAsyncClient Client { get; } @@ -122,5 +124,7 @@ public OctopusAsyncRepository(IOctopusAsyncClient client) public IUserRepository Users { get; } public IUserRolesRepository UserRoles { get; } public IVariableSetRepository VariableSets { get; } + public IWorkerPoolRepository WorkerPools { get; } + public IWorkerRepository Workers { get; } } } \ No newline at end of file diff --git a/source/Octopus.Client/OctopusRepository.cs b/source/Octopus.Client/OctopusRepository.cs index f1afcdc27..0236987f7 100644 --- a/source/Octopus.Client/OctopusRepository.cs +++ b/source/Octopus.Client/OctopusRepository.cs @@ -68,6 +68,8 @@ public OctopusRepository(IOctopusClient client) UserRoles = new UserRolesRepository(client); Users = new UserRepository(client); VariableSets = new VariableSetRepository(client); + Workers = new WorkerRepository(client); + WorkerPools = new WorkerPoolRepository(client); } public IOctopusClient Client { get; } @@ -116,6 +118,8 @@ public OctopusRepository(IOctopusClient client) public IUserRepository Users { get; } public IUserRolesRepository UserRoles { get; } public IVariableSetRepository VariableSets { get; } + public IWorkerPoolRepository WorkerPools { get; } + public IWorkerRepository Workers { get; } } } #endif \ No newline at end of file diff --git a/source/Octopus.Client/Operations/IRegisterMachineOperation.cs b/source/Octopus.Client/Operations/IRegisterMachineOperation.cs index 38aabef18..98333b71e 100644 --- a/source/Octopus.Client/Operations/IRegisterMachineOperation.cs +++ b/source/Octopus.Client/Operations/IRegisterMachineOperation.cs @@ -7,7 +7,7 @@ namespace Octopus.Client.Operations /// /// Encapsulates the operation for registering machines. /// - public interface IRegisterMachineOperation + public interface IRegisterMachineOperation : IRegisterMachineOperationBase { /// /// Gets or sets the environments that this machine should be added to. @@ -29,93 +29,11 @@ public interface IRegisterMachineOperation /// string[] TenantTags { get; set; } - /// - /// Gets or sets the name of the machine that will be used within Octopus to identify this machine. - /// - string MachineName { get; set; } - - /// - /// Gets or sets the machine policy that applies to this machine. - /// - string MachinePolicy { get; set; } - - /// - /// Gets or sets the hostname that Octopus should use when communicating with the Tentacle. - /// - string TentacleHostname { get; set; } - - /// - /// Gets or sets the TCP port that Octopus should use when communicating with the Tentacle. - /// - int TentaclePort { get; set; } - - /// - /// Gets or sets the certificate thumbprint that Octopus should expect when communicating with the Tentacle. - /// - string TentacleThumbprint { get; set; } - - /// - /// Gets or sets the name of the proxy that Octopus should use when communicating with the Tentacle. - /// - string ProxyName { get; set; } - - /// - /// If a machine with the same name already exists, it won't be overwritten by default (instead, an - /// will be thrown). - /// Set this property to true if you do want the existing machine to be overwritten. - /// - bool AllowOverwrite { get; set; } - - /// - /// The communication style to use with the Tentacle. Allowed values are: TentacleActive, in which case the - /// Tentacle will connect to the Octopus server for instructions; or, TentaclePassive, in which case the - /// Tentacle will listen for commands from the server (default). - /// - CommunicationStyle CommunicationStyle { get; set; } - - Uri SubscriptionId { get; set; } - /// /// How the machine should participate in Tenanted Deployments. /// Allowed values are Untenanted, TenantedOrUntenanted or Tenanted /// TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } -#if SYNC_CLIENT - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy server endpoint. - void Execute(OctopusServerEndpoint serverEndpoint); - - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy repository. - void Execute(OctopusRepository repository); - - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy repository. - void Execute(IOctopusRepository repository); -#endif - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy server endpoint. - Task ExecuteAsync(OctopusServerEndpoint serverEndpoint); - - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy repository. - Task ExecuteAsync(OctopusAsyncRepository repository); - - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy repository. - Task ExecuteAsync(IOctopusAsyncRepository repository); } } \ No newline at end of file diff --git a/source/Octopus.Client/Operations/IRegisterMachineOperationBase.cs b/source/Octopus.Client/Operations/IRegisterMachineOperationBase.cs new file mode 100644 index 000000000..ddf669a6b --- /dev/null +++ b/source/Octopus.Client/Operations/IRegisterMachineOperationBase.cs @@ -0,0 +1,95 @@ +using System; +using System.Threading.Tasks; +using Octopus.Client.Model; + +namespace Octopus.Client.Operations +{ + /// + /// Base for registering Deployment Targets and Workers + /// + public interface IRegisterMachineOperationBase + { + /// + /// Gets or sets the name of the machine that will be used within Octopus to identify this machine. + /// + string MachineName { get; set; } + + /// + /// Gets or sets the machine policy that applies to this machine. + /// + string MachinePolicy { get; set; } + + /// + /// Gets or sets the hostname that Octopus should use when communicating with the Tentacle. + /// + string TentacleHostname { get; set; } + + /// + /// Gets or sets the TCP port that Octopus should use when communicating with the Tentacle. + /// + int TentaclePort { get; set; } + + /// + /// Gets or sets the certificate thumbprint that Octopus should expect when communicating with the Tentacle. + /// + string TentacleThumbprint { get; set; } + + /// + /// Gets or sets the name of the proxy that Octopus should use when communicating with the Tentacle. + /// + string ProxyName { get; set; } + + /// + /// If a machine with the same name already exists, it won't be overwritten by default (instead, an + /// will be thrown). + /// Set this property to true if you do want the existing machine to be overwritten. + /// + bool AllowOverwrite { get; set; } + + /// + /// The communication style to use with the Tentacle. Allowed values are: TentacleActive, in which case the + /// Tentacle will connect to the Octopus server for instructions; or, TentaclePassive, in which case the + /// Tentacle will listen for commands from the server (default). + /// + CommunicationStyle CommunicationStyle { get; set; } + + Uri SubscriptionId { get; set; } + +#if SYNC_CLIENT + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server endpoint. + void Execute(OctopusServerEndpoint serverEndpoint); + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy repository. + void Execute(OctopusRepository repository); + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy repository. + void Execute(IOctopusRepository repository); +#endif + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server endpoint. + Task ExecuteAsync(OctopusServerEndpoint serverEndpoint); + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy repository. + Task ExecuteAsync(OctopusAsyncRepository repository); + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy repository. + Task ExecuteAsync(IOctopusAsyncRepository repository); + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Operations/IRegisterWorkerOperation.cs b/source/Octopus.Client/Operations/IRegisterWorkerOperation.cs new file mode 100644 index 000000000..8a255142c --- /dev/null +++ b/source/Octopus.Client/Operations/IRegisterWorkerOperation.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Octopus.Client.Model; + +namespace Octopus.Client.Operations +{ + /// + /// Encapsulates the operation for registering workers. + /// + public interface IRegisterWorkerOperation : IRegisterMachineOperationBase + { + /// + /// Gets or sets the worker pools that this machine should be added to. + /// + string[] WorkerPoolNames { get; set; } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Operations/RegisterMachineOperation.cs b/source/Octopus.Client/Operations/RegisterMachineOperation.cs index a2d23413c..313ca2893 100644 --- a/source/Octopus.Client/Operations/RegisterMachineOperation.cs +++ b/source/Octopus.Client/Operations/RegisterMachineOperation.cs @@ -12,10 +12,8 @@ namespace Octopus.Client.Operations /// /// Encapsulates the operation for registering machines. /// - public class RegisterMachineOperation : IRegisterMachineOperation + public class RegisterMachineOperation : RegisterMachineOperationBase, IRegisterMachineOperation { - readonly IOctopusClientFactory clientFactory; - /// /// Initializes a new instance of the class. /// @@ -27,9 +25,9 @@ public RegisterMachineOperation() : this(null) /// Initializes a new instance of the class. /// /// The client factory. - public RegisterMachineOperation(IOctopusClientFactory clientFactory) + public RegisterMachineOperation(IOctopusClientFactory clientFactory) : base(clientFactory) { - this.clientFactory = clientFactory ?? new OctopusClientFactory(); + } /// @@ -52,51 +50,6 @@ public RegisterMachineOperation(IOctopusClientFactory clientFactory) /// public string[] TenantTags { get; set; } - /// - /// Gets or sets the name of the machine that will be used within Octopus to identify this machine. - /// - public string MachineName { get; set; } - - /// - /// Get or sets the machine policy that applied to this machine. - /// - public string MachinePolicy { get; set; } - - /// - /// Gets or sets the hostname that Octopus should use when communicating with the Tentacle. - /// - public string TentacleHostname { get; set; } - - /// - /// Gets or sets the TCP port that Octopus should use when communicating with the Tentacle. - /// - public int TentaclePort { get; set; } - - /// - /// Gets or sets the certificate thumbprint that Octopus should expect when communicating with the Tentacle. - /// - public string TentacleThumbprint { get; set; } - - /// - /// Gets or sets the name of the proxy that Octopus should use when communicating with the Tentacle. - /// - public string ProxyName { get; set; } - - /// - /// If a machine with the same name already exists, it won't be overwritten by default (instead, an - /// will be thrown). - /// Set this property to true if you do want the existing machine to be overwritten. - /// - public bool AllowOverwrite { get; set; } - - /// - /// The communication style to use with the Tentacle. Allowed values are: TentacleActive, in which case the - /// Tentacle will connect to the Octopus server for instructions; or, TentaclePassive, in which case the - /// Tentacle will listen for commands from the server (default). - /// - public CommunicationStyle CommunicationStyle { get; set; } - - public Uri SubscriptionId { get; set; } /// /// How the machine should participate in Tenanted Deployments. @@ -105,33 +58,6 @@ public RegisterMachineOperation(IOctopusClientFactory clientFactory) public TenantedDeploymentMode TenantedDeploymentParticipation { get; set; } #if SYNC_CLIENT - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy server endpoint. - /// - /// - public void Execute(OctopusServerEndpoint serverEndpoint) - { - using (var client = clientFactory.CreateClient(serverEndpoint)) - { - var repository = new OctopusRepository(client); - - Execute(repository); - } - } - - - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy server repository. - /// - /// - public void Execute(OctopusRepository repository) - { - Execute((IOctopusRepository)repository); - } /// /// Executes the operation against the specified Octopus Deploy server. @@ -139,7 +65,7 @@ public void Execute(OctopusRepository repository) /// The Octopus Deploy server repository. /// /// - public void Execute(IOctopusRepository repository) + public override void Execute(IOctopusRepository repository) { var selectedEnvironments = GetEnvironments(repository); var machinePolicy = GetMachinePolicy(repository); @@ -148,7 +74,8 @@ public void Execute(IOctopusRepository repository) ValidateTenantTags(repository); var proxy = GetProxy(repository); - ApplyChanges(machine, selectedEnvironments, machinePolicy, tenants, proxy); + ApplyBaseChanges(machine, machinePolicy, proxy); + ApplyDeploymentTargetChanges(machine, selectedEnvironments, tenants); if (machine.Id != null) repository.Machines.Modify(machine); @@ -199,30 +126,6 @@ List GetEnvironments(IOctopusRepository repository) return selectedEnvironments; } - MachinePolicyResource GetMachinePolicy(IOctopusRepository repository) - { - var machinePolicy = default(MachinePolicyResource); - if (!string.IsNullOrEmpty(MachinePolicy)) - { - machinePolicy = repository.MachinePolicies.FindByName(MachinePolicy); - if (machinePolicy == null) - throw new ArgumentException(CouldNotFindMessage("machine policy", MachinePolicy)); - } - return machinePolicy; - } - - ProxyResource GetProxy(IOctopusRepository repository) - { - var proxy = default(ProxyResource); - if (!string.IsNullOrEmpty(ProxyName)) - { - proxy = repository.Proxies.FindByName(ProxyName); - if (proxy == null) - throw new ArgumentException(CouldNotFindMessage("proxy name", ProxyName)); - } - return proxy; - } - MachineResource GetMachine(IOctopusRepository repository) { var existing = default(MachineResource); @@ -239,40 +142,13 @@ MachineResource GetMachine(IOctopusRepository repository) } #endif - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy server endpoint. - /// - /// - public async Task ExecuteAsync(OctopusServerEndpoint serverEndpoint) - { - using (var client = await clientFactory.CreateAsyncClient(serverEndpoint).ConfigureAwait(false)) - { - var repository = new OctopusAsyncRepository(client); - - await ExecuteAsync(repository).ConfigureAwait(false); - } - } - - /// - /// Executes the operation against the specified Octopus Deploy server. - /// - /// The Octopus Deploy server repository. - /// - /// - public async Task ExecuteAsync(OctopusAsyncRepository repository) - { - await ExecuteAsync((IOctopusAsyncRepository) repository); - } - /// /// Executes the operation against the specified Octopus Deploy server. /// /// The Octopus Deploy server repository. /// /// - public async Task ExecuteAsync(IOctopusAsyncRepository repository) + public override async Task ExecuteAsync(IOctopusAsyncRepository repository) { var selectedEnvironments = GetEnvironments(repository).ConfigureAwait(false); var machinePolicy = GetMachinePolicy(repository).ConfigureAwait(false); @@ -282,7 +158,8 @@ public async Task ExecuteAsync(IOctopusAsyncRepository repository) var proxy = GetProxy(repository).ConfigureAwait(false); var machine = await machineTask; - ApplyChanges(machine, await selectedEnvironments, await machinePolicy, await tenants, await proxy); + ApplyBaseChanges(machine, await machinePolicy, await proxy); + ApplyDeploymentTargetChanges(machine, await selectedEnvironments, await tenants); if (machine.Id != null) await repository.Machines.Modify(machine).ConfigureAwait(false); @@ -346,31 +223,6 @@ async Task> GetEnvironments(IOctopusAsyncRepository re return selectedEnvironments; } - async Task GetMachinePolicy(IOctopusAsyncRepository repository) - { - - var machinePolicy = default(MachinePolicyResource); - if (!string.IsNullOrEmpty(MachinePolicy)) - { - machinePolicy = await repository.MachinePolicies.FindByName(MachinePolicy).ConfigureAwait(false); - if (machinePolicy == null) - throw new ArgumentException(CouldNotFindMessage("machine policy", MachinePolicy)); - } - return machinePolicy; - } - - async Task GetProxy(IOctopusAsyncRepository repository) - { - var proxy = default(ProxyResource); - if (!string.IsNullOrEmpty(ProxyName)) - { - proxy = await repository.Proxies.FindByName(ProxyName).ConfigureAwait(false); - if (proxy == null) - throw new ArgumentException(CouldNotFindMessage("proxy name", ProxyName)); - } - return proxy; - } - async Task GetMachine(IOctopusAsyncRepository repository) { var existing = default(MachineResource); @@ -386,40 +238,13 @@ async Task GetMachine(IOctopusAsyncRepository repository) return existing ?? new MachineResource(); } - void ApplyChanges(MachineResource machine, IEnumerable environment, MachinePolicyResource machinePolicy, IEnumerable tenants, ProxyResource proxy) + void ApplyDeploymentTargetChanges(MachineResource machine, IEnumerable environment, IEnumerable tenants) { machine.EnvironmentIds = new ReferenceCollection(environment.Select(e => e.Id).ToArray()); machine.TenantIds = new ReferenceCollection(tenants.Select(t => t.Id).ToArray()); machine.TenantTags = new ReferenceCollection(TenantTags); machine.Roles = new ReferenceCollection(Roles); - machine.Name = MachineName; machine.TenantedDeploymentParticipation = TenantedDeploymentParticipation; - - if (machinePolicy != null) - machine.MachinePolicyId = machinePolicy.Id; - - if (CommunicationStyle == CommunicationStyle.TentaclePassive) - { - var listening = new ListeningTentacleEndpointResource(); - listening.Uri = new Uri("https://" + TentacleHostname.ToLowerInvariant() + ":" + TentaclePort.ToString(CultureInfo.InvariantCulture) + "/").ToString(); - listening.Thumbprint = TentacleThumbprint; - listening.ProxyId = proxy?.Id; - machine.Endpoint = listening; - } - else if (CommunicationStyle == CommunicationStyle.TentacleActive) - { - var polling = new PollingTentacleEndpointResource(); - polling.Uri = SubscriptionId.ToString(); - polling.Thumbprint = TentacleThumbprint; - machine.Endpoint = polling; - } - } - - static string CouldNotFindMessage(string modelType, params string[] missing) - { - return missing.Length == 1 - ? $"Could not find the {modelType} named {missing.Single()} on the Octopus server. Ensure the {modelType} exists and you have permission to access it." - : $"Could not find the {modelType}s named: {string.Join(", ", missing)} on the Octopus server. Ensure the {modelType}s exist and you have permission to access them."; } } } \ No newline at end of file diff --git a/source/Octopus.Client/Operations/RegisterMachineOperationBase.cs b/source/Octopus.Client/Operations/RegisterMachineOperationBase.cs new file mode 100644 index 000000000..23f2d5db8 --- /dev/null +++ b/source/Octopus.Client/Operations/RegisterMachineOperationBase.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client.Exceptions; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Client.Operations +{ + /// + /// Encapsulates the operation for registering machines. + /// + public abstract class RegisterMachineOperationBase : IRegisterMachineOperationBase + { + readonly IOctopusClientFactory clientFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The client factory. + public RegisterMachineOperationBase(IOctopusClientFactory clientFactory) + { + this.clientFactory = clientFactory ?? new OctopusClientFactory(); + } + + + /// + /// Gets or sets the name of the machine that will be used within Octopus to identify this machine. + /// + public string MachineName { get; set; } + + /// + /// Get or sets the machine policy that applied to this machine. + /// + public string MachinePolicy { get; set; } + + /// + /// Gets or sets the hostname that Octopus should use when communicating with the Tentacle. + /// + public string TentacleHostname { get; set; } + + /// + /// Gets or sets the TCP port that Octopus should use when communicating with the Tentacle. + /// + public int TentaclePort { get; set; } + + /// + /// Gets or sets the certificate thumbprint that Octopus should expect when communicating with the Tentacle. + /// + public string TentacleThumbprint { get; set; } + + /// + /// Gets or sets the name of the proxy that Octopus should use when communicating with the Tentacle. + /// + public string ProxyName { get; set; } + + /// + /// If a machine with the same name already exists, it won't be overwritten by default (instead, an + /// will be thrown). + /// Set this property to true if you do want the existing machine to be overwritten. + /// + public bool AllowOverwrite { get; set; } + + /// + /// The communication style to use with the Tentacle. Allowed values are: TentacleActive, in which case the + /// Tentacle will connect to the Octopus server for instructions; or, TentaclePassive, in which case the + /// Tentacle will listen for commands from the server (default). + /// + public CommunicationStyle CommunicationStyle { get; set; } + + public Uri SubscriptionId { get; set; } + +#if SYNC_CLIENT + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server endpoint. + /// + /// + public void Execute(OctopusServerEndpoint serverEndpoint) + { + using (var client = clientFactory.CreateClient(serverEndpoint)) + { + var repository = new OctopusRepository(client); + + Execute(repository); + } + } + + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server repository. + /// + /// + public void Execute(OctopusRepository repository) + { + Execute((IOctopusRepository)repository); + } + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server repository. + /// + /// + public abstract void Execute(IOctopusRepository repository); + + protected MachinePolicyResource GetMachinePolicy(IOctopusRepository repository) + { + var machinePolicy = default(MachinePolicyResource); + if (!string.IsNullOrEmpty(MachinePolicy)) + { + machinePolicy = repository.MachinePolicies.FindByName(MachinePolicy); + if (machinePolicy == null) + throw new ArgumentException(CouldNotFindMessage("machine policy", MachinePolicy)); + } + return machinePolicy; + } + + protected ProxyResource GetProxy(IOctopusRepository repository) + { + var proxy = default(ProxyResource); + if (!string.IsNullOrEmpty(ProxyName)) + { + proxy = repository.Proxies.FindByName(ProxyName); + if (proxy == null) + throw new ArgumentException(CouldNotFindMessage("proxy name", ProxyName)); + } + return proxy; + } +#endif + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server endpoint. + /// + /// + public async Task ExecuteAsync(OctopusServerEndpoint serverEndpoint) + { + using (var client = await clientFactory.CreateAsyncClient(serverEndpoint).ConfigureAwait(false)) + { + var repository = new OctopusAsyncRepository(client); + + await ExecuteAsync(repository).ConfigureAwait(false); + } + } + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server repository. + /// + /// + public async Task ExecuteAsync(OctopusAsyncRepository repository) + { + await ExecuteAsync((IOctopusAsyncRepository) repository); + } + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server repository. + /// + /// + public abstract Task ExecuteAsync(IOctopusAsyncRepository repository); + + protected async Task GetMachinePolicy(IOctopusAsyncRepository repository) + { + + var machinePolicy = default(MachinePolicyResource); + if (!string.IsNullOrEmpty(MachinePolicy)) + { + machinePolicy = await repository.MachinePolicies.FindByName(MachinePolicy).ConfigureAwait(false); + if (machinePolicy == null) + throw new ArgumentException(CouldNotFindMessage("machine policy", MachinePolicy)); + } + return machinePolicy; + } + + protected async Task GetProxy(IOctopusAsyncRepository repository) + { + var proxy = default(ProxyResource); + if (!string.IsNullOrEmpty(ProxyName)) + { + proxy = await repository.Proxies.FindByName(ProxyName).ConfigureAwait(false); + if (proxy == null) + throw new ArgumentException(CouldNotFindMessage("proxy name", ProxyName)); + } + return proxy; + } + + + protected void ApplyBaseChanges(MachineBasedResource machine, MachinePolicyResource machinePolicy, ProxyResource proxy) + { + machine.Name = MachineName; + if (machinePolicy != null) + machine.MachinePolicyId = machinePolicy.Id; + + if (CommunicationStyle == CommunicationStyle.TentaclePassive) + { + var listening = new ListeningTentacleEndpointResource(); + listening.Uri = new Uri("https://" + TentacleHostname.ToLowerInvariant() + ":" + TentaclePort.ToString(CultureInfo.InvariantCulture) + "/").ToString(); + listening.Thumbprint = TentacleThumbprint; + listening.ProxyId = proxy?.Id; + machine.Endpoint = listening; + } + else if (CommunicationStyle == CommunicationStyle.TentacleActive) + { + var polling = new PollingTentacleEndpointResource(); + polling.Uri = SubscriptionId.ToString(); + polling.Thumbprint = TentacleThumbprint; + machine.Endpoint = polling; + } + } + + protected static string CouldNotFindMessage(string modelType, params string[] missing) + { + return missing.Length == 1 + ? $"Could not find the {modelType} named {missing.Single()} on the Octopus server. Ensure the {modelType} exists and you have permission to access it." + : $"Could not find the {modelType}s named: {string.Join(", ", missing)} on the Octopus server. Ensure the {modelType}s exist and you have permission to access them."; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Operations/RegisterWorkerOperation.cs b/source/Octopus.Client/Operations/RegisterWorkerOperation.cs new file mode 100644 index 000000000..dbaa6cfee --- /dev/null +++ b/source/Octopus.Client/Operations/RegisterWorkerOperation.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Octopus.Client.Exceptions; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Client.Operations +{ + /// + /// Encapsulates the operation for registering machines. + /// + public class RegisterWorkerOperation : RegisterMachineOperationBase, IRegisterWorkerOperation + { + /// + /// Initializes a new instance of the class. + /// + public RegisterWorkerOperation() : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The client factory. + public RegisterWorkerOperation(IOctopusClientFactory clientFactory) : base(clientFactory) + { + + } + + /// + /// Gets or sets the worker pools that this machine should be added to. + /// + public string[] WorkerPoolNames { get; set; } + +#if SYNC_CLIENT + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server repository. + /// + /// + public override void Execute(IOctopusRepository repository) + { + var selectedPools = GetWorkerPools(repository); + var machinePolicy = GetMachinePolicy(repository); + var machine = GetWorker(repository); + var proxy = GetProxy(repository); + + ApplyBaseChanges(machine, machinePolicy, proxy); + + machine.WorkerPoolIds = new ReferenceCollection(selectedPools.Select(p => p.Id).ToArray()); + + if (machine.Id != null) + repository.Workers.Modify(machine); + else + repository.Workers.Create(machine); + } + + List GetWorkerPools(IOctopusRepository repository) + { + var selectedPools = repository.WorkerPools.FindByNames(WorkerPoolNames); + + var missing = WorkerPoolNames.Except(selectedPools.Select(p => p.Name), StringComparer.OrdinalIgnoreCase).ToList(); + + if (missing.Any()) + throw new ArgumentException(CouldNotFindMessage("worker pool", missing.ToArray())); + + return selectedPools; + } + + WorkerResource GetWorker(IOctopusRepository repository) + { + var existing = default(WorkerResource); + try + { + existing = repository.Workers.FindByName(MachineName); + if (!AllowOverwrite && existing?.Id != null) + throw new ArgumentException($"A worker named '{MachineName}' already exists. Use the 'force' parameter if you intended to update the existing machine."); + } + catch (OctopusDeserializationException) // eat it, probably caused by resource incompatability between versions + { + } + return existing ?? new WorkerResource(); + } +#endif + + /// + /// Executes the operation against the specified Octopus Deploy server. + /// + /// The Octopus Deploy server repository. + /// + /// + public override async Task ExecuteAsync(IOctopusAsyncRepository repository) + { + var selectedPools = GetWorkerPools(repository).ConfigureAwait(false); + var machinePolicy = GetMachinePolicy(repository).ConfigureAwait(false); + var machineTask = GetWorker(repository).ConfigureAwait(false); + var proxy = GetProxy(repository).ConfigureAwait(false); + + var machine = await machineTask; + ApplyBaseChanges(machine, await machinePolicy, await proxy); + + machine.WorkerPoolIds = new ReferenceCollection((await selectedPools).Select(p => p.Id).ToArray()); + + if (machine.Id != null) + await repository.Workers.Modify(machine).ConfigureAwait(false); + else + await repository.Workers.Create(machine).ConfigureAwait(false); + } + + async Task> GetWorkerPools(IOctopusAsyncRepository repository) + { + var selectedPools = await repository.WorkerPools.FindByNames(WorkerPoolNames).ConfigureAwait(false); + + var missing = WorkerPoolNames.Except(selectedPools.Select(p => p.Name), StringComparer.OrdinalIgnoreCase).ToList(); + + if (missing.Any()) + throw new ArgumentException(CouldNotFindMessage("worker pool", missing.ToArray())); + + return selectedPools; + } + + async Task GetWorker(IOctopusAsyncRepository repository) + { + var existing = default(WorkerResource); + try + { + existing = await repository.Workers.FindByName(MachineName).ConfigureAwait(false); + if (!AllowOverwrite && existing?.Id != null) + throw new ArgumentException($"A worker named '{MachineName}' already exists. Use the 'force' parameter if you intended to update the existing machine."); + } + catch (OctopusDeserializationException) // eat it, probably caused by resource incompatability between versions + { + } + return existing ?? new WorkerResource(); + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Repositories/Async/TaskRepository.cs b/source/Octopus.Client/Repositories/Async/TaskRepository.cs index 332588e11..033143312 100644 --- a/source/Octopus.Client/Repositories/Async/TaskRepository.cs +++ b/source/Octopus.Client/Repositories/Async/TaskRepository.cs @@ -10,10 +10,10 @@ namespace Octopus.Client.Repositories.Async { public interface ITaskRepository : IPaginate, IGet, ICreate { - Task ExecuteHealthCheck(string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null); + Task ExecuteHealthCheck(string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null, string restrictTo = null, string workerpoolId = null, string[] workerIds = null); Task ExecuteCalamariUpdate(string description = null, string[] machineIds = null); Task ExecuteBackup(string description = null); - Task ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null); + Task ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null, string restrictTo = null, string workerpooltId = null, string[] workerIds = null); Task ExecuteAdHocScript(string scriptBody, string[] machineIds = null, string[] environmentIds = null, string[] targetRoles = null, string description = null, string syntax = "PowerShell"); Task GetDetails(TaskResource resource, bool? includeVerboseOutput = null, int? tail = null); Task ExecuteActionTemplate(ActionTemplateResource resource, Dictionary properties, string[] machineIds = null, string[] environmentIds = null, string[] targetRoles = null, string description = null); @@ -37,7 +37,9 @@ public TaskRepository(IOctopusAsyncClient client) { } - public Task ExecuteHealthCheck(string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null) + public Task ExecuteHealthCheck( + string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null, + string restrictTo = null, string workerpoolId = null, string[] workerIds = null) { var resource = new TaskResource(); resource.Name = BuiltInTasks.Health.Name; @@ -47,7 +49,9 @@ public Task ExecuteHealthCheck(string description = null, int time {BuiltInTasks.Health.Arguments.Timeout, TimeSpan.FromMinutes(timeoutAfterMinutes)}, {BuiltInTasks.Health.Arguments.MachineTimeout, TimeSpan.FromMinutes(machineTimeoutAfterMinutes)}, {BuiltInTasks.Health.Arguments.EnvironmentId, environmentId}, - {BuiltInTasks.Health.Arguments.MachineIds, machineIds} + {BuiltInTasks.Health.Arguments.WorkerpoolId, workerpoolId}, + {BuiltInTasks.Health.Arguments.RestrictedTo, restrictTo}, + {BuiltInTasks.Health.Arguments.MachineIds, machineIds?.Concat(workerIds ?? new string[0]).ToArray() ?? workerIds} }; return Create(resource); } @@ -72,7 +76,7 @@ public Task ExecuteBackup(string description = null) return Create(resource); } - public Task ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null) + public Task ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null, string restrictTo = null, string workerpoolId = null, string[] workerIds = null) { var resource = new TaskResource(); resource.Name = BuiltInTasks.Upgrade.Name; @@ -80,7 +84,9 @@ public Task ExecuteTentacleUpgrade(string description = null, stri resource.Arguments = new Dictionary { {BuiltInTasks.Upgrade.Arguments.EnvironmentId, environmentId}, - {BuiltInTasks.Upgrade.Arguments.MachineIds, machineIds} + {BuiltInTasks.Upgrade.Arguments.WorkerpoolId, workerpoolId}, + {BuiltInTasks.Upgrade.Arguments.RestrictedTo, restrictTo}, + {BuiltInTasks.Upgrade.Arguments.MachineIds, machineIds?.Concat(workerIds ?? new string[0]).ToArray() ?? workerIds} }; return Create(resource); } @@ -150,7 +156,7 @@ public Task Rerun(TaskResource resource) { return Client.Post(resource.Link("Rerun"), (TaskResource)null); } - + public Task Cancel(TaskResource resource) { return Client.Post(resource.Link("Cancel"), (TaskResource)null); @@ -180,11 +186,11 @@ public Task WaitForCompletion(TaskResource[] tasks, int pollIntervalSeconds = 4, return WaitForCompletion(tasks, pollIntervalSeconds, timeoutAfterMinutes, taskInterval); } - public Task WaitForCompletion(TaskResource[] tasks, int pollIntervalSeconds = 4, int timeoutAfterMinutes = 0, Func interval = null) + public Task WaitForCompletion(TaskResource[] tasks, int pollIntervalSeconds = 4, int timeoutAfterMinutes = 0, Func interval = null) => WaitForCompletion(tasks, pollIntervalSeconds, TimeSpan.FromMinutes(timeoutAfterMinutes), interval); public async Task WaitForCompletion(TaskResource[] tasks, int pollIntervalSeconds = 4, TimeSpan? timeoutAfter = null, Func interval = null) - { + { var start = Stopwatch.StartNew(); if (tasks == null || tasks.Length == 0) return; @@ -212,7 +218,7 @@ public async Task WaitForCompletion(TaskResource[] tasks, int pollIntervalSecond } /// - /// + /// /// /// Number of items per page, setting to less than the total items still retreives all items, but uses multiple requests reducing memory load on the server /// diff --git a/source/Octopus.Client/Repositories/Async/WorkerPoolRepository.cs b/source/Octopus.Client/Repositories/Async/WorkerPoolRepository.cs new file mode 100644 index 000000000..33f176802 --- /dev/null +++ b/source/Octopus.Client/Repositories/Async/WorkerPoolRepository.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Octopus.Client.Editors.Async; +using Octopus.Client.Model; + +namespace Octopus.Client.Repositories.Async +{ + public interface IWorkerPoolRepository : IFindByName, IGet, ICreate, IModify, IDelete, IGetAll + { + Task> GetMachines(WorkerPoolResource workerPool, + int? skip = 0, + int? take = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null); + Task Summary( + string ids = null, + string partialName = null, + string machinePartialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + bool? hideEmptyPools = false); + Task Sort(string[] workerPoolIdsInOrder); + Task CreateOrModify(string name); + Task CreateOrModify(string name, string description); + } + + class WorkerPoolRepository : BasicRepository, IWorkerPoolRepository + { + public WorkerPoolRepository(IOctopusAsyncClient client) + : base(client, "WorkerPools") + { + } + + public async Task> GetMachines(WorkerPoolResource workerPool, + int? skip = 0, + int? take = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null) + { + var resources = new List(); + + await Client.Paginate(workerPool.Link("Workers"), new { + skip, + take, + partialName, + isDisabled, + healthStatuses, + commStyles, + }, page => + { + resources.AddRange(page.Items); + return true; + }).ConfigureAwait(false); + + return resources; + } + + public Task Summary( + string ids = null, + string partialName = null, + string machinePartialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + bool? hideEmptyPools = false) + { + return Client.Get(Client.RootDocument.Link("WorkerPoolsSummary"), new + { + ids, + partialName, + machinePartialName, + isDisabled, + healthStatuses, + commStyles, + hideEmptyPools, + }); + } + + public Task Sort(string[] workerPoolIdsInOrder) + { + return Client.Put(Client.RootDocument.Link("WorkerPoolSortOrder"), workerPoolIdsInOrder); + } + + public Task CreateOrModify(string name) + { + return new WorkerPoolEditor(this).CreateOrModify(name); + } + + public Task CreateOrModify(string name, string description) + { + return new WorkerPoolEditor(this).CreateOrModify(name, description); + } + } +} diff --git a/source/Octopus.Client/Repositories/Async/WorkerRepository.cs b/source/Octopus.Client/Repositories/Async/WorkerRepository.cs new file mode 100644 index 000000000..e6a2313be --- /dev/null +++ b/source/Octopus.Client/Repositories/Async/WorkerRepository.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Octopus.Client.Editors.Async; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Client.Repositories.Async +{ + public interface IWorkerRepository : IFindByName, IGet, ICreate, IModify, IDelete + { + Task Discover(string host, int port = 10933, DiscoverableEndpointType? discoverableEndpointType = null); + Task GetConnectionStatus(WorkerResource machine); + Task> FindByThumbprint(string thumbprint); + + Task CreateOrModify( + string name, + EndpointResource endpoint, + WorkerPoolResource[] pools); + + Task> List(int skip = 0, + int? take = null, + string ids = null, + string name = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + string workerpoolIds = null); + } + + class WorkerRepository : BasicRepository, IWorkerRepository + { + public WorkerRepository(IOctopusAsyncClient client) : base(client, "Workers") + { + } + + public Task Discover(string host, int port = 10933, DiscoverableEndpointType? type = null) + { + return Client.Get(Client.RootDocument.Link("DiscoverMachine"), new { host, port, type }); + } + + public Task GetConnectionStatus(WorkerResource worker) + { + if (worker == null) throw new ArgumentNullException("worker"); + return Client.Get(worker.Link("Connection")); + } + + public Task> FindByThumbprint(string thumbprint) + { + if (thumbprint == null) throw new ArgumentNullException("thumbprint"); + return Client.Get>(Client.RootDocument.Link("Workers"), new { id = "all", thumbprint }); + } + + public Task CreateOrModify( + string name, + EndpointResource endpoint, + WorkerPoolResource[] workerpools) + { + return new WorkerEditor(this).CreateOrModify(name, endpoint, workerpools); + } + + public Task> List(int skip = 0, + int? take = null, + string ids = null, + string name = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + string workerpoolIds = null) + { + return Client.List(Client.RootDocument.Link("Workers"), new + { + skip, + take, + ids, + name, + partialName, + isDisabled, + healthStatuses, + commStyles, + workerpoolIds + }); + } + } +} diff --git a/source/Octopus.Client/Repositories/TaskRepository.cs b/source/Octopus.Client/Repositories/TaskRepository.cs index 3b6c6a649..be2eb46cf 100644 --- a/source/Octopus.Client/Repositories/TaskRepository.cs +++ b/source/Octopus.Client/Repositories/TaskRepository.cs @@ -9,10 +9,10 @@ namespace Octopus.Client.Repositories { public interface ITaskRepository : IPaginate, IGet, ICreate { - TaskResource ExecuteHealthCheck(string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null); + TaskResource ExecuteHealthCheck(string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null, string restrictTo = null, string workerpoolId = null, string[] workerIds = null); TaskResource ExecuteCalamariUpdate(string description = null, string[] machineIds = null); TaskResource ExecuteBackup(string description = null); - TaskResource ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null); + TaskResource ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null, string restrictTo = null, string workerpoolId = null, string[] workerIds = null); TaskResource ExecuteAdHocScript(string scriptBody, string[] machineIds = null, string[] environmentIds = null, string[] targetRoles = null, string description = null, string syntax = "PowerShell"); TaskResource ExecuteActionTemplate(ActionTemplateResource resource, Dictionary properties, string[] machineIds = null, string[] environmentIds = null, string[] targetRoles = null, string description = null); TaskResource ExecuteCommunityActionTemplatesSynchronisation(string description = null); @@ -35,7 +35,10 @@ public TaskRepository(IOctopusClient client) { } - public TaskResource ExecuteHealthCheck(string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null) + public TaskResource ExecuteHealthCheck( + string description = null, int timeoutAfterMinutes = 5, int machineTimeoutAfterMinutes = 1, string environmentId = null, string[] machineIds = null, + string restrictTo = null, string workerpoolId = null, string[] workerIds = null + ) { var resource = new TaskResource(); resource.Name = BuiltInTasks.Health.Name; @@ -45,7 +48,9 @@ public TaskResource ExecuteHealthCheck(string description = null, int timeoutAft {BuiltInTasks.Health.Arguments.Timeout, TimeSpan.FromMinutes(timeoutAfterMinutes)}, {BuiltInTasks.Health.Arguments.MachineTimeout, TimeSpan.FromMinutes(machineTimeoutAfterMinutes)}, {BuiltInTasks.Health.Arguments.EnvironmentId, environmentId}, - {BuiltInTasks.Health.Arguments.MachineIds, machineIds} + {BuiltInTasks.Health.Arguments.WorkerpoolId, workerpoolId}, + {BuiltInTasks.Health.Arguments.RestrictedTo, restrictTo}, + {BuiltInTasks.Health.Arguments.MachineIds, machineIds?.Concat(workerIds ?? new string[0]).ToArray() ?? workerIds} }; return Create(resource); } @@ -70,7 +75,7 @@ public TaskResource ExecuteBackup(string description = null) return Create(resource); } - public TaskResource ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null) + public TaskResource ExecuteTentacleUpgrade(string description = null, string environmentId = null, string[] machineIds = null, string restrictTo = null, string workerpoolId = null, string[] workerIds = null) { var resource = new TaskResource(); resource.Name = BuiltInTasks.Upgrade.Name; @@ -78,7 +83,9 @@ public TaskResource ExecuteTentacleUpgrade(string description = null, string env resource.Arguments = new Dictionary { {BuiltInTasks.Upgrade.Arguments.EnvironmentId, environmentId}, - {BuiltInTasks.Upgrade.Arguments.MachineIds, machineIds} + {BuiltInTasks.Upgrade.Arguments.WorkerpoolId, workerpoolId}, + {BuiltInTasks.Upgrade.Arguments.RestrictedTo, restrictTo}, + {BuiltInTasks.Upgrade.Arguments.MachineIds, machineIds?.Concat(workerIds ?? new string[0]).ToArray() ?? workerIds} }; return Create(resource); } diff --git a/source/Octopus.Client/Repositories/WorkerPoolRepository.cs b/source/Octopus.Client/Repositories/WorkerPoolRepository.cs new file mode 100644 index 000000000..bddbf5e67 --- /dev/null +++ b/source/Octopus.Client/Repositories/WorkerPoolRepository.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using Octopus.Client.Editors; +using Octopus.Client.Model; + +namespace Octopus.Client.Repositories +{ + public interface IWorkerPoolRepository : IFindByName, IGet, ICreate, IModify, IDelete, IGetAll + { + List GetMachines(WorkerPoolResource workerPool, + int? skip = 0, + int? take = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null); + WorkerPoolsSummaryResource Summary( + string ids = null, + string partialName = null, + string machinePartialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + bool? hideEmptypools = false); + void Sort(string[] workerpoolIdsInOrder); + WorkerPoolEditor CreateOrModify(string name); + WorkerPoolEditor CreateOrModify(string name, string description); + } + + class WorkerPoolRepository : BasicRepository, IWorkerPoolRepository + { + public WorkerPoolRepository(IOctopusClient client) + : base(client, "WorkerPools") + { + } + + public List GetMachines(WorkerPoolResource workerPool, + int? skip = 0, + int? take = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null) + { + var resources = new List(); + + Client.Paginate(workerPool.Link("Workers"), new + { + skip, + take, + partialName, + isDisabled, + healthStatuses, + commStyles + }, page => + { + resources.AddRange(page.Items); + return true; + }); + + return resources; + } + + public WorkerPoolsSummaryResource Summary( + string ids = null, + string partialName = null, + string machinePartialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + bool? hideEmptyPools = false) + { + return Client.Get(Client.RootDocument.Link("WorkerPoolsSummary"), new + { + ids, + partialName, + machinePartialName, + isDisabled, + healthStatuses, + commStyles, + hideEmptyPools, + }); + } + + public void Sort(string[] workerpoolIdsInOrder) + { + Client.Put(Client.RootDocument.Link("WorkerPoolsSortOrder"), workerpoolIdsInOrder); + } + + public WorkerPoolEditor CreateOrModify(string name) + { + return new WorkerPoolEditor(this).CreateOrModify(name); + } + + public WorkerPoolEditor CreateOrModify(string name, string description) + { + return new WorkerPoolEditor(this).CreateOrModify(name, description); + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Repositories/WorkerRepository.cs b/source/Octopus.Client/Repositories/WorkerRepository.cs new file mode 100644 index 000000000..f35bca3ea --- /dev/null +++ b/source/Octopus.Client/Repositories/WorkerRepository.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using Octopus.Client.Editors; +using Octopus.Client.Model; +using Octopus.Client.Model.Endpoints; + +namespace Octopus.Client.Repositories +{ + public interface IWorkerRepository : IFindByName, IGet, ICreate, IModify, IDelete + { + WorkerResource Discover(string host, int port = 10933, DiscoverableEndpointType? discoverableEndpointType = null); + MachineConnectionStatus GetConnectionStatus(WorkerResource machine); + List FindByThumbprint(string thumbprint); + + WorkerEditor CreateOrModify( + string name, + EndpointResource endpoint, + WorkerPoolResource[] pools); + + ResourceCollection List(int skip = 0, + int? take = null, + string ids = null, + string name = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + string workerpoolIds = null); + } + + class WorkerRepository : BasicRepository, IWorkerRepository + { + public WorkerRepository(IOctopusClient client) : base(client, "Workers") + { + } + + public WorkerResource Discover(string host, int port = 10933, DiscoverableEndpointType? type = null) + { + return Client.Get(Client.RootDocument.Link("DiscoverMachine"), new { host, port, type }); + } + + public MachineConnectionStatus GetConnectionStatus(WorkerResource machine) + { + if (machine == null) throw new ArgumentNullException("machine"); + return Client.Get(machine.Link("Connection")); + } + + public List FindByThumbprint(string thumbprint) + { + if (thumbprint == null) throw new ArgumentNullException("thumbprint"); + return Client.Get>(Client.RootDocument.Link("Workers"), new { id = "all", thumbprint }); + } + + + public WorkerEditor CreateOrModify( + string name, + EndpointResource endpoint, + WorkerPoolResource[] pools) + { + return new WorkerEditor(this).CreateOrModify(name, endpoint, pools); + } + + public ResourceCollection List(int skip = 0, + int? take = null, + string ids = null, + string name = null, + string partialName = null, + bool? isDisabled = false, + string healthStatuses = null, + string commStyles = null, + string workerpoolIds = null) + { + return Client.List(Client.RootDocument.Link("workers"), new + { + skip, + take, + ids, + name, + partialName, + isDisabled, + healthStatuses, + commStyles, + workerpoolIds + }); + } + } +} \ No newline at end of file