From d07173f80e141346575a024a9de71764da901a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Thu, 25 Jul 2024 17:08:39 -0500 Subject: [PATCH 01/13] Allow the user to set max-in-flight while pushing app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- actor/v7pushaction/actor.go | 2 +- actor/v7pushaction/actor_test.go | 2 +- .../create_deployment_for_push_plan.go | 13 ++++-- .../create_deployment_for_push_plan_test.go | 46 +++++++++++++++++++ actor/v7pushaction/push_plan.go | 2 + ...up_deployment_information_for_push_plan.go | 13 ++++++ ...loyment_information_for_push_plan_test.go} | 18 +++++++- ...setup_deployment_strategy_for_push_plan.go | 7 --- command/v7/push_command.go | 7 +++ command/v7/push_command_test.go | 20 ++++++++ integration/v7/push/help_test.go | 1 + integration/v7/push/rolling_push_test.go | 24 ++++++++++ resources/deployment_resource.go | 16 +++++++ 13 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 actor/v7pushaction/setup_deployment_information_for_push_plan.go rename actor/v7pushaction/{setup_deployment_strategy_for_push_plan_test.go => setup_deployment_information_for_push_plan_test.go} (64%) delete mode 100644 actor/v7pushaction/setup_deployment_strategy_for_push_plan.go diff --git a/actor/v7pushaction/actor.go b/actor/v7pushaction/actor.go index f2c74ef9cea..829b64e2a19 100644 --- a/actor/v7pushaction/actor.go +++ b/actor/v7pushaction/actor.go @@ -71,7 +71,7 @@ func NewActor(v3Actor V7Actor, sharedActor SharedActor) *Actor { SetDefaultBitsPathForPushPlan, SetupDropletPathForPushPlan, actor.SetupAllResourcesForPushPlan, - SetupDeploymentStrategyForPushPlan, + SetupDeploymentInformationForPushPlan, SetupNoStartForPushPlan, SetupNoWaitForPushPlan, SetupTaskAppForPushPlan, diff --git a/actor/v7pushaction/actor_test.go b/actor/v7pushaction/actor_test.go index d3ebc799729..32f314564a4 100644 --- a/actor/v7pushaction/actor_test.go +++ b/actor/v7pushaction/actor_test.go @@ -22,7 +22,7 @@ var _ = Describe("Actor", func() { SetDefaultBitsPathForPushPlan, SetupDropletPathForPushPlan, actor.SetupAllResourcesForPushPlan, - SetupDeploymentStrategyForPushPlan, + SetupDeploymentInformationForPushPlan, SetupNoStartForPushPlan, SetupNoWaitForPushPlan, SetupTaskAppForPushPlan, diff --git a/actor/v7pushaction/create_deployment_for_push_plan.go b/actor/v7pushaction/create_deployment_for_push_plan.go index 241568f8141..1f6e04454b7 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan.go +++ b/actor/v7pushaction/create_deployment_for_push_plan.go @@ -8,10 +8,15 @@ import ( func (actor Actor) CreateDeploymentForApplication(pushPlan PushPlan, eventStream chan<- *PushEvent, progressBar ProgressBar) (PushPlan, Warnings, error) { eventStream <- &PushEvent{Plan: pushPlan, Event: StartingDeployment} - var dep resources.Deployment - dep.DropletGUID = pushPlan.DropletGUID - dep.Strategy = pushPlan.Strategy - dep.Relationships = resources.Relationships{constant.RelationshipTypeApplication: resources.Relationship{GUID: pushPlan.Application.GUID}} + dep := resources.Deployment{ + Strategy: pushPlan.Strategy, + Options: resources.DeploymentOpts{ + MaxInFlight: pushPlan.MaxInFlight, + }, + DropletGUID: pushPlan.DropletGUID, + Relationships: resources.Relationships{constant.RelationshipTypeApplication: resources.Relationship{GUID: pushPlan.Application.GUID}}, + } + deploymentGUID, warnings, err := actor.V7Actor.CreateDeployment(dep) if err != nil { diff --git a/actor/v7pushaction/create_deployment_for_push_plan_test.go b/actor/v7pushaction/create_deployment_for_push_plan_test.go index 9bbcfa30578..46031d80dc8 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan_test.go +++ b/actor/v7pushaction/create_deployment_for_push_plan_test.go @@ -6,6 +6,7 @@ import ( "code.cloudfoundry.org/cli/actor/v7action" . "code.cloudfoundry.org/cli/actor/v7pushaction" "code.cloudfoundry.org/cli/actor/v7pushaction/v7pushactionfakes" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/resources" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -106,6 +107,51 @@ var _ = Describe("CreateDeploymentForApplication()", func() { Expect(events).To(ConsistOf(StartingDeployment)) }) }) + + When("strategy is provided", func() { + BeforeEach(func() { + fakeV7Actor.PollStartForDeploymentCalls(func(_ resources.Application, _ string, _ bool, handleInstanceDetails func(string)) (warnings v7action.Warnings, err error) { + handleInstanceDetails("Instances starting...") + return nil, nil + }) + + fakeV7Actor.CreateDeploymentReturns( + "some-deployment-guid", + v7action.Warnings{"some-deployment-warning"}, + nil, + ) + paramPlan.Strategy = "rolling" + paramPlan.MaxInFlight = 10 + }) + + It("waits for the app to start", func() { + Expect(fakeV7Actor.PollStartForDeploymentCallCount()).To(Equal(1)) + givenApp, givenDeploymentGUID, noWait, _ := fakeV7Actor.PollStartForDeploymentArgsForCall(0) + Expect(givenApp).To(Equal(resources.Application{GUID: "some-app-guid"})) + Expect(givenDeploymentGUID).To(Equal("some-deployment-guid")) + Expect(noWait).To(Equal(false)) + Expect(events).To(ConsistOf(StartingDeployment, InstanceDetails, WaitingForDeployment)) + Expect(fakeV7Actor.CreateDeploymentCallCount()).To(Equal(1)) + dep := fakeV7Actor.CreateDeploymentArgsForCall(0) + Expect(dep).To(Equal(resources.Deployment{ + Strategy: "rolling", + Options: resources.DeploymentOpts{MaxInFlight: 10}, + Relationships: resources.Relationships{ + constant.RelationshipTypeApplication: resources.Relationship{GUID: "some-app-guid"}, + }, + })) + }) + + It("returns errors and warnings", func() { + Expect(returnedPushPlan).To(Equal(paramPlan)) + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("some-deployment-warning")) + }) + + It("records deployment events", func() { + Expect(events).To(ConsistOf(StartingDeployment, InstanceDetails, WaitingForDeployment)) + }) + }) }) Describe("waiting for app to start", func() { diff --git a/actor/v7pushaction/push_plan.go b/actor/v7pushaction/push_plan.go index eacb52d577a..9460509d065 100644 --- a/actor/v7pushaction/push_plan.go +++ b/actor/v7pushaction/push_plan.go @@ -20,6 +20,7 @@ type PushPlan struct { NoStart bool NoWait bool Strategy constant.DeploymentStrategy + MaxInFlight int TaskTypeApplication bool DockerImageCredentials v7action.DockerImageCredentials @@ -47,6 +48,7 @@ type FlagOverrides struct { HealthCheckType constant.HealthCheckType Instances types.NullInt Memory string + MaxInFlight int NoStart bool NoWait bool ProvidedAppPath string diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan.go b/actor/v7pushaction/setup_deployment_information_for_push_plan.go new file mode 100644 index 00000000000..481fcb9a82d --- /dev/null +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan.go @@ -0,0 +1,13 @@ +package v7pushaction + +import "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + +func SetupDeploymentInformationForPushPlan(pushPlan PushPlan, overrides FlagOverrides) (PushPlan, error) { + pushPlan.Strategy = overrides.Strategy + + if overrides.Strategy != constant.DeploymentStrategyDefault { + pushPlan.MaxInFlight = overrides.MaxInFlight + } + + return pushPlan, nil +} diff --git a/actor/v7pushaction/setup_deployment_strategy_for_push_plan_test.go b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go similarity index 64% rename from actor/v7pushaction/setup_deployment_strategy_for_push_plan_test.go rename to actor/v7pushaction/setup_deployment_information_for_push_plan_test.go index 5d0d8df0aba..82aa75edfb7 100644 --- a/actor/v7pushaction/setup_deployment_strategy_for_push_plan_test.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("SetupDeploymentStrategyForPushPlan", func() { +var _ = Describe("SetupDeploymentInformationForPushPlan", func() { var ( pushPlan PushPlan overrides FlagOverrides @@ -24,24 +24,38 @@ var _ = Describe("SetupDeploymentStrategyForPushPlan", func() { }) JustBeforeEach(func() { - expectedPushPlan, executeErr = SetupDeploymentStrategyForPushPlan(pushPlan, overrides) + expectedPushPlan, executeErr = SetupDeploymentInformationForPushPlan(pushPlan, overrides) }) When("flag overrides specifies strategy", func() { BeforeEach(func() { overrides.Strategy = "rolling" + overrides.MaxInFlight = 5 }) It("sets the strategy on the push plan", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(expectedPushPlan.Strategy).To(Equal(constant.DeploymentStrategyRolling)) }) + + It("sets the max in flight on the push plan", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MaxInFlight).To(Equal(5)) + }) }) When("flag overrides does not specify strategy", func() { + BeforeEach(func() { + overrides.MaxInFlight = 10 + }) It("leaves the strategy as its default value on the push plan", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(expectedPushPlan.Strategy).To(Equal(constant.DeploymentStrategyDefault)) }) + + It("does not set MaxInFlight", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MaxInFlight).To(Equal(0)) + }) }) }) diff --git a/actor/v7pushaction/setup_deployment_strategy_for_push_plan.go b/actor/v7pushaction/setup_deployment_strategy_for_push_plan.go deleted file mode 100644 index 0e6c2526342..00000000000 --- a/actor/v7pushaction/setup_deployment_strategy_for_push_plan.go +++ /dev/null @@ -1,7 +0,0 @@ -package v7pushaction - -func SetupDeploymentStrategyForPushPlan(pushPlan PushPlan, overrides FlagOverrides) (PushPlan, error) { - pushPlan.Strategy = overrides.Strategy - - return pushPlan, nil -} diff --git a/command/v7/push_command.go b/command/v7/push_command.go index 12e0001b15e..e66e7a6c269 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -91,6 +91,7 @@ type PushCommand struct { LogRateLimit string `long:"log-rate-limit" short:"l" description:"Log rate limit per second, in bytes (e.g. 128B, 4K, 1M). -l=-1 represents unlimited"` PathToManifest flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` Memory string `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` + MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` NoRoute bool `long:"no-route" description:"Do not map a route to this app"` NoStart bool `long:"no-start" description:"Do not stage and start the app after pushing"` @@ -347,6 +348,7 @@ func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) { HealthCheckType: cmd.HealthCheckType.Type, HealthCheckTimeout: cmd.HealthCheckTimeout.Value, Instances: cmd.Instances.NullInt, + MaxInFlight: cmd.MaxInFlight, Memory: cmd.Memory, NoStart: cmd.NoStart, NoWait: cmd.NoWait, @@ -485,6 +487,11 @@ func (cmd PushCommand) ValidateFlags() error { } case !cmd.validBuildpacks(): return translatableerror.InvalidBuildpacksError{} + + case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: + return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1: + return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } return nil diff --git a/command/v7/push_command_test.go b/command/v7/push_command_test.go index 00a464644cc..b038f82d1ac 100644 --- a/command/v7/push_command_test.go +++ b/command/v7/push_command_test.go @@ -1072,6 +1072,7 @@ var _ = Describe("push Command", func() { cmd.RandomRoute = false cmd.NoStart = true cmd.NoWait = true + cmd.MaxInFlight = 1 cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} cmd.Instances = flag.Instances{NullInt: types.NullInt{Value: 10, IsSet: true}} cmd.PathToManifest = "/manifest/path" @@ -1108,6 +1109,7 @@ var _ = Describe("push Command", func() { Expect(overrides.Vars).To(Equal([]template.VarKV{{Name: "key", Value: "val"}})) Expect(overrides.Task).To(BeTrue()) Expect(overrides.LogRateLimit).To(Equal("512M")) + Expect(overrides.MaxInFlight).To(Equal(1)) }) When("a docker image is provided", func() { @@ -1290,5 +1292,23 @@ var _ = Describe("push Command", func() { "--task", "--strategy=canary", }, }), + + Entry("max-in-flight is passed without strategy", + func() { + cmd.MaxInFlight = 10 + }, + translatableerror.RequiredFlagsError{ + Arg1: "--max-in-flight", + Arg2: "--strategy", + }), + + Entry("max-in-flight is smaller than 1", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.MaxInFlight = 0 + }, + translatableerror.IncorrectUsageError{ + Message: "--max-in-flight must be greater than or equal to 1", + }), ) }) diff --git a/integration/v7/push/help_test.go b/integration/v7/push/help_test.go index c86d0b9999b..c484576e81f 100644 --- a/integration/v7/push/help_test.go +++ b/integration/v7/push/help_test.go @@ -91,6 +91,7 @@ var _ = Describe("help", func() { Eventually(session).Should(Say(`--stack, -s`)) Eventually(session).Should(Say(`--start-command, -c`)) Eventually(session).Should(Say(`--strategy`)) + Eventually(session).Should(Say(`--max-in-flight`)) Eventually(session).Should(Say(`--task`)) Eventually(session).Should(Say(`--var`)) Eventually(session).Should(Say(`--vars-file`)) diff --git a/integration/v7/push/rolling_push_test.go b/integration/v7/push/rolling_push_test.go index 689faf5244f..98e26aeca78 100644 --- a/integration/v7/push/rolling_push_test.go +++ b/integration/v7/push/rolling_push_test.go @@ -53,6 +53,30 @@ var _ = Describe("push with --strategy rolling", func() { Expect(session).To(Say(`#0\s+running`)) }) }) + + It("pushes the app and creates a new deployment with max in flight set", func() { + helpers.WithHelloWorldApp(func(appDir string) { + session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, + PushCommandName, appName, "--strategy", "rolling", "--max-in-flight", "3", + ) + + Eventually(session).Should(Exit(0)) + Expect(session).To(Say(`Pushing app %s to org %s / space %s as %s\.\.\.`, appName, organization, space, userName)) + Expect(session).To(Say(`Packaging files to upload\.\.\.`)) + Expect(session).To(Say(`Uploading files\.\.\.`)) + Expect(session).To(Say(`100.00%`)) + Expect(session).To(Say(`Waiting for API to complete processing files\.\.\.`)) + Expect(session).To(Say(`Staging app and tracing logs\.\.\.`)) + Expect(session).To(Say(`Starting deployment for app %s\.\.\.`, appName)) + Expect(session).To(Say(`Waiting for app to deploy\.\.\.`)) + Expect(session).To(Say(`name:\s+%s`, appName)) + Expect(session).To(Say(`requested state:\s+started`)) + Expect(session).To(Say(`routes:\s+%s.%s`, appName, helpers.DefaultSharedDomain())) + Expect(session).To(Say(`type:\s+web`)) + Expect(session).To(Say(`start command:\s+%s`, helpers.StaticfileBuildpackStartCommand)) + Expect(session).To(Say(`#0\s+running`)) + }) + }) }) When("canceling the deployment", func() { diff --git a/resources/deployment_resource.go b/resources/deployment_resource.go index aa342468d15..6e5b6cc71a3 100644 --- a/resources/deployment_resource.go +++ b/resources/deployment_resource.go @@ -13,6 +13,7 @@ type Deployment struct { StatusValue constant.DeploymentStatusValue StatusReason constant.DeploymentStatusReason LastStatusChange string + Options DeploymentOpts RevisionGUID string DropletGUID string CreatedAt string @@ -22,6 +23,10 @@ type Deployment struct { Strategy constant.DeploymentStrategy } +type DeploymentOpts struct { + MaxInFlight int `json:"max_in_flight"` +} + // MarshalJSON converts a Deployment into a Cloud Controller Deployment. func (d Deployment) MarshalJSON() ([]byte, error) { type Revision struct { @@ -33,6 +38,7 @@ func (d Deployment) MarshalJSON() ([]byte, error) { var ccDeployment struct { Droplet *Droplet `json:"droplet,omitempty"` + Options *DeploymentOpts `json:"options,omitempty"` Revision *Revision `json:"revision,omitempty"` Strategy constant.DeploymentStrategy `json:"strategy,omitempty"` Relationships Relationships `json:"relationships,omitempty"` @@ -50,6 +56,14 @@ func (d Deployment) MarshalJSON() ([]byte, error) { ccDeployment.Strategy = d.Strategy } + var b DeploymentOpts + if d.Options != b { + ccDeployment.Options = &d.Options + if d.Options.MaxInFlight < 1 { + ccDeployment.Options.MaxInFlight = 1 + } + } + ccDeployment.Relationships = d.Relationships return json.Marshal(ccDeployment) @@ -72,6 +86,7 @@ func (d *Deployment) UnmarshalJSON(data []byte) error { Droplet Droplet `json:"droplet,omitempty"` NewProcesses []Process `json:"new_processes,omitempty"` Strategy constant.DeploymentStrategy `json:"strategy"` + Options DeploymentOpts `json:"options,omitempty"` } err := cloudcontroller.DecodeJSON(data, &ccDeployment) @@ -89,6 +104,7 @@ func (d *Deployment) UnmarshalJSON(data []byte) error { d.DropletGUID = ccDeployment.Droplet.GUID d.NewProcesses = ccDeployment.NewProcesses d.Strategy = ccDeployment.Strategy + d.Options = ccDeployment.Options return nil } From d0e5398bb81c2962bcc19cf009f656af7a341a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Fri, 26 Jul 2024 11:57:57 -0500 Subject: [PATCH 02/13] Add max-in-flight flag to the restart command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/restart_command.go | 25 ++++++++++-- command/v7/restart_command_test.go | 30 +++++++++++++++ command/v7/shared/app_stager.go | 9 +++-- command/v7/shared/app_stager_test.go | 38 ++++++++++++------- .../v7/isolated/restart_command_test.go | 3 +- 5 files changed, 85 insertions(+), 20 deletions(-) diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index 1a453d50b9e..a8d5d17e95e 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -6,12 +6,14 @@ import ( "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" + "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" ) type RestartCommand struct { BaseCommand + MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively restarted at any given time. Only applies when --strategy flag is specified."` RequiredArgs flag.AppName `positional-args:"yes"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` @@ -50,6 +52,11 @@ func (cmd RestartCommand) Execute(args []string) error { return err } + err = cmd.ValidateFlags() + if err != nil { + return err + } + app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID) cmd.UI.DisplayWarnings(warnings) if err != nil { @@ -73,9 +80,10 @@ func (cmd RestartCommand) Execute(args []string) error { } opts := shared.AppStartOpts{ - Strategy: cmd.Strategy.Name, - NoWait: cmd.NoWait, - AppAction: constant.ApplicationRestarting, + Strategy: cmd.Strategy.Name, + NoWait: cmd.NoWait, + MaxInFlight: cmd.MaxInFlight, + AppAction: constant.ApplicationRestarting, } if packageGUID != "" { err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), packageGUID, opts) @@ -91,3 +99,14 @@ func (cmd RestartCommand) Execute(args []string) error { return nil } + +func (cmd RestartCommand) ValidateFlags() error { + switch true { + case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: + return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1: + return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + } + + return nil +} diff --git a/command/v7/restart_command_test.go b/command/v7/restart_command_test.go index 2cea1f43b97..ad4c77965d6 100644 --- a/command/v7/restart_command_test.go +++ b/command/v7/restart_command_test.go @@ -8,6 +8,7 @@ import ( "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/commandfakes" "code.cloudfoundry.org/cli/command/flag" + "code.cloudfoundry.org/cli/command/translatableerror" v7 "code.cloudfoundry.org/cli/command/v7" "code.cloudfoundry.org/cli/command/v7/shared/sharedfakes" "code.cloudfoundry.org/cli/command/v7/v7fakes" @@ -187,4 +188,33 @@ var _ = Describe("restart Command", func() { }) }) + DescribeTable("ValidateFlags returns an error", + func(setup func(), expectedErr error) { + setup() + err := cmd.ValidateFlags() + if expectedErr == nil { + Expect(err).To(BeNil()) + } else { + Expect(err).To(MatchError(expectedErr)) + } + }, + + Entry("max-in-flight is passed without strategy", + func() { + cmd.MaxInFlight = 10 + }, + translatableerror.RequiredFlagsError{ + Arg1: "--max-in-flight", + Arg2: "--strategy", + }), + + Entry("max-in-flight is smaller than 1", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.MaxInFlight = 0 + }, + translatableerror.IncorrectUsageError{ + Message: "--max-in-flight must be greater than or equal to 1", + }), + ) }) diff --git a/command/v7/shared/app_stager.go b/command/v7/shared/app_stager.go index 5364bbd31c2..2aed583033d 100644 --- a/command/v7/shared/app_stager.go +++ b/command/v7/shared/app_stager.go @@ -29,9 +29,10 @@ type AppStager interface { } type AppStartOpts struct { - Strategy constant.DeploymentStrategy - NoWait bool - AppAction constant.ApplicationAction + AppAction constant.ApplicationAction + MaxInFlight int + NoWait bool + Strategy constant.DeploymentStrategy } type Stager struct { @@ -124,9 +125,11 @@ func (stager *Stager) StartApp(app resources.Application, space configv3.Space, switch opts.AppAction { case constant.ApplicationRollingBack: dep.RevisionGUID = resourceGuid + dep.Options.MaxInFlight = opts.MaxInFlight deploymentGUID, warnings, err = stager.Actor.CreateDeployment(dep) default: dep.DropletGUID = resourceGuid + dep.Options.MaxInFlight = opts.MaxInFlight deploymentGUID, warnings, err = stager.Actor.CreateDeployment(dep) } diff --git a/command/v7/shared/app_stager_test.go b/command/v7/shared/app_stager_test.go index 9c6b936ea59..067ddb96310 100644 --- a/command/v7/shared/app_stager_test.go +++ b/command/v7/shared/app_stager_test.go @@ -35,6 +35,7 @@ var _ = Describe("app stager", func() { organization configv3.Organization pkgGUID string strategy constant.DeploymentStrategy + maxInFlight int noWait bool appAction constant.ApplicationAction @@ -58,6 +59,7 @@ var _ = Describe("app stager", func() { space = configv3.Space{Name: "some-space", GUID: "some-space-guid"} organization = configv3.Organization{Name: "some-org"} strategy = constant.DeploymentStrategyDefault + maxInFlight = 2 appAction = constant.ApplicationRestarting fakeActor.GetStreamingLogsForApplicationByNameAndSpaceStub = func(appName string, spaceGUID string, client sharedaction.LogCacheClient) (<-chan sharedaction.LogMessage, <-chan error, context.CancelFunc, v7action.Warnings, error) { @@ -107,9 +109,10 @@ var _ = Describe("app stager", func() { JustBeforeEach(func() { appStager = shared.NewAppStager(fakeActor, testUI, fakeConfig, fakeLogCacheClient) opts := shared.AppStartOpts{ - Strategy: strategy, - NoWait: noWait, - AppAction: appAction, + AppAction: appAction, + MaxInFlight: maxInFlight, + NoWait: noWait, + Strategy: strategy, } executeErr = appStager.StageAndStart(app, space, organization, pkgGUID, opts) }) @@ -173,13 +176,8 @@ var _ = Describe("app stager", func() { BeforeEach(func() { strategy = constant.DeploymentStrategyRolling noWait = true + maxInFlight = 5 appStager = shared.NewAppStager(fakeActor, testUI, fakeConfig, fakeLogCacheClient) - opts := shared.AppStartOpts{ - Strategy: strategy, - NoWait: noWait, - AppAction: appAction, - } - executeErr = appStager.StageAndStart(app, space, organization, pkgGUID, opts) }) It("Restages and starts the app", func() { @@ -190,6 +188,13 @@ var _ = Describe("app stager", func() { Expect(testUI.Out).To(Say("First instance restaged correctly, restaging remaining in the background")) }) + + It("creates expected deployment", func() { + Expect(fakeActor.CreateDeploymentCallCount()).To(Equal(1), "CreateDeployment...") + dep := fakeActor.CreateDeploymentArgsForCall(0) + Expect(dep.Options.MaxInFlight).To(Equal(5)) + Expect(string(dep.Strategy)).To(Equal("rolling")) + }) }) }) @@ -344,6 +349,7 @@ var _ = Describe("app stager", func() { strategy = constant.DeploymentStrategyDefault noWait = true + maxInFlight = 2 appAction = constant.ApplicationRestarting app = resources.Application{GUID: "app-guid", Name: "app-name", State: constant.ApplicationStarted} @@ -358,9 +364,10 @@ var _ = Describe("app stager", func() { JustBeforeEach(func() { appStager = shared.NewAppStager(fakeActor, testUI, fakeConfig, fakeLogCacheClient) opts := shared.AppStartOpts{ - Strategy: strategy, - NoWait: noWait, - AppAction: appAction, + Strategy: strategy, + NoWait: noWait, + MaxInFlight: maxInFlight, + AppAction: appAction, } executeErr = appStager.StartApp(app, space, organization, resourceGUID, opts) }) @@ -399,6 +406,7 @@ var _ = Describe("app stager", func() { dep := fakeActor.CreateDeploymentArgsForCall(0) Expect(dep.Relationships[constant.RelationshipTypeApplication].GUID).To(Equal(app.GUID)) Expect(dep.RevisionGUID).To(Equal("revision-guid")) + Expect(dep.Options.MaxInFlight).To(Equal(2)) Expect(testUI.Err).To(Say("create-deployment-warning")) Expect(testUI.Out).To(Say("Waiting for app to deploy...")) @@ -408,6 +416,10 @@ var _ = Describe("app stager", func() { }) When("the app starts successfully", func() { + BeforeEach(func() { + maxInFlight = 3 + }) + It("displays output for each step of deploying", func() { Expect(executeErr).To(BeNil()) @@ -416,6 +428,7 @@ var _ = Describe("app stager", func() { dep := fakeActor.CreateDeploymentArgsForCall(0) Expect(dep.Relationships[constant.RelationshipTypeApplication].GUID).To(Equal(app.GUID)) Expect(dep.DropletGUID).To(Equal("droplet-guid")) + Expect(dep.Options.MaxInFlight).To(Equal(3)) Expect(testUI.Err).To(Say("create-deployment-warning")) Expect(testUI.Out).To(Say("Waiting for app to deploy...")) @@ -627,5 +640,4 @@ var _ = Describe("app stager", func() { Expect(executeErr).To(Not(HaveOccurred())) }) }) - }) diff --git a/integration/v7/isolated/restart_command_test.go b/integration/v7/isolated/restart_command_test.go index d98e00f0c52..077d6f6c851 100644 --- a/integration/v7/isolated/restart_command_test.go +++ b/integration/v7/isolated/restart_command_test.go @@ -53,6 +53,7 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say("--strategy Deployment strategy can be canary, rolling or null.")) Eventually(session).Should(Say("--no-wait Exit when the first instance of the web process is healthy")) + Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`)) @@ -101,7 +102,7 @@ var _ = Describe("restart command", func() { }) }) It("creates a deploy", func() { - session := helpers.CF("restart", appName, "--strategy=rolling") + session := helpers.CF("restart", appName, "--strategy=rolling", "--max-in-flight=3") Eventually(session).Should(Say(`Restarting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) Eventually(session).Should(Say(`Creating deployment for app %s\.\.\.`, appName)) Eventually(session).Should(Say(`Waiting for app to deploy\.\.\.`)) From 22f9462ceada578538464540751b33db6c55f906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Fri, 26 Jul 2024 13:59:26 -0500 Subject: [PATCH 03/13] Add max-in-flight flag to restage command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/restage_command.go | 24 ++++++++++-- command/v7/restage_command_test.go | 38 ++++++++++++++++++- .../v7/isolated/restage_command_test.go | 3 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index 76b02c618bd..f9709313ef5 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -16,6 +16,7 @@ type RestageCommand struct { RequiredArgs flag.AppName `positional-args:"yes"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` + MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being restaged. Only applies when --strategy flag is specified."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` usage interface{} `usage:"CF_NAME restage APP_NAME\n\n This command will cause downtime unless you use '--strategy' flag.\n\nEXAMPLES:\n CF_NAME restage APP_NAME\n CF_NAME restage APP_NAME --strategy rolling\n CF_NAME restage APP_NAME --strategy canary --no-wait"` relatedCommands interface{} `related_commands:"restart"` @@ -52,6 +53,11 @@ func (cmd RestageCommand) Execute(args []string) error { return err } + err = cmd.ValidateFlags() + if err != nil { + return err + } + if len(cmd.Strategy.Name) <= 0 { cmd.UI.DisplayWarning("This action will cause app downtime.") } @@ -78,9 +84,10 @@ func (cmd RestageCommand) Execute(args []string) error { } opts := shared.AppStartOpts{ - Strategy: cmd.Strategy.Name, - NoWait: cmd.NoWait, - AppAction: constant.ApplicationRestarting, + AppAction: constant.ApplicationRestarting, + MaxInFlight: cmd.MaxInFlight, + NoWait: cmd.NoWait, + Strategy: cmd.Strategy.Name, } err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), pkg.GUID, opts) if err != nil { @@ -90,6 +97,17 @@ func (cmd RestageCommand) Execute(args []string) error { return nil } +func (cmd RestageCommand) ValidateFlags() error { + switch { + case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: + return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1: + return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + } + + return nil +} + func mapErr(config command.Config, appName string, err error) error { switch err.(type) { case actionerror.AllInstancesCrashedError: diff --git a/command/v7/restage_command_test.go b/command/v7/restage_command_test.go index 652e7740d84..2b36bc2cc35 100644 --- a/command/v7/restage_command_test.go +++ b/command/v7/restage_command_test.go @@ -8,6 +8,7 @@ import ( "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/commandfakes" "code.cloudfoundry.org/cli/command/flag" + "code.cloudfoundry.org/cli/command/translatableerror" v7 "code.cloudfoundry.org/cli/command/v7" "code.cloudfoundry.org/cli/command/v7/shared/sharedfakes" "code.cloudfoundry.org/cli/command/v7/v7fakes" @@ -73,6 +74,9 @@ var _ = Describe("restage Command", func() { v7action.Warnings{"get-package-warning"}, nil, ) + + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.MaxInFlight = 4 }) JustBeforeEach(func() { @@ -108,6 +112,7 @@ var _ = Describe("restage Command", func() { When("No strategy flag is given", func() { BeforeEach(func() { cmd.Strategy.Name = constant.DeploymentStrategyDefault + cmd.MaxInFlight = 0 }) It("warns that there will be app downtime", func() { Expect(testUI.Err).To(Say("This action will cause app downtime.")) @@ -170,7 +175,8 @@ var _ = Describe("restage Command", func() { Expect(spaceForApp).To(Equal(fakeConfig.TargetedSpace())) Expect(orgForApp).To(Equal(fakeConfig.TargetedOrganization())) Expect(pkgGUID).To(Equal("earliest-package-guid")) - Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyDefault)) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyRolling)) + Expect(opts.MaxInFlight).To(Equal(4)) Expect(opts.NoWait).To(Equal(false)) Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) }) @@ -188,4 +194,34 @@ var _ = Describe("restage Command", func() { It("succeeds", func() { Expect(executeErr).To(Not(HaveOccurred())) }) + + DescribeTable("ValidateFlags returns an error", + func(setup func(), expectedErr error) { + setup() + err := cmd.ValidateFlags() + if expectedErr == nil { + Expect(err).To(BeNil()) + } else { + Expect(err).To(MatchError(expectedErr)) + } + }, + Entry("max-in-flight is passed without strategy", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyDefault} + cmd.MaxInFlight = 10 + }, + translatableerror.RequiredFlagsError{ + Arg1: "--max-in-flight", + Arg2: "--strategy", + }), + + Entry("max-in-flight is smaller than 1", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.MaxInFlight = 0 + }, + translatableerror.IncorrectUsageError{ + Message: "--max-in-flight must be greater than or equal to 1", + }), + ) }) diff --git a/integration/v7/isolated/restage_command_test.go b/integration/v7/isolated/restage_command_test.go index ee6ee8a10fe..9184c1b7581 100644 --- a/integration/v7/isolated/restage_command_test.go +++ b/integration/v7/isolated/restage_command_test.go @@ -32,6 +32,7 @@ var _ = Describe("restage command", func() { Eventually(session).Should(Say("rg")) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say("--strategy Deployment strategy can be canary, rolling or null")) + Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say("--no-wait Exit when the first instance of the web process is healthy")) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) @@ -145,7 +146,7 @@ var _ = Describe("restage command", func() { userName, _ := helpers.GetCredentials() session := helpers.CustomCF(helpers.CFEnv{ EnvVars: map[string]string{"CF_STARTUP_TIMEOUT": "0.1"}, - }, "restage", appName, "--strategy", "rolling") + }, "restage", appName, "--strategy", "rolling", "--max-in-flight", "3") Consistently(session.Err).ShouldNot(Say(`This action will cause app downtime\.`)) Eventually(session).Should(Say(`Restaging app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) Eventually(session).Should(Say(`Creating deployment for app %s\.\.\.`, appName)) From 192513520d5c80e841910e77934baa092f239204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Fri, 26 Jul 2024 14:33:05 -0500 Subject: [PATCH 04/13] Add max-in-flight flag to rollback command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/rollback_command.go | 27 ++++++++++++++----- command/v7/rollback_command_test.go | 22 +++++++++++++++ .../v7/isolated/rollback_command_test.go | 3 ++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index 84119377363..2cb87c41d8f 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -8,6 +8,7 @@ import ( "code.cloudfoundry.org/cli/cf/errors" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" + "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" ) @@ -20,6 +21,7 @@ type RollbackCommand struct { Version flag.Revision `long:"version" required:"true" description:"Roll back to the specified revision"` relatedCommands interface{} `related_commands:"revisions"` usage interface{} `usage:"CF_NAME rollback APP_NAME [--version VERSION] [-f]"` + MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being rolled back."` LogCacheClient sharedaction.LogCacheClient Stager shared.AppStager @@ -50,6 +52,11 @@ func (cmd RollbackCommand) Execute(args []string) error { return err } + err = cmd.ValidateFlags() + if err != nil { + return err + } + app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID) cmd.UI.DisplayWarnings(warnings) if err != nil { @@ -72,10 +79,6 @@ func (cmd RollbackCommand) Execute(args []string) error { return err } - if err != nil { - return err - } - // TODO Localization? if !cmd.Force { cmd.UI.DisplayTextWithFlavor("Rolling '{{.AppName}}' back to revision '{{.TargetRevision}}' will create a new revision. The new revision will use the settings from revision '{{.TargetRevision}}'.", map[string]interface{}{ @@ -108,9 +111,10 @@ func (cmd RollbackCommand) Execute(args []string) error { }) opts := shared.AppStartOpts{ - Strategy: constant.DeploymentStrategyRolling, - NoWait: false, - AppAction: constant.ApplicationRollingBack, + AppAction: constant.ApplicationRollingBack, + MaxInFlight: cmd.MaxInFlight, + NoWait: false, + Strategy: constant.DeploymentStrategyRolling, } if cmd.Strategy.Name != "" { @@ -126,3 +130,12 @@ func (cmd RollbackCommand) Execute(args []string) error { return nil } + +func (cmd RollbackCommand) ValidateFlags() error { + switch { + case cmd.MaxInFlight < 1: + return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + } + + return nil +} diff --git a/command/v7/rollback_command_test.go b/command/v7/rollback_command_test.go index 6f80f7ca15f..f0947c17208 100644 --- a/command/v7/rollback_command_test.go +++ b/command/v7/rollback_command_test.go @@ -8,6 +8,7 @@ import ( "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/commandfakes" "code.cloudfoundry.org/cli/command/flag" + "code.cloudfoundry.org/cli/command/translatableerror" v7 "code.cloudfoundry.org/cli/command/v7" "code.cloudfoundry.org/cli/command/v7/shared/sharedfakes" "code.cloudfoundry.org/cli/command/v7/v7fakes" @@ -76,6 +77,7 @@ var _ = Describe("rollback Command", func() { }, Stager: fakeAppStager, } + cmd.MaxInFlight = 5 }) JustBeforeEach(func() { @@ -220,6 +222,7 @@ var _ = Describe("rollback Command", func() { Expect(revisionGUID).To(Equal("some-1-guid")) Expect(opts.AppAction).To(Equal(constant.ApplicationRollingBack)) Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyRolling)) + Expect(opts.MaxInFlight).To(Equal(5)) Expect(testUI.Out).To(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision will use the settings from revision '1'.", app)) Expect(testUI.Out).To(Say("Are you sure you want to continue?")) @@ -286,4 +289,23 @@ var _ = Describe("rollback Command", func() { }) }) + DescribeTable("ValidateFlags returns an error", + func(setup func(), expectedErr error) { + setup() + err := cmd.ValidateFlags() + if expectedErr == nil { + Expect(err).To(BeNil()) + } else { + Expect(err).To(MatchError(expectedErr)) + } + }, + + Entry("max-in-flight is smaller than 1", + func() { + cmd.MaxInFlight = 0 + }, + translatableerror.IncorrectUsageError{ + Message: "--max-in-flight must be greater than or equal to 1", + }), + ) }) diff --git a/integration/v7/isolated/rollback_command_test.go b/integration/v7/isolated/rollback_command_test.go index 75c12d6f621..61554adf62f 100644 --- a/integration/v7/isolated/rollback_command_test.go +++ b/integration/v7/isolated/rollback_command_test.go @@ -37,6 +37,7 @@ var _ = Describe("rollback command", func() { Expect(session).To(Say("-f Force rollback without confirmation")) Expect(session).To(Say("--strategy Deployment strategy can be canary or rolling. When not specified, it defaults to rolling.")) Expect(session).To(Say("--version Roll back to the specified revision")) + Expect(session).To(Say("--max-in-flight")) Expect(session).To(Say("SEE ALSO:")) Expect(session).To(Say("revisions")) }) @@ -113,7 +114,7 @@ applications: When("the -f flag is provided", func() { It("does not prompt the user, and just rolls back", func() { - session := helpers.CF("rollback", appName, "--version", "1", "-f") + session := helpers.CF("rollback", appName, "--version", "1", "-f", "--max-in-flight", "3") Eventually(session).Should(Exit(0)) Expect(session).To(HaveRollbackOutput(appName, orgName, spaceName, userName)) From b70dcf7edad9c0887a18bb7d1a45c3ec76bc6f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Mon, 29 Jul 2024 15:46:05 -0500 Subject: [PATCH 05/13] Add max-in-flight flag to copy-src command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/copy_source_command.go | 16 ++- command/v7/copy_source_command_test.go | 110 +++++++++++------- .../v7/isolated/copy_source_command_test.go | 1 + 3 files changed, 79 insertions(+), 48 deletions(-) diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index 9d18a329d83..f8591df6cdc 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -17,6 +17,7 @@ type CopySourceCommand struct { RequiredArgs flag.CopySourceArgs `positional-args:"yes"` usage interface{} `usage:"CF_NAME copy-source SOURCE_APP DESTINATION_APP [-s TARGET_SPACE [-o TARGET_ORG]] [--no-restart] [--strategy STRATEGY] [--no-wait]"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null"` + MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` NoRestart bool `long:"no-restart" description:"Do not restage the destination application"` Organization string `short:"o" long:"organization" description:"Org that contains the destination application"` @@ -48,6 +49,14 @@ func (cmd *CopySourceCommand) ValidateFlags() error { } } + if cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0 { + return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} + } + + if cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1 { + return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + } + return nil } @@ -160,9 +169,10 @@ func (cmd CopySourceCommand) Execute(args []string) error { cmd.UI.DisplayNewline() opts := shared.AppStartOpts{ - Strategy: cmd.Strategy.Name, - NoWait: cmd.NoWait, - AppAction: constant.ApplicationRestarting, + AppAction: constant.ApplicationRestarting, + MaxInFlight: cmd.MaxInFlight, + NoWait: cmd.NoWait, + Strategy: cmd.Strategy.Name, } err = cmd.Stager.StageAndStart(targetApp, targetSpace, targetOrg, pkg.GUID, opts) if err != nil { diff --git a/command/v7/copy_source_command_test.go b/command/v7/copy_source_command_test.go index 2afadbdb1b0..f0df517f1d2 100644 --- a/command/v7/copy_source_command_test.go +++ b/command/v7/copy_source_command_test.go @@ -125,49 +125,6 @@ var _ = Describe("copy-source Command", func() { }) }) - When("the target organization is specified but the targeted space isn't", func() { - BeforeEach(func() { - cmd.Organization = "some-other-organization" - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError(translatableerror.RequiredFlagsError{ - Arg1: "--organization, -o", - Arg2: "--space, -s", - })) - }) - }) - - When("the no restart and strategy flags are both provided", func() { - BeforeEach(func() { - cmd.NoRestart = true - cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{ - Args: []string{ - "--no-restart", "--strategy", - }, - })) - }) - }) - - When("the no restart and no wait flags are both provided", func() { - BeforeEach(func() { - cmd.NoRestart = true - cmd.NoWait = true - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{ - Args: []string{ - "--no-restart", "--no-wait", - }, - })) - }) - }) - When("a target org and space is provided", func() { BeforeEach(func() { cmd.Organization = "destination-org" @@ -329,6 +286,7 @@ var _ = Describe("copy-source Command", func() { cmd.Strategy = flag.DeploymentStrategy{ Name: constant.DeploymentStrategyRolling, } + cmd.MaxInFlight = 5 }) It("stages and starts the app with the appropriate strategy", func() { @@ -338,9 +296,10 @@ var _ = Describe("copy-source Command", func() { Expect(spaceForApp).To(Equal(configv3.Space{Name: "some-space", GUID: "some-space-guid"})) Expect(orgForApp).To(Equal(configv3.Organization{Name: "some-org"})) Expect(pkgGUID).To(Equal("target-package-guid")) - Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyRolling)) - Expect(opts.NoWait).To(Equal(false)) Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.MaxInFlight).To(Equal(5)) + Expect(opts.NoWait).To(Equal(false)) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyRolling)) }) }) @@ -349,6 +308,7 @@ var _ = Describe("copy-source Command", func() { cmd.Strategy = flag.DeploymentStrategy{ Name: constant.DeploymentStrategyCanary, } + cmd.MaxInFlight = 1 }) It("stages and starts the app with the appropriate strategy", func() { @@ -417,4 +377,64 @@ var _ = Describe("copy-source Command", func() { It("succeeds", func() { Expect(executeErr).To(Not(HaveOccurred())) }) + + DescribeTable("ValidateFlags returns an error", + func(setup func(), expectedErr error) { + setup() + err := cmd.ValidateFlags() + if expectedErr == nil { + Expect(err).To(BeNil()) + } else { + Expect(err).To(MatchError(expectedErr)) + } + }, + Entry("the target organization is specified but the targeted space isn't", + func() { + cmd.Organization = "some-other-organization" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--organization, -o", + Arg2: "--space, -s", + }), + + Entry("the no restart and strategy flags are both provided", + func() { + cmd.NoRestart = true + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + }, + translatableerror.ArgumentCombinationError{ + Args: []string{ + "--no-restart", "--strategy", + }, + }), + + Entry("the no restart and no wait flags are both provided", + func() { + cmd.NoRestart = true + cmd.NoWait = true + }, + translatableerror.ArgumentCombinationError{ + Args: []string{ + "--no-restart", "--no-wait", + }, + }), + + Entry("max-in-flight is passed without strategy", + func() { + cmd.MaxInFlight = 10 + }, + translatableerror.RequiredFlagsError{ + Arg1: "--max-in-flight", + Arg2: "--strategy", + }), + + Entry("max-in-flight is smaller than 1", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.MaxInFlight = 0 + }, + translatableerror.IncorrectUsageError{ + Message: "--max-in-flight must be greater than or equal to 1", + }), + ) }) diff --git a/integration/v7/isolated/copy_source_command_test.go b/integration/v7/isolated/copy_source_command_test.go index 83891422636..bd8829140ca 100644 --- a/integration/v7/isolated/copy_source_command_test.go +++ b/integration/v7/isolated/copy_source_command_test.go @@ -449,6 +449,7 @@ func helpText(session *Session) { Eventually(session).Should(Say(`cf copy-source SOURCE_APP DESTINATION_APP \[-s TARGET_SPACE \[-o TARGET_ORG\]\] \[--no-restart\] \[--strategy STRATEGY\] \[--no-wait\]`)) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) + Eventually(session).Should(Say(`--max-in-flight\s+Defines the maximum number of instances`)) Eventually(session).Should(Say(`--no-wait\s+ Exit when the first instance of the web process is healthy`)) Eventually(session).Should(Say(`--no-restart\s+Do not restage the destination application`)) Eventually(session).Should(Say(`--organization, -o\s+Org that contains the destination application`)) From 62dd35624319ed356bf996bd303f6fad16cbd046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Wed, 7 Aug 2024 11:18:49 -0500 Subject: [PATCH 06/13] Fix issues with max-in-flight validations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/copy_source_command.go | 2 +- command/v7/push_command.go | 2 +- command/v7/restage_command.go | 2 +- command/v7/restart_command.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index f8591df6cdc..bb7f49b68db 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -53,7 +53,7 @@ func (cmd *CopySourceCommand) ValidateFlags() error { return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} } - if cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1 { + if cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0) { return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/push_command.go b/command/v7/push_command.go index e66e7a6c269..780c1a553ec 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -490,7 +490,7 @@ func (cmd PushCommand) ValidateFlags() error { case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} - case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1: + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0): return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index f9709313ef5..7b32effd84b 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -101,7 +101,7 @@ func (cmd RestageCommand) ValidateFlags() error { switch { case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} - case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1: + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0): return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index a8d5d17e95e..6633983cf69 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -104,7 +104,7 @@ func (cmd RestartCommand) ValidateFlags() error { switch true { case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} - case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight < 1: + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0): return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } From ea1a89c6fb0cad0e9edb8cdbb0960d70a7e59e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Wed, 7 Aug 2024 13:09:58 -0500 Subject: [PATCH 07/13] Fix rollback and tests that check help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/rollback_command.go | 2 +- integration/v7/isolated/restage_command_test.go | 2 +- integration/v7/isolated/restart_command_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index 2cb87c41d8f..9cc135f1327 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -133,7 +133,7 @@ func (cmd RollbackCommand) Execute(args []string) error { func (cmd RollbackCommand) ValidateFlags() error { switch { - case cmd.MaxInFlight < 1: + case cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/integration/v7/isolated/restage_command_test.go b/integration/v7/isolated/restage_command_test.go index 9184c1b7581..48cc84089fb 100644 --- a/integration/v7/isolated/restage_command_test.go +++ b/integration/v7/isolated/restage_command_test.go @@ -31,7 +31,7 @@ var _ = Describe("restage command", func() { Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("rg")) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say("--strategy Deployment strategy can be canary, rolling or null")) + Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say("--no-wait Exit when the first instance of the web process is healthy")) Eventually(session).Should(Say("ENVIRONMENT:")) diff --git a/integration/v7/isolated/restart_command_test.go b/integration/v7/isolated/restart_command_test.go index 077d6f6c851..13aff013a9e 100644 --- a/integration/v7/isolated/restart_command_test.go +++ b/integration/v7/isolated/restart_command_test.go @@ -51,8 +51,8 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("rs")) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say("--strategy Deployment strategy can be canary, rolling or null.")) - Eventually(session).Should(Say("--no-wait Exit when the first instance of the web process is healthy")) + Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null.`)) + Eventually(session).Should(Say(`--no-wait\s+Exit when the first instance of the web process is healthy`)) Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) From 91d1c275269e134a3e8418734d14461301ddd830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Wed, 7 Aug 2024 14:52:44 -0500 Subject: [PATCH 08/13] Remove `--no-wait` to ensure no flakes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- integration/v7/isolated/continue_deployment_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/v7/isolated/continue_deployment_test.go b/integration/v7/isolated/continue_deployment_test.go index 18ea3dab317..4c1bf73370e 100644 --- a/integration/v7/isolated/continue_deployment_test.go +++ b/integration/v7/isolated/continue_deployment_test.go @@ -87,7 +87,7 @@ var _ = Describe("Continue Deployment", func() { When("There is a canary deployment", func() { It("succeeds", func() { helpers.WithHelloWorldApp(func(appDir string) { - helpers.CF("push", appName, "-p", appDir, "--strategy=canary", "--no-wait").Wait() + helpers.CF("push", appName, "-p", appDir, "--strategy=canary").Wait() }) session := helpers.CF("continue-deployment", appName) From b1a0160b86dd35dcc07e34725e6fb91226eb359c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Wed, 7 Aug 2024 14:53:47 -0500 Subject: [PATCH 09/13] Fix other integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- integration/v7/isolated/restage_command_test.go | 4 ++-- integration/v7/isolated/restart_command_test.go | 2 +- integration/v7/isolated/rollback_command_test.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration/v7/isolated/restage_command_test.go b/integration/v7/isolated/restage_command_test.go index 48cc84089fb..fd60b93276d 100644 --- a/integration/v7/isolated/restage_command_test.go +++ b/integration/v7/isolated/restage_command_test.go @@ -31,9 +31,9 @@ var _ = Describe("restage command", func() { Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("rg")) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) Eventually(session).Should(Say("--max-in-flight")) - Eventually(session).Should(Say("--no-wait Exit when the first instance of the web process is healthy")) + Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) + Eventually(session).Should(Say(`--no-wait\s+Exit when the first instance of the web process is healthy`)) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`)) diff --git a/integration/v7/isolated/restart_command_test.go b/integration/v7/isolated/restart_command_test.go index 13aff013a9e..82ebf8239e9 100644 --- a/integration/v7/isolated/restart_command_test.go +++ b/integration/v7/isolated/restart_command_test.go @@ -51,9 +51,9 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("rs")) Eventually(session).Should(Say("OPTIONS:")) + Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null.`)) Eventually(session).Should(Say(`--no-wait\s+Exit when the first instance of the web process is healthy`)) - Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`)) diff --git a/integration/v7/isolated/rollback_command_test.go b/integration/v7/isolated/rollback_command_test.go index 61554adf62f..e862edb55e1 100644 --- a/integration/v7/isolated/rollback_command_test.go +++ b/integration/v7/isolated/rollback_command_test.go @@ -34,9 +34,9 @@ var _ = Describe("rollback command", func() { Expect(session).To(Say("USAGE:")) Expect(session).To(Say(`cf rollback APP_NAME \[--version VERSION\]`)) Expect(session).To(Say("OPTIONS:")) - Expect(session).To(Say("-f Force rollback without confirmation")) + Expect(session).To(Say(`-f\s+Force rollback without confirmation`)) Expect(session).To(Say("--strategy Deployment strategy can be canary or rolling. When not specified, it defaults to rolling.")) - Expect(session).To(Say("--version Roll back to the specified revision")) + Expect(session).To(Say(`--version\s+Roll back to the specified revision`)) Expect(session).To(Say("--max-in-flight")) Expect(session).To(Say("SEE ALSO:")) Expect(session).To(Say("revisions")) From 5d88245d0a6be32e2c8ff1a5a480e00d55827b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Wed, 7 Aug 2024 16:08:13 -0500 Subject: [PATCH 10/13] Another fixed test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- integration/v7/isolated/restage_command_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/v7/isolated/restage_command_test.go b/integration/v7/isolated/restage_command_test.go index fd60b93276d..97f2317d6a1 100644 --- a/integration/v7/isolated/restage_command_test.go +++ b/integration/v7/isolated/restage_command_test.go @@ -31,8 +31,8 @@ var _ = Describe("restage command", func() { Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("rg")) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) + Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say(`--no-wait\s+Exit when the first instance of the web process is healthy`)) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) From ffcb89818bd5f1a515f1eb6382d198fa375cfcc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Thu, 8 Aug 2024 09:16:44 -0500 Subject: [PATCH 11/13] Fix order of push options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/push_command.go | 2 +- integration/v7/push/help_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/command/v7/push_command.go b/command/v7/push_command.go index 780c1a553ec..7de7b5fd2fa 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -90,8 +90,8 @@ type PushCommand struct { Instances flag.Instances `long:"instances" short:"i" description:"Number of instances"` LogRateLimit string `long:"log-rate-limit" short:"l" description:"Log rate limit per second, in bytes (e.g. 128B, 4K, 1M). -l=-1 represents unlimited"` PathToManifest flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` - Memory string `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` + Memory string `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` NoRoute bool `long:"no-route" description:"Do not map a route to this app"` NoStart bool `long:"no-start" description:"Do not stage and start the app after pushing"` diff --git a/integration/v7/push/help_test.go b/integration/v7/push/help_test.go index c484576e81f..32845920753 100644 --- a/integration/v7/push/help_test.go +++ b/integration/v7/push/help_test.go @@ -81,6 +81,7 @@ var _ = Describe("help", func() { Eventually(session).Should(Say(`--instances, -i`)) Eventually(session).Should(Say(`--log-rate-limit, -l\s+Log rate limit per second, in bytes \(e.g. 128B, 4K, 1M\). -l=-1 represents unlimited`)) Eventually(session).Should(Say(`--manifest, -f`)) + Eventually(session).Should(Say(`--max-in-flight`)) Eventually(session).Should(Say(`--memory, -m`)) Eventually(session).Should(Say(`--no-manifest`)) Eventually(session).Should(Say(`--no-route`)) @@ -91,7 +92,6 @@ var _ = Describe("help", func() { Eventually(session).Should(Say(`--stack, -s`)) Eventually(session).Should(Say(`--start-command, -c`)) Eventually(session).Should(Say(`--strategy`)) - Eventually(session).Should(Say(`--max-in-flight`)) Eventually(session).Should(Say(`--task`)) Eventually(session).Should(Say(`--var`)) Eventually(session).Should(Say(`--vars-file`)) From 63c394b59ef0ff23822ec2d68d1e8cde54d0e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Thu, 8 Aug 2024 10:48:29 -0500 Subject: [PATCH 12/13] Change the way we check for presence of the --max-in-flight flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Provide a better error when talking to older versions of CAPI - Address some comments in the PR review Signed-off-by: João Pereira --- .../create_deployment_for_push_plan.go | 9 +++++---- actor/v7pushaction/push_plan.go | 2 +- ...tup_deployment_information_for_push_plan.go | 4 ++-- ...eployment_information_for_push_plan_test.go | 13 +++++++++++-- .../convert_to_translatable_error.go | 7 +++++++ command/v7/copy_source_command.go | 18 +++++++++++------- command/v7/copy_source_command_test.go | 12 ++++++++---- command/v7/push_command.go | 6 +++--- command/v7/push_command_test.go | 11 +++++++---- command/v7/restage_command.go | 18 +++++++++++------- command/v7/restage_command_test.go | 11 +++++++---- command/v7/restart_command.go | 18 +++++++++++------- command/v7/restart_command_test.go | 6 ++++-- command/v7/rollback_command.go | 14 ++++++++------ command/v7/rollback_command_test.go | 6 ++++-- 15 files changed, 100 insertions(+), 55 deletions(-) diff --git a/actor/v7pushaction/create_deployment_for_push_plan.go b/actor/v7pushaction/create_deployment_for_push_plan.go index 1f6e04454b7..5b23178c3a5 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan.go +++ b/actor/v7pushaction/create_deployment_for_push_plan.go @@ -9,14 +9,15 @@ func (actor Actor) CreateDeploymentForApplication(pushPlan PushPlan, eventStream eventStream <- &PushEvent{Plan: pushPlan, Event: StartingDeployment} dep := resources.Deployment{ - Strategy: pushPlan.Strategy, - Options: resources.DeploymentOpts{ - MaxInFlight: pushPlan.MaxInFlight, - }, + Strategy: pushPlan.Strategy, DropletGUID: pushPlan.DropletGUID, Relationships: resources.Relationships{constant.RelationshipTypeApplication: resources.Relationship{GUID: pushPlan.Application.GUID}}, } + if pushPlan.MaxInFlight != 0 { + dep.Options = resources.DeploymentOpts{MaxInFlight: pushPlan.MaxInFlight} + } + deploymentGUID, warnings, err := actor.V7Actor.CreateDeployment(dep) if err != nil { diff --git a/actor/v7pushaction/push_plan.go b/actor/v7pushaction/push_plan.go index 9460509d065..876a8ec2259 100644 --- a/actor/v7pushaction/push_plan.go +++ b/actor/v7pushaction/push_plan.go @@ -48,7 +48,7 @@ type FlagOverrides struct { HealthCheckType constant.HealthCheckType Instances types.NullInt Memory string - MaxInFlight int + MaxInFlight *int NoStart bool NoWait bool ProvidedAppPath string diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan.go b/actor/v7pushaction/setup_deployment_information_for_push_plan.go index 481fcb9a82d..11e4dd26d8e 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan.go @@ -5,8 +5,8 @@ import "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" func SetupDeploymentInformationForPushPlan(pushPlan PushPlan, overrides FlagOverrides) (PushPlan, error) { pushPlan.Strategy = overrides.Strategy - if overrides.Strategy != constant.DeploymentStrategyDefault { - pushPlan.MaxInFlight = overrides.MaxInFlight + if overrides.Strategy != constant.DeploymentStrategyDefault && overrides.MaxInFlight != nil { + pushPlan.MaxInFlight = *overrides.MaxInFlight } return pushPlan, nil diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go index 82aa75edfb7..14515c9f446 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go @@ -30,7 +30,8 @@ var _ = Describe("SetupDeploymentInformationForPushPlan", func() { When("flag overrides specifies strategy", func() { BeforeEach(func() { overrides.Strategy = "rolling" - overrides.MaxInFlight = 5 + maxInFlight := 5 + overrides.MaxInFlight = &maxInFlight }) It("sets the strategy on the push plan", func() { @@ -46,7 +47,8 @@ var _ = Describe("SetupDeploymentInformationForPushPlan", func() { When("flag overrides does not specify strategy", func() { BeforeEach(func() { - overrides.MaxInFlight = 10 + maxInFlight := 10 + overrides.MaxInFlight = &maxInFlight }) It("leaves the strategy as its default value on the push plan", func() { Expect(executeErr).ToNot(HaveOccurred()) @@ -58,4 +60,11 @@ var _ = Describe("SetupDeploymentInformationForPushPlan", func() { Expect(expectedPushPlan.MaxInFlight).To(Equal(0)) }) }) + + When("flag not provided", func() { + It("does not set MaxInFlight", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MaxInFlight).To(Equal(0)) + }) + }) }) diff --git a/command/translatableerror/convert_to_translatable_error.go b/command/translatableerror/convert_to_translatable_error.go index 4bcacac1e35..f8636a7c5ec 100644 --- a/command/translatableerror/convert_to_translatable_error.go +++ b/command/translatableerror/convert_to_translatable_error.go @@ -188,6 +188,13 @@ func ConvertToTranslatableError(err error) error { return RunTaskError{Message: "App is not staged."} } + if strings.Contains(e.Message, "Unknown field(s): 'options'") { + return MinimumCFAPIVersionNotMetError{ + Command: "'--max-in-flight' flag", + MinimumVersion: "3.173.0", + } + } + // JSON Errors case *json.SyntaxError: return JSONSyntaxError{Err: e} diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index bb7f49b68db..c4ae96eeef1 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -17,7 +17,7 @@ type CopySourceCommand struct { RequiredArgs flag.CopySourceArgs `positional-args:"yes"` usage interface{} `usage:"CF_NAME copy-source SOURCE_APP DESTINATION_APP [-s TARGET_SPACE [-o TARGET_ORG]] [--no-restart] [--strategy STRATEGY] [--no-wait]"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null"` - MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` + MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` NoRestart bool `long:"no-restart" description:"Do not restage the destination application"` Organization string `short:"o" long:"organization" description:"Org that contains the destination application"` @@ -49,11 +49,11 @@ func (cmd *CopySourceCommand) ValidateFlags() error { } } - if cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0 { + if cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil { return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} } - if cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0) { + if cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1 { return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } @@ -169,11 +169,15 @@ func (cmd CopySourceCommand) Execute(args []string) error { cmd.UI.DisplayNewline() opts := shared.AppStartOpts{ - AppAction: constant.ApplicationRestarting, - MaxInFlight: cmd.MaxInFlight, - NoWait: cmd.NoWait, - Strategy: cmd.Strategy.Name, + AppAction: constant.ApplicationRestarting, + NoWait: cmd.NoWait, + Strategy: cmd.Strategy.Name, } + + if cmd.MaxInFlight != nil { + opts.MaxInFlight = *cmd.MaxInFlight + } + err = cmd.Stager.StageAndStart(targetApp, targetSpace, targetOrg, pkg.GUID, opts) if err != nil { return mapErr(cmd.Config, targetApp.Name, err) diff --git a/command/v7/copy_source_command_test.go b/command/v7/copy_source_command_test.go index f0df517f1d2..9d3787dc6ff 100644 --- a/command/v7/copy_source_command_test.go +++ b/command/v7/copy_source_command_test.go @@ -286,7 +286,8 @@ var _ = Describe("copy-source Command", func() { cmd.Strategy = flag.DeploymentStrategy{ Name: constant.DeploymentStrategyRolling, } - cmd.MaxInFlight = 5 + maxInFlight := 5 + cmd.MaxInFlight = &maxInFlight }) It("stages and starts the app with the appropriate strategy", func() { @@ -308,7 +309,8 @@ var _ = Describe("copy-source Command", func() { cmd.Strategy = flag.DeploymentStrategy{ Name: constant.DeploymentStrategyCanary, } - cmd.MaxInFlight = 1 + maxInFlight := 1 + cmd.MaxInFlight = &maxInFlight }) It("stages and starts the app with the appropriate strategy", func() { @@ -421,7 +423,8 @@ var _ = Describe("copy-source Command", func() { Entry("max-in-flight is passed without strategy", func() { - cmd.MaxInFlight = 10 + maxInFlight := 5 + cmd.MaxInFlight = &maxInFlight }, translatableerror.RequiredFlagsError{ Arg1: "--max-in-flight", @@ -431,7 +434,8 @@ var _ = Describe("copy-source Command", func() { Entry("max-in-flight is smaller than 1", func() { cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} - cmd.MaxInFlight = 0 + maxInFlight := 0 + cmd.MaxInFlight = &maxInFlight }, translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", diff --git a/command/v7/push_command.go b/command/v7/push_command.go index 7de7b5fd2fa..cbf71d78e78 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -90,7 +90,7 @@ type PushCommand struct { Instances flag.Instances `long:"instances" short:"i" description:"Number of instances"` LogRateLimit string `long:"log-rate-limit" short:"l" description:"Log rate limit per second, in bytes (e.g. 128B, 4K, 1M). -l=-1 represents unlimited"` PathToManifest flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` - MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` + MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` Memory string `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` NoRoute bool `long:"no-route" description:"Do not map a route to this app"` @@ -488,9 +488,9 @@ func (cmd PushCommand) ValidateFlags() error { case !cmd.validBuildpacks(): return translatableerror.InvalidBuildpacksError{} - case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: + case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil: return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} - case cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0): + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/push_command_test.go b/command/v7/push_command_test.go index b038f82d1ac..ead21520928 100644 --- a/command/v7/push_command_test.go +++ b/command/v7/push_command_test.go @@ -1072,7 +1072,8 @@ var _ = Describe("push Command", func() { cmd.RandomRoute = false cmd.NoStart = true cmd.NoWait = true - cmd.MaxInFlight = 1 + maxInFlight := 1 + cmd.MaxInFlight = &maxInFlight cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} cmd.Instances = flag.Instances{NullInt: types.NullInt{Value: 10, IsSet: true}} cmd.PathToManifest = "/manifest/path" @@ -1109,7 +1110,7 @@ var _ = Describe("push Command", func() { Expect(overrides.Vars).To(Equal([]template.VarKV{{Name: "key", Value: "val"}})) Expect(overrides.Task).To(BeTrue()) Expect(overrides.LogRateLimit).To(Equal("512M")) - Expect(overrides.MaxInFlight).To(Equal(1)) + Expect(*overrides.MaxInFlight).To(Equal(1)) }) When("a docker image is provided", func() { @@ -1295,7 +1296,8 @@ var _ = Describe("push Command", func() { Entry("max-in-flight is passed without strategy", func() { - cmd.MaxInFlight = 10 + maxInFlight := 10 + cmd.MaxInFlight = &maxInFlight }, translatableerror.RequiredFlagsError{ Arg1: "--max-in-flight", @@ -1305,7 +1307,8 @@ var _ = Describe("push Command", func() { Entry("max-in-flight is smaller than 1", func() { cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} - cmd.MaxInFlight = 0 + maxInFlight := 0 + cmd.MaxInFlight = &maxInFlight }, translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index 7b32effd84b..6fa503b2d81 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -16,7 +16,7 @@ type RestageCommand struct { RequiredArgs flag.AppName `positional-args:"yes"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` - MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being restaged. Only applies when --strategy flag is specified."` + MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being restaged. Only applies when --strategy flag is specified."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` usage interface{} `usage:"CF_NAME restage APP_NAME\n\n This command will cause downtime unless you use '--strategy' flag.\n\nEXAMPLES:\n CF_NAME restage APP_NAME\n CF_NAME restage APP_NAME --strategy rolling\n CF_NAME restage APP_NAME --strategy canary --no-wait"` relatedCommands interface{} `related_commands:"restart"` @@ -84,11 +84,15 @@ func (cmd RestageCommand) Execute(args []string) error { } opts := shared.AppStartOpts{ - AppAction: constant.ApplicationRestarting, - MaxInFlight: cmd.MaxInFlight, - NoWait: cmd.NoWait, - Strategy: cmd.Strategy.Name, + AppAction: constant.ApplicationRestarting, + NoWait: cmd.NoWait, + Strategy: cmd.Strategy.Name, } + + if cmd.MaxInFlight != nil { + opts.MaxInFlight = *cmd.MaxInFlight + } + err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), pkg.GUID, opts) if err != nil { return mapErr(cmd.Config, cmd.RequiredArgs.AppName, err) @@ -99,9 +103,9 @@ func (cmd RestageCommand) Execute(args []string) error { func (cmd RestageCommand) ValidateFlags() error { switch { - case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: + case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil: return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} - case cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0): + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/restage_command_test.go b/command/v7/restage_command_test.go index 2b36bc2cc35..8cddad4ca5e 100644 --- a/command/v7/restage_command_test.go +++ b/command/v7/restage_command_test.go @@ -76,7 +76,8 @@ var _ = Describe("restage Command", func() { ) cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} - cmd.MaxInFlight = 4 + maxInFlight := 4 + cmd.MaxInFlight = &maxInFlight }) JustBeforeEach(func() { @@ -112,7 +113,7 @@ var _ = Describe("restage Command", func() { When("No strategy flag is given", func() { BeforeEach(func() { cmd.Strategy.Name = constant.DeploymentStrategyDefault - cmd.MaxInFlight = 0 + cmd.MaxInFlight = nil }) It("warns that there will be app downtime", func() { Expect(testUI.Err).To(Say("This action will cause app downtime.")) @@ -208,7 +209,8 @@ var _ = Describe("restage Command", func() { Entry("max-in-flight is passed without strategy", func() { cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyDefault} - cmd.MaxInFlight = 10 + maxInFlight := 10 + cmd.MaxInFlight = &maxInFlight }, translatableerror.RequiredFlagsError{ Arg1: "--max-in-flight", @@ -218,7 +220,8 @@ var _ = Describe("restage Command", func() { Entry("max-in-flight is smaller than 1", func() { cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} - cmd.MaxInFlight = 0 + maxInFlight := 0 + cmd.MaxInFlight = &maxInFlight }, translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index 6633983cf69..dfbe9c5e4a2 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -13,7 +13,7 @@ import ( type RestartCommand struct { BaseCommand - MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively restarted at any given time. Only applies when --strategy flag is specified."` + MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively restarted at any given time. Only applies when --strategy flag is specified."` RequiredArgs flag.AppName `positional-args:"yes"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` @@ -80,11 +80,15 @@ func (cmd RestartCommand) Execute(args []string) error { } opts := shared.AppStartOpts{ - Strategy: cmd.Strategy.Name, - NoWait: cmd.NoWait, - MaxInFlight: cmd.MaxInFlight, - AppAction: constant.ApplicationRestarting, + Strategy: cmd.Strategy.Name, + NoWait: cmd.NoWait, + AppAction: constant.ApplicationRestarting, } + + if cmd.MaxInFlight != nil { + opts.MaxInFlight = *cmd.MaxInFlight + } + if packageGUID != "" { err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), packageGUID, opts) if err != nil { @@ -102,9 +106,9 @@ func (cmd RestartCommand) Execute(args []string) error { func (cmd RestartCommand) ValidateFlags() error { switch true { - case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight > 0: + case cmd.Strategy.Name == constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil: return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} - case cmd.Strategy.Name != constant.DeploymentStrategyDefault && (cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0): + case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/restart_command_test.go b/command/v7/restart_command_test.go index ad4c77965d6..4d4f8e828bd 100644 --- a/command/v7/restart_command_test.go +++ b/command/v7/restart_command_test.go @@ -201,7 +201,8 @@ var _ = Describe("restart Command", func() { Entry("max-in-flight is passed without strategy", func() { - cmd.MaxInFlight = 10 + maxInFlight := 10 + cmd.MaxInFlight = &maxInFlight }, translatableerror.RequiredFlagsError{ Arg1: "--max-in-flight", @@ -211,7 +212,8 @@ var _ = Describe("restart Command", func() { Entry("max-in-flight is smaller than 1", func() { cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} - cmd.MaxInFlight = 0 + maxInFlight := 0 + cmd.MaxInFlight = &maxInFlight }, translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index 9cc135f1327..abda4d5e4c4 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -21,7 +21,7 @@ type RollbackCommand struct { Version flag.Revision `long:"version" required:"true" description:"Roll back to the specified revision"` relatedCommands interface{} `related_commands:"revisions"` usage interface{} `usage:"CF_NAME rollback APP_NAME [--version VERSION] [-f]"` - MaxInFlight int `long:"max-in-flight" default:"-1" description:"Defines the maximum number of instances that will be actively being rolled back."` + MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being rolled back."` LogCacheClient sharedaction.LogCacheClient Stager shared.AppStager @@ -111,10 +111,12 @@ func (cmd RollbackCommand) Execute(args []string) error { }) opts := shared.AppStartOpts{ - AppAction: constant.ApplicationRollingBack, - MaxInFlight: cmd.MaxInFlight, - NoWait: false, - Strategy: constant.DeploymentStrategyRolling, + AppAction: constant.ApplicationRollingBack, + NoWait: false, + Strategy: constant.DeploymentStrategyRolling, + } + if cmd.MaxInFlight != nil { + opts.MaxInFlight = *cmd.MaxInFlight } if cmd.Strategy.Name != "" { @@ -133,7 +135,7 @@ func (cmd RollbackCommand) Execute(args []string) error { func (cmd RollbackCommand) ValidateFlags() error { switch { - case cmd.MaxInFlight < -1 || cmd.MaxInFlight == 0: + case cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } diff --git a/command/v7/rollback_command_test.go b/command/v7/rollback_command_test.go index f0947c17208..9edcfc564a9 100644 --- a/command/v7/rollback_command_test.go +++ b/command/v7/rollback_command_test.go @@ -77,7 +77,8 @@ var _ = Describe("rollback Command", func() { }, Stager: fakeAppStager, } - cmd.MaxInFlight = 5 + maxInFlight := 5 + cmd.MaxInFlight = &maxInFlight }) JustBeforeEach(func() { @@ -302,7 +303,8 @@ var _ = Describe("rollback Command", func() { Entry("max-in-flight is smaller than 1", func() { - cmd.MaxInFlight = 0 + maxInFlight := 0 + cmd.MaxInFlight = &maxInFlight }, translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", From deff01c4c7ddb0750eea2054ab1d4af7bc92949a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Thu, 8 Aug 2024 15:54:48 -0500 Subject: [PATCH 13/13] Change order of flags so they are alphabetized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Pereira --- command/v7/rollback_command.go | 2 +- integration/v7/isolated/rollback_command_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index abda4d5e4c4..14854c42a36 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -17,11 +17,11 @@ type RollbackCommand struct { Force bool `short:"f" description:"Force rollback without confirmation"` RequiredArgs flag.AppName `positional-args:"yes"` + MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being rolled back."` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary or rolling. When not specified, it defaults to rolling."` Version flag.Revision `long:"version" required:"true" description:"Roll back to the specified revision"` relatedCommands interface{} `related_commands:"revisions"` usage interface{} `usage:"CF_NAME rollback APP_NAME [--version VERSION] [-f]"` - MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being rolled back."` LogCacheClient sharedaction.LogCacheClient Stager shared.AppStager diff --git a/integration/v7/isolated/rollback_command_test.go b/integration/v7/isolated/rollback_command_test.go index e862edb55e1..056c2115b9e 100644 --- a/integration/v7/isolated/rollback_command_test.go +++ b/integration/v7/isolated/rollback_command_test.go @@ -35,9 +35,9 @@ var _ = Describe("rollback command", func() { Expect(session).To(Say(`cf rollback APP_NAME \[--version VERSION\]`)) Expect(session).To(Say("OPTIONS:")) Expect(session).To(Say(`-f\s+Force rollback without confirmation`)) - Expect(session).To(Say("--strategy Deployment strategy can be canary or rolling. When not specified, it defaults to rolling.")) - Expect(session).To(Say(`--version\s+Roll back to the specified revision`)) Expect(session).To(Say("--max-in-flight")) + Expect(session).To(Say(`--strategy\s+Deployment strategy can be canary or rolling. When not specified, it defaults to rolling.`)) + Expect(session).To(Say(`--version\s+Roll back to the specified revision`)) Expect(session).To(Say("SEE ALSO:")) Expect(session).To(Say("revisions")) })