diff --git a/go.mod b/go.mod index a46e3a7b..e11b84b0 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/OctopusDeploy/go-octodiff v1.0.0 - github.com/OctopusDeploy/go-octopusdeploy/v2 v2.114.1 + github.com/OctopusDeploy/go-octopusdeploy/v2 v2.116.0 github.com/bmatcuk/doublestar/v4 v4.10.0 github.com/briandowns/spinner v1.23.2 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 15f5c58f..275e45a7 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4VDhOQ0Ven0= github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU= -github.com/OctopusDeploy/go-octopusdeploy/v2 v2.114.1 h1:7lrYQSCo2HeixFRBGGKA8a7QjuBw9L+4A4kmUDUEzSE= -github.com/OctopusDeploy/go-octopusdeploy/v2 v2.114.1/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus= +github.com/OctopusDeploy/go-octopusdeploy/v2 v2.116.0 h1:kW1H9qngKgI34OkfYN6/PFpjBEQRK/tZm70MCElHOME= +github.com/OctopusDeploy/go-octopusdeploy/v2 v2.116.0/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w= diff --git a/pkg/cmd/release/deploy/deploy.go b/pkg/cmd/release/deploy/deploy.go index 0df6d614..12c2a25d 100644 --- a/pkg/cmd/release/deploy/deploy.go +++ b/pkg/cmd/release/deploy/deploy.go @@ -70,6 +70,8 @@ const ( FlagAliasGuidedFailureMode = "guided-failure-mode" FlagAliasGuidedFailureModeLegacy = "guidedFailure" + FlagPriority = "priority" + FlagForcePackageDownload = "force-package-download" FlagAliasForcePackageDownloadLegacy = "forcePackageDownload" @@ -107,6 +109,7 @@ type DeployFlags struct { UpdateVariables *flag.Flag[bool] ExcludedSteps *flag.Flag[[]string] GuidedFailureMode *flag.Flag[string] // tri-state: true, false, or "use default". Can we model it with an optional bool? + Priority *flag.Flag[string] // tri-state: true, false, or "use default" ForcePackageDownload *flag.Flag[bool] DeploymentTargets *flag.Flag[[]string] ExcludeTargets *flag.Flag[[]string] @@ -127,6 +130,7 @@ func NewDeployFlags() *DeployFlags { UpdateVariables: flag.New[bool](FlagUpdateVariables, false), ExcludedSteps: flag.New[[]string](FlagSkip, false), GuidedFailureMode: flag.New[string](FlagGuidedFailure, false), + Priority: flag.New[string](FlagPriority, false), ForcePackageDownload: flag.New[bool](FlagForcePackageDownload, false), DeploymentTargets: flag.New[[]string](FlagDeploymentTarget, false), ExcludeTargets: flag.New[[]string](FlagExcludeDeploymentTarget, false), @@ -169,6 +173,7 @@ func NewCmdDeploy(f factory.Factory) *cobra.Command { flags.BoolVarP(&deployFlags.UpdateVariables.Value, deployFlags.UpdateVariables.Name, "", false, "Overwrite the release variable snapshot by re-importing variables from the project.") flags.StringArrayVarP(&deployFlags.ExcludedSteps.Value, deployFlags.ExcludedSteps.Name, "", nil, "Exclude specific steps from the deployment") flags.StringVarP(&deployFlags.GuidedFailureMode.Value, deployFlags.GuidedFailureMode.Name, "", "", "Enable Guided failure mode (true/false/default)") + flags.StringVarP(&deployFlags.Priority.Value, deployFlags.Priority.Name, "", "", "Jump the task queue ahead of other queued tasks (true/false/default). Requires the Priority Tasks feature, and the TaskPrioritize permission to set true.") flags.BoolVarP(&deployFlags.ForcePackageDownload.Value, deployFlags.ForcePackageDownload.Name, "", false, "Force re-download of packages") flags.StringArrayVarP(&deployFlags.DeploymentTargets.Value, deployFlags.DeploymentTargets.Name, "", nil, "Deploy to this target (can be specified multiple times)") flags.StringArrayVarP(&deployFlags.ExcludeTargets.Value, deployFlags.ExcludeTargets.Name, "", nil, "Deploy to targets except for this (can be specified multiple times)") @@ -203,6 +208,10 @@ func deployRun(cmd *cobra.Command, f factory.Factory, flags *DeployFlags) error outputFormat = constants.OutputFormatTable } + if _, err = executionscommon.ParsePriorityMode(flags.Priority.Value); err != nil { + return err + } + octopus, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) if err != nil { return err @@ -223,6 +232,7 @@ func deployRun(cmd *cobra.Command, f factory.Factory, flags *DeployFlags) error ScheduledExpiryTime: flags.MaxQueueTime.Value, ExcludedSteps: flags.ExcludedSteps.Value, GuidedFailureMode: flags.GuidedFailureMode.Value, + Priority: flags.Priority.Value, ForcePackageDownload: flags.ForcePackageDownload.Value, DeploymentTargets: flags.DeploymentTargets.Value, ExcludeTargets: flags.ExcludeTargets.Value, @@ -262,6 +272,7 @@ func deployRun(cmd *cobra.Command, f factory.Factory, flags *DeployFlags) error resolvedFlags.MaxQueueTime.Value = options.ScheduledExpiryTime resolvedFlags.ExcludedSteps.Value = options.ExcludedSteps resolvedFlags.GuidedFailureMode.Value = options.GuidedFailureMode + resolvedFlags.Priority.Value = options.Priority resolvedFlags.DeploymentTargets.Value = options.DeploymentTargets resolvedFlags.ExcludeTargets.Value = options.ExcludeTargets resolvedFlags.DeploymentFreezeNames.Value = options.DeploymentFreezeNames @@ -297,6 +308,7 @@ func deployRun(cmd *cobra.Command, f factory.Factory, flags *DeployFlags) error resolvedFlags.MaxQueueTime, resolvedFlags.ExcludedSteps, resolvedFlags.GuidedFailureMode, + resolvedFlags.Priority, resolvedFlags.ForcePackageDownload, resolvedFlags.DeploymentTargets, resolvedFlags.ExcludeTargets, @@ -1033,6 +1045,8 @@ func PrintAdvancedSummary(stdout io.Writer, options *executor.TaskOptionsDeployR gfmStr := executionscommon.LookupGuidedFailureModeString(options.GuidedFailureMode) + priorityStr := executionscommon.LookupPriorityString(options.Priority, "Use default setting from the lifecycle phase") + pkgDownloadStr := executionscommon.LookupPackageDownloadString(!options.ForcePackageDownload) depTargetsStr := "All included" @@ -1068,9 +1082,10 @@ func PrintAdvancedSummary(stdout io.Writer, options *executor.TaskOptionsDeployR Deploy Time: cyan(%s) Skipped Steps: cyan(%s) Guided Failure Mode: cyan(%s) + Priority: cyan(%s) Package Download: cyan(%s) Deployment Targets: cyan(%s) - `)), deployAtStr, skipStepsStr, gfmStr, pkgDownloadStr, depTargetsStr) + `)), deployAtStr, skipStepsStr, gfmStr, priorityStr, pkgDownloadStr, depTargetsStr) } func selectRelease(octopus *octopusApiClient.Client, ask question.Asker, questionText string, space *spaces.Space, project *projects.Project, channel *channels.Channel) (*releases.Release, error) { diff --git a/pkg/cmd/release/deploy/deploy_test.go b/pkg/cmd/release/deploy/deploy_test.go index fde01017..65a91514 100644 --- a/pkg/cmd/release/deploy/deploy_test.go +++ b/pkg/cmd/release/deploy/deploy_test.go @@ -1861,6 +1861,95 @@ func TestDeployCreate_AutomationMode(t *testing.T) { assert.Equal(t, "", stdErr.String()) }}, + {"release deploy with --priority false sends Priority Off", func(t *testing.T, api *testutil.MockHttpServer, rootCmd *cobra.Command, stdOut *bytes.Buffer, stdErr *bytes.Buffer) { + cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { + defer api.Close() + rootCmd.SetArgs([]string{"release", "deploy", "--project", fireProject.Name, "--version", "1.0", "--environment", "dev", "--priority", "false", "--output-format", "basic"}) + return rootCmd.ExecuteC() + }) + + api.ExpectRequest(t, "GET", "/api/").RespondWith(rootResource) + api.ExpectRequest(t, "GET", "/api/Spaces-1").RespondWith(rootResource) + api.ExpectRequest(t, "GET", "/api/Spaces-1/projects/"+fireProject.GetName()).RespondWith(fireProject) + + req := api.ExpectRequest(t, "POST", "/api/Spaces-1/deployments/create/untenanted/v1") + requestBody, err := testutil.ReadJson[deployments.CreateDeploymentUntenantedCommandV1](req.Request.Body) + assert.Nil(t, err) + + assert.Equal(t, deployments.CreateDeploymentUntenantedCommandV1{ + ReleaseVersion: "1.0", + EnvironmentNames: []string{"dev"}, + CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{ + SpaceID: "Spaces-1", + ProjectIDOrName: fireProject.Name, + Priority: "Off", + }, + }, requestBody) + + req.RespondWith(&deployments.CreateDeploymentResponseV1{ + DeploymentServerTasks: []*deployments.DeploymentServerTask{ + {DeploymentID: "Deployments-203", ServerTaskID: "ServerTasks-29394"}, + }, + }) + + _, err = testutil.ReceivePair(cmdReceiver) + assert.Nil(t, err) + + assert.Equal(t, "ServerTasks-29394\n", stdOut.String()) + assert.Equal(t, "", stdErr.String()) + }}, + + {"release deploy with --priority default leaves Priority unset", func(t *testing.T, api *testutil.MockHttpServer, rootCmd *cobra.Command, stdOut *bytes.Buffer, stdErr *bytes.Buffer) { + cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { + defer api.Close() + rootCmd.SetArgs([]string{"release", "deploy", "--project", fireProject.Name, "--version", "1.0", "--environment", "dev", "--priority", "default", "--output-format", "basic"}) + return rootCmd.ExecuteC() + }) + + api.ExpectRequest(t, "GET", "/api/").RespondWith(rootResource) + api.ExpectRequest(t, "GET", "/api/Spaces-1").RespondWith(rootResource) + api.ExpectRequest(t, "GET", "/api/Spaces-1/projects/"+fireProject.GetName()).RespondWith(fireProject) + + req := api.ExpectRequest(t, "POST", "/api/Spaces-1/deployments/create/untenanted/v1") + requestBody, err := testutil.ReadJson[deployments.CreateDeploymentUntenantedCommandV1](req.Request.Body) + assert.Nil(t, err) + + assert.Equal(t, deployments.CreateDeploymentUntenantedCommandV1{ + ReleaseVersion: "1.0", + EnvironmentNames: []string{"dev"}, + CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{ + SpaceID: "Spaces-1", + ProjectIDOrName: fireProject.Name, + }, + }, requestBody) + + req.RespondWith(&deployments.CreateDeploymentResponseV1{ + DeploymentServerTasks: []*deployments.DeploymentServerTask{ + {DeploymentID: "Deployments-203", ServerTaskID: "ServerTasks-29394"}, + }, + }) + + _, err = testutil.ReceivePair(cmdReceiver) + assert.Nil(t, err) + + assert.Equal(t, "ServerTasks-29394\n", stdOut.String()) + assert.Equal(t, "", stdErr.String()) + }}, + + {"release deploy rejects an unrecognised --priority value before contacting the server", func(t *testing.T, api *testutil.MockHttpServer, rootCmd *cobra.Command, stdOut *bytes.Buffer, stdErr *bytes.Buffer) { + cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { + defer api.Close() + rootCmd.SetArgs([]string{"release", "deploy", "--project", fireProject.Name, "--version", "1.0", "--environment", "dev", "--priority", "urgent"}) + return rootCmd.ExecuteC() + }) + + _, err := testutil.ReceivePair(cmdReceiver) + assert.EqualError(t, err, "'urgent' is not a valid value for priority, expected true, false or default") + + assert.Equal(t, "", stdOut.String()) + assert.Equal(t, "", stdErr.String()) + }}, + {"release deploy specifying all the args; untentanted", func(t *testing.T, api *testutil.MockHttpServer, rootCmd *cobra.Command, stdOut *bytes.Buffer, stdErr *bytes.Buffer) { cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { defer api.Close() @@ -1873,6 +1962,7 @@ func TestDeployCreate_AutomationMode(t *testing.T) { "--deploy-at-expiry", "2022-09-10 13:37:03 +10:00", "--skip", "Install", "--skip", "Cleanup", "--guided-failure", "true", + "--priority", "true", "--force-package-download", "--update-variables", "--target", "firstMachine", "--target", "secondMachine", @@ -1908,6 +1998,7 @@ func TestDeployCreate_AutomationMode(t *testing.T) { ExcludedMachineNames: []string{"thirdMachine"}, SkipStepNames: []string{"Install", "Cleanup"}, UseGuidedFailure: &trueVal, + Priority: "On", RunAt: "2022-09-10 13:32:03 +10:00", NoRunAfter: "2022-09-10 13:37:03 +10:00", Variables: map[string]string{ @@ -1947,6 +2038,7 @@ func TestDeployCreate_AutomationMode(t *testing.T) { "--tenant", "Coke", "--tenant", "Pepsi", "--tenant-tag", "Region/us-east", "--guided-failure", "true", + "--priority", "true", "--force-package-download", "--update-variables", "--target", "firstMachine", "--target", "secondMachine", @@ -1983,6 +2075,7 @@ func TestDeployCreate_AutomationMode(t *testing.T) { ExcludedMachineNames: []string{"thirdMachine"}, SkipStepNames: []string{"Install", "Cleanup"}, UseGuidedFailure: &trueVal, + Priority: "On", RunAt: "2022-09-10 13:32:03 +10:00", NoRunAfter: "2022-09-10 13:37:03 +10:00", Variables: map[string]string{ @@ -2083,7 +2176,7 @@ func TestDeployCreate_GenerationOfAutomationCommand_MasksSensitiveVariables(t *t // need to very it's wired up properly receiver := testutil.GoBegin2(func() (*cobra.Command, error) { defer testutil.Close(api, qa) - rootCmd.SetArgs([]string{"release", "deploy", "--project", "fire project", "--version", "2.0", "--environment", "dev"}) + rootCmd.SetArgs([]string{"release", "deploy", "--project", "fire project", "--version", "2.0", "--environment", "dev", "--priority", "true"}) return rootCmd.ExecuteC() }) @@ -2173,6 +2266,7 @@ func TestDeployCreate_GenerationOfAutomationCommand_MasksSensitiveVariables(t *t CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{ SpaceID: "Spaces-1", ProjectIDOrName: fireProject.Name, + Priority: "On", Variables: map[string]string{ "Boring Variable": "BORING", "Nuclear Launch Codes": "9001", @@ -2199,10 +2293,11 @@ func TestDeployCreate_GenerationOfAutomationCommand_MasksSensitiveVariables(t *t Deploy Time: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Jump the task queue Package Download: Use cached packages (if available) Deployment Targets: All included - Automation Command: octopus release deploy --space 'Default Space' --project 'Fire Project' --version '2.0' --environment 'dev' --variable 'Boring Variable:BORING' --variable 'Nuclear Launch Codes:*****' --variable 'Secret Password:*****' --no-prompt + Automation Command: octopus release deploy --space 'Default Space' --project 'Fire Project' --version '2.0' --environment 'dev' --priority 'true' --variable 'Boring Variable:BORING' --variable 'Nuclear Launch Codes:*****' --variable 'Secret Password:*****' --no-prompt Warning: Command includes some sensitive variable values which have been replaced with placeholders. Successfully started 2 deployment(s) @@ -2225,6 +2320,7 @@ func TestDeployCreate_PrintAdvancedSummary(t *testing.T) { Deploy Time: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Use default setting from the lifecycle phase Package Download: Use cached packages (if available) Deployment Targets: All included `), stdout.String()) @@ -2234,6 +2330,7 @@ func TestDeployCreate_PrintAdvancedSummary(t *testing.T) { options := &executor.TaskOptionsDeployRelease{ ScheduledStartTime: "2022-09-23", GuidedFailureMode: "false", + Priority: "true", ForcePackageDownload: true, ExcludedSteps: []string{"Step 1", "Step 37"}, DeploymentTargets: []string{"vm-1", "vm-2"}, @@ -2246,6 +2343,7 @@ func TestDeployCreate_PrintAdvancedSummary(t *testing.T) { Deploy Time: 2022-09-23 Skipped Steps: Step 1,Step 37 Guided Failure Mode: Do not use guided failure mode + Priority: Jump the task queue Package Download: Re-download packages from feed Deployment Targets: Include vm-1,vm-2; Exclude vm-3,vm-4 `), stdout.String()) @@ -2262,6 +2360,7 @@ func TestDeployCreate_PrintAdvancedSummary(t *testing.T) { Deploy Time: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Use default setting from the lifecycle phase Package Download: Use cached packages (if available) Deployment Targets: Include vm-2 `), stdout.String()) @@ -2278,6 +2377,7 @@ func TestDeployCreate_PrintAdvancedSummary(t *testing.T) { Deploy Time: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Use default setting from the lifecycle phase Package Download: Use cached packages (if available) Deployment Targets: Exclude vm-4 `), stdout.String()) diff --git a/pkg/cmd/runbook/run/run.go b/pkg/cmd/runbook/run/run.go index ad57eb89..c1ac4afb 100644 --- a/pkg/cmd/runbook/run/run.go +++ b/pkg/cmd/runbook/run/run.go @@ -74,6 +74,8 @@ const ( FlagAliasGuidedFailureMode = "guided-failure-mode" FlagAliasGuidedFailureModeLegacy = "guidedFailure" + FlagPriority = "priority" + FlagForcePackageDownload = "force-package-download" FlagAliasForcePackageDownloadLegacy = "forcePackageDownload" @@ -106,6 +108,7 @@ type RunFlags struct { Snapshot *flag.Flag[string] ExcludedSteps *flag.Flag[[]string] GuidedFailureMode *flag.Flag[string] // tri-state: true, false, or "use default". Can we model it with an optional bool? + Priority *flag.Flag[string] // tri-state: true, false, or "use default" ForcePackageDownload *flag.Flag[bool] RunTargets *flag.Flag[[]string] ExcludeTargets *flag.Flag[[]string] @@ -129,6 +132,7 @@ func NewRunFlags() *RunFlags { Snapshot: flag.New[string](FlagSnapshot, false), ExcludedSteps: flag.New[[]string](FlagSkip, false), GuidedFailureMode: flag.New[string](FlagGuidedFailure, false), + Priority: flag.New[string](FlagPriority, false), ForcePackageDownload: flag.New[bool](FlagForcePackageDownload, false), RunTargets: flag.New[[]string](FlagRunTarget, false), ExcludeTargets: flag.New[[]string](FlagExcludeRunTarget, false), @@ -171,6 +175,7 @@ func NewCmdRun(f factory.Factory) *cobra.Command { flags.StringVarP(&runFlags.Snapshot.Value, runFlags.Snapshot.Name, "", "", "Name or ID of the snapshot to run. If not supplied, the command will attempt to use the published snapshot.") flags.StringArrayVarP(&runFlags.ExcludedSteps.Value, runFlags.ExcludedSteps.Name, "", nil, "Exclude specific steps from the runbook") flags.StringVarP(&runFlags.GuidedFailureMode.Value, runFlags.GuidedFailureMode.Name, "", "", "Enable Guided failure mode (true/false/default)") + flags.StringVarP(&runFlags.Priority.Value, runFlags.Priority.Name, "", "", "Jump the task queue ahead of other queued tasks (true/false/default). Requires the Priority Tasks feature. For runbook runs, 'default' is the same as 'false'.") flags.BoolVarP(&runFlags.ForcePackageDownload.Value, runFlags.ForcePackageDownload.Name, "", false, "Force re-download of packages") flags.StringArrayVarP(&runFlags.RunTargets.Value, runFlags.RunTargets.Name, "", nil, "Run on this target (can be specified multiple times)") flags.StringArrayVarP(&runFlags.ExcludeTargets.Value, runFlags.ExcludeTargets.Name, "", nil, "Run on targets except for this (can be specified multiple times)") @@ -210,6 +215,10 @@ func runbookRun(cmd *cobra.Command, f factory.Factory, flags *RunFlags) error { outputFormat = constants.OutputFormatTable } + if _, err = executionscommon.ParsePriorityMode(flags.Priority.Value); err != nil { + return err + } + octopus, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) if err != nil { return err @@ -289,6 +298,7 @@ func runDbRunbook(cmd *cobra.Command, f factory.Factory, flags *RunFlags, octopu ScheduledExpiryTime: flags.MaxQueueTime.Value, ExcludedSteps: flags.ExcludedSteps.Value, GuidedFailureMode: flags.GuidedFailureMode.Value, + Priority: flags.Priority.Value, ForcePackageDownload: flags.ForcePackageDownload.Value, RunTargets: flags.RunTargets.Value, ExcludeTargets: flags.ExcludeTargets.Value, @@ -330,6 +340,7 @@ func runDbRunbook(cmd *cobra.Command, f factory.Factory, flags *RunFlags, octopu resolvedFlags.MaxQueueTime.Value = options.ScheduledExpiryTime resolvedFlags.ExcludedSteps.Value = options.ExcludedSteps resolvedFlags.GuidedFailureMode.Value = options.GuidedFailureMode + resolvedFlags.Priority.Value = options.Priority resolvedFlags.RunTargets.Value = options.RunTargets resolvedFlags.ExcludeTargets.Value = options.ExcludeTargets @@ -364,6 +375,7 @@ func runDbRunbook(cmd *cobra.Command, f factory.Factory, flags *RunFlags, octopu resolvedFlags.MaxQueueTime, resolvedFlags.ExcludedSteps, resolvedFlags.GuidedFailureMode, + resolvedFlags.Priority, resolvedFlags.ForcePackageDownload, resolvedFlags.RunTargets, resolvedFlags.ExcludeTargets, @@ -420,6 +432,7 @@ func runGitRunbook(cmd *cobra.Command, f factory.Factory, flags *RunFlags, octop ScheduledExpiryTime: flags.MaxQueueTime.Value, ExcludedSteps: flags.ExcludedSteps.Value, GuidedFailureMode: flags.GuidedFailureMode.Value, + Priority: flags.Priority.Value, ForcePackageDownload: flags.ForcePackageDownload.Value, RunTargets: flags.RunTargets.Value, ExcludeTargets: flags.ExcludeTargets.Value, @@ -464,6 +477,7 @@ func runGitRunbook(cmd *cobra.Command, f factory.Factory, flags *RunFlags, octop resolvedFlags.MaxQueueTime.Value = options.ScheduledExpiryTime resolvedFlags.ExcludedSteps.Value = options.ExcludedSteps resolvedFlags.GuidedFailureMode.Value = options.GuidedFailureMode + resolvedFlags.Priority.Value = options.Priority resolvedFlags.RunTargets.Value = options.RunTargets resolvedFlags.ExcludeTargets.Value = options.ExcludeTargets resolvedFlags.GitRef.Value = options.GitReference @@ -502,6 +516,7 @@ func runGitRunbook(cmd *cobra.Command, f factory.Factory, flags *RunFlags, octop resolvedFlags.MaxQueueTime, resolvedFlags.ExcludedSteps, resolvedFlags.GuidedFailureMode, + resolvedFlags.Priority, resolvedFlags.ForcePackageDownload, resolvedFlags.RunTargets, resolvedFlags.ExcludeTargets, @@ -1247,6 +1262,8 @@ func PrintAdvancedSummary(stdout io.Writer, options *executor.TaskOptionsRunbook gfmStr := executionscommon.LookupGuidedFailureModeString(options.GuidedFailureMode) + priorityStr := executionscommon.LookupPriorityString(options.Priority, "Do not jump the task queue") + pkgDownloadStr := executionscommon.LookupPackageDownloadString(!options.ForcePackageDownload) runTargetsStr := "All included" @@ -1282,9 +1299,10 @@ func PrintAdvancedSummary(stdout io.Writer, options *executor.TaskOptionsRunbook Run At: cyan(%s) Skipped Steps: cyan(%s) Guided Failure Mode: cyan(%s) + Priority: cyan(%s) Package Download: cyan(%s) Run Targets: cyan(%s) - `)), runAtStr, skipStepsStr, gfmStr, pkgDownloadStr, runTargetsStr) + `)), runAtStr, skipStepsStr, gfmStr, priorityStr, pkgDownloadStr, runTargetsStr) } func selectRunbook(octopus *octopusApiClient.Client, ask question.Asker, questionText string, space *spaces.Space, project *projects.Project) (*runbooks.Runbook, error) { diff --git a/pkg/cmd/runbook/run/run_by_tag.go b/pkg/cmd/runbook/run/run_by_tag.go index f72a030f..d281bff5 100644 --- a/pkg/cmd/runbook/run/run_by_tag.go +++ b/pkg/cmd/runbook/run/run_by_tag.go @@ -364,6 +364,7 @@ func runRunbooksByTag(cmd *cobra.Command, f factory.Factory, flags *RunFlags, oc ScheduledExpiryTime: flags.MaxQueueTime.Value, ExcludedSteps: flags.ExcludedSteps.Value, GuidedFailureMode: flags.GuidedFailureMode.Value, + Priority: flags.Priority.Value, ForcePackageDownload: flags.ForcePackageDownload.Value, RunTargets: flags.RunTargets.Value, ExcludeTargets: flags.ExcludeTargets.Value, diff --git a/pkg/cmd/runbook/run/run_test.go b/pkg/cmd/runbook/run/run_test.go index 33c1904d..4febc4e2 100644 --- a/pkg/cmd/runbook/run/run_test.go +++ b/pkg/cmd/runbook/run/run_test.go @@ -287,6 +287,7 @@ func TestRunbookRun_AutomationMode(t *testing.T) { "--skip", "Install", "--skip", "Cleanup", "--snapshot", "Snapshot FWKMLUX", "--guided-failure", "true", + "--priority", "true", "--force-package-download", "--target", "firstMachine", "--target", "secondMachine", "--exclude-target", "thirdMachine", @@ -317,6 +318,7 @@ func TestRunbookRun_AutomationMode(t *testing.T) { ExcludedMachineNames: []string{"thirdMachine"}, SkipStepNames: []string{"Install", "Cleanup"}, UseGuidedFailure: &trueVar, + Priority: "On", RunAt: "2022-09-10 13:32:03 +10:00", NoRunAfter: "2022-09-10 13:37:03 +10:00", Variables: map[string]string{ @@ -636,6 +638,7 @@ func TestGitRunbookRun_AutomationMode(t *testing.T) { "--run-at-expiry", "2022-09-10 13:37:03 +10:00", "--skip", "Install", "--skip", "Cleanup", "--guided-failure", "true", + "--priority", "true", "--force-package-download", "--target", "firstMachine", "--target", "secondMachine", "--exclude-target", "thirdMachine", @@ -669,6 +672,7 @@ func TestGitRunbookRun_AutomationMode(t *testing.T) { ExcludedMachineNames: []string{"thirdMachine"}, SkipStepNames: []string{"Install", "Cleanup"}, UseGuidedFailure: &trueVar, + Priority: "On", RunAt: "2022-09-10 13:32:03 +10:00", NoRunAfter: "2022-09-10 13:37:03 +10:00", Variables: map[string]string{ @@ -728,6 +732,7 @@ func TestRunbookRun_PrintAdvancedSummary(t *testing.T) { Run At: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Do not jump the task queue Package Download: Use cached packages (if available) Run Targets: All included `), stdout.String()) @@ -738,6 +743,7 @@ func TestRunbookRun_PrintAdvancedSummary(t *testing.T) { TaskOptionsRunbookRunBase: executor.TaskOptionsRunbookRunBase{ ScheduledStartTime: "2022-09-23", GuidedFailureMode: "false", + Priority: "true", ForcePackageDownload: true, ExcludedSteps: []string{"Step 1", "Step 37"}, RunTargets: []string{"vm-1", "vm-2"}, @@ -751,6 +757,7 @@ func TestRunbookRun_PrintAdvancedSummary(t *testing.T) { Run At: 2022-09-23 Skipped Steps: Step 1,Step 37 Guided Failure Mode: Do not use guided failure mode + Priority: Jump the task queue Package Download: Re-download packages from feed Run Targets: Include vm-1,vm-2; Exclude vm-3,vm-4 `), stdout.String()) @@ -769,6 +776,7 @@ func TestRunbookRun_PrintAdvancedSummary(t *testing.T) { Run At: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Do not jump the task queue Package Download: Use cached packages (if available) Run Targets: Include vm-2 `), stdout.String()) @@ -787,6 +795,7 @@ func TestRunbookRun_PrintAdvancedSummary(t *testing.T) { Run At: Now Skipped Steps: None Guided Failure Mode: Use default setting from the target environment + Priority: Do not jump the task queue Package Download: Use cached packages (if available) Run Targets: Exclude vm-4 `), stdout.String()) diff --git a/pkg/executionscommon/executionscommon.go b/pkg/executionscommon/executionscommon.go index 4348d7bd..91af36fa 100644 --- a/pkg/executionscommon/executionscommon.go +++ b/pkg/executionscommon/executionscommon.go @@ -338,6 +338,37 @@ func LookupGuidedFailureModeString(value string) string { } } +// ParsePriorityMode maps the CLI's tri-state priority value onto the server's PriorityMode. +// An empty result is omitted from the request, leaving the server to apply its own default. +func ParsePriorityMode(value string) (string, error) { + b, err := strconv.ParseBool(value) + if err == nil { + if b { + return "On", nil + } + return "Off", nil + } + if value == "" || strings.EqualFold("default", value) { + return "", nil + } + return "", fmt.Errorf("'%s' is not a valid value for priority, expected true, false or default", value) +} + +func LookupPriorityString(value string, defaultDescription string) string { + priority, err := ParsePriorityMode(value) + if err != nil { + return fmt.Sprintf("Unknown %s", value) + } + switch priority { + case "On": + return "Jump the task queue" + case "Off": + return "Do not jump the task queue" + default: + return defaultDescription + } +} + func LookupPackageDownloadString(value bool) string { if value { return "Use cached packages (if available)" diff --git a/pkg/executor/release.go b/pkg/executor/release.go index 7e57a9c1..b3390ad7 100644 --- a/pkg/executor/release.go +++ b/pkg/executor/release.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/OctopusDeploy/cli/pkg/executionscommon" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deployments" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/releases" @@ -101,6 +102,7 @@ type TaskOptionsDeployRelease struct { ScheduledExpiryTime string ExcludedSteps []string GuidedFailureMode string // ["", "true", "false", "default"]. Note default and "" are the same, the only difference is whether interactive mode prompts you + Priority string // ["", "true", "false", "default"]. "" and "default" both defer to the lifecycle's priority setting ForcePackageDownload bool DeploymentTargets []string ExcludeTargets []string @@ -171,6 +173,11 @@ func releaseDeploy(octopus *client.Client, space *spaces.Space, input any) error } } + abstractCmd.Priority, err = executionscommon.ParsePriorityMode(params.Priority) + if err != nil { + return err + } + // If either tenants or tenantTags are specified then it must be a tenanted deployment. // Otherwise it must be untenanted. // If the server has a tenanted deployment and both TenantNames+Tags are empty, the request fails, diff --git a/pkg/executor/runbook.go b/pkg/executor/runbook.go index e6ca7038..35244f1a 100644 --- a/pkg/executor/runbook.go +++ b/pkg/executor/runbook.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/OctopusDeploy/cli/pkg/executionscommon" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deployments" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks" @@ -31,6 +32,7 @@ type TaskOptionsRunbookRunBase struct { ScheduledExpiryTime string ExcludedSteps []string GuidedFailureMode string // ["", "true", "false", "default"]. Note default and "" are the same, the only difference is whether interactive mode prompts you + Priority string // ["", "true", "false", "default"]. "" and "default" both leave the server to decide ForcePackageDownload bool RunTargets []string ExcludeTargets []string @@ -95,6 +97,12 @@ func runbookRun(octopus *client.Client, space *spaces.Space, input any) error { return fmt.Errorf("'%s' is not a valid value for guided failure mode", params.GuidedFailureMode) } } + + abstractCmd.Priority, err = executionscommon.ParsePriorityMode(params.Priority) + if err != nil { + return err + } + runCommand := runbooks.NewRunbookRunCommandV1(space.ID, params.ProjectName) runCommand.RunbookName = params.RunbookName runCommand.EnvironmentNames = params.Environments @@ -169,6 +177,12 @@ func gitRunbookRun(octopus *client.Client, space *spaces.Space, input any) error return fmt.Errorf("'%s' is not a valid value for guided failure mode", params.GuidedFailureMode) } } + + abstractCmd.Priority, err = executionscommon.ParsePriorityMode(params.Priority) + if err != nil { + return err + } + runCommand := runbooks.NewGitRunbookRunCommandV1(space.ID, params.ProjectName) runCommand.RunbookName = params.RunbookName runCommand.EnvironmentNames = params.Environments