From 92ba6a102db7c7932b645ea1762f14c0565c9fb1 Mon Sep 17 00:00:00 2001 From: Paul Gradie Date: Wed, 24 Jun 2020 10:56:39 +1000 Subject: [PATCH] Adds new runbook run endpoint * Adds new endpoints for creating Runbook Runs * Adds version awareness to runbook run methods * Adjust version tolerance to ensure integration tests are happy and to support pre-release canary customers Co-authored-by: Andrew Best --- ...AreaShouldNotRegress..NETCore.approved.txt | 24 +++++++ ...houldNotRegress..NETFramework.approved.txt | 24 +++++++ .../Model/RunbookRunParameters.cs | 63 +++++++++++++++++++ .../Repositories/Async/RunbookRepository.cs | 38 ++++++++++- .../Repositories/RunbookRepository.cs | 36 ++++++++++- 5 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 source/Octopus.Client/Model/RunbookRunParameters.cs diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt index 5966ffba3..ed930cf8b 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt @@ -4312,6 +4312,28 @@ Octopus.Client.Model Int32 QuantityToKeep { get; set; } Boolean ShouldKeepForever { get; set; } } + class RunbookRunParameters + { + .ctor() + String EnvironmentId { get; set; } + String[] EnvironmentIds { get; set; } + String[] ExcludedMachineIds { get; set; } + Boolean ForcePackageDownload { get; set; } + Dictionary FormValues { get; set; } + String ProjectId { get; set; } + Nullable QueueTime { get; set; } + Nullable QueueTimeExpiry { get; set; } + String RunbookId { get; set; } + String RunbookSnapshotNameOrId { get; set; } + String[] SkipActions { get; set; } + String[] SpecificMachineIds { get; set; } + String TenantId { get; set; } + String[] TenantIds { get; set; } + String[] TenantTagNames { get; set; } + Boolean UseDefaultSnapshot { get; set; } + Nullable UseGuidedFailure { get; set; } + static Octopus.Client.Model.RunbookRunParameters MapFrom(Octopus.Client.Model.RunbookRunResource) + } class RunbookRunPreviewResource Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource @@ -6892,6 +6914,7 @@ Octopus.Client.Repositories Octopus.Client.Model.RunbookRunTemplateResource GetRunbookRunTemplate(Octopus.Client.Model.RunbookResource) Octopus.Client.Model.RunbookSnapshotTemplateResource GetRunbookSnapshotTemplate(Octopus.Client.Model.RunbookResource) Octopus.Client.Model.RunbookRunResource Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunResource) + Octopus.Client.Model.RunbookRunResource[] Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunParameters) } interface IRunbookRunRepository Octopus.Client.Repositories.IGet @@ -7581,6 +7604,7 @@ Octopus.Client.Repositories.Async Task GetRunbookRunTemplate(Octopus.Client.Model.RunbookResource) Task GetRunbookSnapshotTemplate(Octopus.Client.Model.RunbookResource) Task Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunResource) + Task Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunParameters) } interface IRunbookRunRepository Octopus.Client.Repositories.Async.IGet diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt index 47267ebf3..d64b54298 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt @@ -4331,6 +4331,28 @@ Octopus.Client.Model Int32 QuantityToKeep { get; set; } Boolean ShouldKeepForever { get; set; } } + class RunbookRunParameters + { + .ctor() + String EnvironmentId { get; set; } + String[] EnvironmentIds { get; set; } + String[] ExcludedMachineIds { get; set; } + Boolean ForcePackageDownload { get; set; } + Dictionary FormValues { get; set; } + String ProjectId { get; set; } + Nullable QueueTime { get; set; } + Nullable QueueTimeExpiry { get; set; } + String RunbookId { get; set; } + String RunbookSnapshotNameOrId { get; set; } + String[] SkipActions { get; set; } + String[] SpecificMachineIds { get; set; } + String TenantId { get; set; } + String[] TenantIds { get; set; } + String[] TenantTagNames { get; set; } + Boolean UseDefaultSnapshot { get; set; } + Nullable UseGuidedFailure { get; set; } + static Octopus.Client.Model.RunbookRunParameters MapFrom(Octopus.Client.Model.RunbookRunResource) + } class RunbookRunPreviewResource Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource @@ -6917,6 +6939,7 @@ Octopus.Client.Repositories Octopus.Client.Model.RunbookRunTemplateResource GetRunbookRunTemplate(Octopus.Client.Model.RunbookResource) Octopus.Client.Model.RunbookSnapshotTemplateResource GetRunbookSnapshotTemplate(Octopus.Client.Model.RunbookResource) Octopus.Client.Model.RunbookRunResource Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunResource) + Octopus.Client.Model.RunbookRunResource[] Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunParameters) } interface IRunbookRunRepository Octopus.Client.Repositories.IGet @@ -7606,6 +7629,7 @@ Octopus.Client.Repositories.Async Task GetRunbookRunTemplate(Octopus.Client.Model.RunbookResource) Task GetRunbookSnapshotTemplate(Octopus.Client.Model.RunbookResource) Task Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunResource) + Task Run(Octopus.Client.Model.RunbookResource, Octopus.Client.Model.RunbookRunParameters) } interface IRunbookRunRepository Octopus.Client.Repositories.Async.IGet diff --git a/source/Octopus.Client/Model/RunbookRunParameters.cs b/source/Octopus.Client/Model/RunbookRunParameters.cs new file mode 100644 index 000000000..8b6eacdfe --- /dev/null +++ b/source/Octopus.Client/Model/RunbookRunParameters.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Octopus.Client.Model +{ + public class RunbookRunParameters + { + /// + /// Parameter class for marshalling params between OctopusCLI and Octopus Server + /// This class is used to facilitate backwards compatibility while extending /runbooks/{id}/run" + /// + public RunbookRunParameters() + { + FormValues = new Dictionary(); + SpecificMachineIds = new string[0]; + ExcludedMachineIds = new string[0]; + EnvironmentIds = new string[0]; + TenantTagNames = new string[0]; + TenantIds = new string[0]; + SkipActions = new string[0]; + } + + public bool UseDefaultSnapshot { get; set; } = true; + + public string RunbookId { get; set; } + public string ProjectId { get; set; } + public string RunbookSnapshotNameOrId { get; set; } + public string EnvironmentId { get; set; } + public string[] EnvironmentIds { get; set; } + public bool ForcePackageDownload { get; set; } + public bool? UseGuidedFailure { get; set; } + public string[] SpecificMachineIds { get; set; } + public string[] ExcludedMachineIds { get; set; } + public string TenantId { get; set; } + public string[] TenantIds { get; set; } + public string[] TenantTagNames { get; set; } + public string[] SkipActions { get; set; } + public DateTimeOffset? QueueTime { get; set; } + public DateTimeOffset? QueueTimeExpiry { get; set; } + public Dictionary FormValues { get; set; } + + public static RunbookRunParameters MapFrom(RunbookRunResource runbookRun) + { + return new RunbookRunParameters + { + UseDefaultSnapshot = true, + RunbookId = runbookRun.RunbookId, + ProjectId = runbookRun.ProjectId, + EnvironmentId = runbookRun.EnvironmentId, + ForcePackageDownload = runbookRun.ForcePackageDownload, + UseGuidedFailure = runbookRun.UseGuidedFailure, + SpecificMachineIds = runbookRun.SpecificMachineIds != null ? runbookRun.SpecificMachineIds.ToArray() : new string[0], + ExcludedMachineIds = runbookRun.ExcludedMachineIds != null ? runbookRun.ExcludedMachineIds.ToArray() : new string[0], + TenantId = runbookRun.TenantId, + SkipActions = runbookRun.SkipActions != null ? runbookRun.SkipActions.ToArray() : new string[0], + QueueTime = runbookRun.QueueTime, + QueueTimeExpiry = runbookRun.QueueTimeExpiry, + FormValues = runbookRun.FormValues ?? new Dictionary() + }; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Client/Repositories/Async/RunbookRepository.cs b/source/Octopus.Client/Repositories/Async/RunbookRepository.cs index d7018f340..45347410b 100644 --- a/source/Octopus.Client/Repositories/Async/RunbookRepository.cs +++ b/source/Octopus.Client/Repositories/Async/RunbookRepository.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Octopus.Client.Editors.Async; +using Octopus.Client.Exceptions; using Octopus.Client.Model; namespace Octopus.Client.Repositories.Async @@ -12,13 +14,19 @@ public interface IRunbookRepository : IFindByName, IGet GetRunbookRunTemplate(RunbookResource runbook); Task GetPreview(DeploymentPromotionTarget promotionTarget); Task Run(RunbookResource runbook, RunbookRunResource runbookRun); + Task Run(RunbookResource runbook, RunbookRunParameters runbookRunParameters); } class RunbookRepository : BasicRepository, IRunbookRepository { + private readonly SemanticVersion integrationTestVersion; + private readonly SemanticVersion versionAfterWhichRunbookRunParametersAreAvailable; + public RunbookRepository(IOctopusAsyncRepository repository) : base(repository, "Runbooks") { + integrationTestVersion = SemanticVersion.Parse("0.0.0-local"); + versionAfterWhichRunbookRunParametersAreAvailable = SemanticVersion.Parse("2020.2.99999"); } public Task FindByName(ProjectResource project, string name) @@ -46,9 +54,33 @@ public Task GetPreview(DeploymentPromotionTarget prom return Client.Get(promotionTarget.Link("RunbookRunPreview")); } - public Task Run(RunbookResource runbook, RunbookRunResource runbookRun) + private bool ServerSupportsRunbookRunParameters(string version) + { + var serverVersion = SemanticVersion.Parse(version); + + return serverVersion >= versionAfterWhichRunbookRunParametersAreAvailable || + serverVersion == integrationTestVersion; + } + + public async Task Run(RunbookResource runbook, RunbookRunResource runbookRun) { - return Client.Post(runbook.Link("CreateRunbookRun"), runbookRun); + var serverSupportsRunbookRunParameters = ServerSupportsRunbookRunParameters((await Repository.LoadRootDocument()).Version); + + return serverSupportsRunbookRunParameters + ? (await Run(runbook, RunbookRunParameters.MapFrom(runbookRun))).FirstOrDefault() + : await Client.Post(runbook.Link("CreateRunbookRun"), runbookRun); + } + + public async Task Run(RunbookResource runbook, RunbookRunParameters runbookRunParameters) + { + var serverVersion = (await Repository.LoadRootDocument()).Version; + var serverSupportsRunbookRunParameters = ServerSupportsRunbookRunParameters(serverVersion); + + if (serverSupportsRunbookRunParameters == false) + throw new UnsupportedApiVersionException($"This Octopus Deploy server is an older version ({serverVersion}) that does not yet support RunbookRunParameters. " + + $"Please update your Octopus Deploy server to 2020.3.* or newer to access this feature."); + + return await Client.Post(runbook.Link("CreateRunbookRun"), runbookRunParameters); } } } diff --git a/source/Octopus.Client/Repositories/RunbookRepository.cs b/source/Octopus.Client/Repositories/RunbookRepository.cs index dc6f3fdf3..c0d8d4211 100644 --- a/source/Octopus.Client/Repositories/RunbookRepository.cs +++ b/source/Octopus.Client/Repositories/RunbookRepository.cs @@ -1,4 +1,6 @@ +using System.Linq; using Octopus.Client.Editors; +using Octopus.Client.Exceptions; using Octopus.Client.Model; namespace Octopus.Client.Repositories @@ -11,13 +13,19 @@ public interface IRunbookRepository : IFindByName, IGet, IRunbookRepository { + private readonly SemanticVersion versionAfterWhichRunbookRunParametersAreAvailable; + private readonly SemanticVersion integrationTestVersion; + public RunbookRepository(IOctopusRepository repository) : base(repository, "Runbooks") { + integrationTestVersion = SemanticVersion.Parse("0.0.0-local"); + versionAfterWhichRunbookRunParametersAreAvailable = SemanticVersion.Parse("2020.2.99999"); } public RunbookResource FindByName(ProjectResource project, string name) @@ -45,9 +53,33 @@ public RunbookRunPreviewResource GetPreview(DeploymentPromotionTarget promotionT return Client.Get(promotionTarget.Link("RunbookRunPreview")); } + private bool ServerSupportsRunbookRunParameters(string version) + { + var serverVersion = SemanticVersion.Parse(version); + + return serverVersion >= versionAfterWhichRunbookRunParametersAreAvailable || + serverVersion == integrationTestVersion; + } + public RunbookRunResource Run(RunbookResource runbook, RunbookRunResource runbookRun) { - return Client.Post(runbook.Link("CreateRunbookRun"), runbookRun); + var serverSupportsRunbookRunParameters = ServerSupportsRunbookRunParameters(Repository.LoadRootDocument().Version); + + return serverSupportsRunbookRunParameters + ? Run(runbook, RunbookRunParameters.MapFrom(runbookRun)).FirstOrDefault() + : Client.Post(runbook.Link("CreateRunbookRun"), runbookRun); + } + + public RunbookRunResource[] Run(RunbookResource runbook, RunbookRunParameters runbookRunParameters) + { + var serverVersion = Repository.LoadRootDocument().Version; + var serverSupportsRunbookRunParameters = ServerSupportsRunbookRunParameters(serverVersion); + + if (serverSupportsRunbookRunParameters == false) + throw new UnsupportedApiVersionException($"This Octopus Deploy server is an older version ({serverVersion}) that does not yet support RunbookRunParameters. " + + "Please update your Octopus Deploy server to 2020.3.* or newer to access this feature."); + + return Client.Post(runbook.Link("CreateRunbookRun"), runbookRunParameters); } } } \ No newline at end of file