Skip to content
Merged
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
17 changes: 16 additions & 1 deletion pkg/cmd/release/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const (
FlagAliasGuidedFailureMode = "guided-failure-mode"
FlagAliasGuidedFailureModeLegacy = "guidedFailure"

FlagPriority = "priority"

FlagForcePackageDownload = "force-package-download"
FlagAliasForcePackageDownloadLegacy = "forcePackageDownload"

Expand Down Expand Up @@ -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]
Expand All @@ -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),
Expand Down Expand Up @@ -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)")
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
104 changes: 102 additions & 2 deletions pkg/cmd/release/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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",
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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()
})

Expand Down Expand Up @@ -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",
Expand All @@ -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)

Expand All @@ -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())
Expand All @@ -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"},
Expand All @@ -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())
Expand All @@ -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())
Expand All @@ -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())
Expand Down
Loading