Skip to content

Commit 92fed62

Browse files
committed
Add flag --instance-steps to restage command
Signed-off-by: João Pereira <[email protected]>
1 parent 9246cb4 commit 92fed62

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

command/v7/restage_command.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package v7
22

33
import (
4+
"strconv"
5+
"strings"
6+
47
"code.cloudfoundry.org/cli/actor/actionerror"
58
"code.cloudfoundry.org/cli/actor/v7action"
69
"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
@@ -9,15 +12,17 @@ import (
912
"code.cloudfoundry.org/cli/command/flag"
1013
"code.cloudfoundry.org/cli/command/translatableerror"
1114
"code.cloudfoundry.org/cli/command/v7/shared"
15+
"code.cloudfoundry.org/cli/resources"
1216
)
1317

1418
type RestageCommand struct {
1519
BaseCommand
1620

1721
RequiredArgs flag.AppName `positional-args:"yes"`
18-
Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."`
22+
InstanceSteps string `long:"instance-steps" description:"An array of percentage steps to deploy when using deployment strategy canary. (e.g. 20,40,60)"`
1923
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."`
2024
NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"`
25+
Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."`
2126
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"`
2227
relatedCommands interface{} `related_commands:"restart"`
2328
envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"`
@@ -93,6 +98,18 @@ func (cmd RestageCommand) Execute(args []string) error {
9398
opts.MaxInFlight = *cmd.MaxInFlight
9499
}
95100

101+
if cmd.InstanceSteps != "" {
102+
if len(cmd.InstanceSteps) > 0 {
103+
for _, v := range strings.Split(cmd.InstanceSteps, ",") {
104+
parsedInt, err := strconv.ParseInt(v, 0, 64)
105+
if err != nil {
106+
return err
107+
}
108+
opts.CanarySteps = append(opts.CanarySteps, resources.CanaryStep{InstanceWeight: parsedInt})
109+
}
110+
}
111+
}
112+
96113
err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), pkg.GUID, opts)
97114
if err != nil {
98115
return mapErr(cmd.Config, cmd.RequiredArgs.AppName, err)
@@ -107,6 +124,10 @@ func (cmd RestageCommand) ValidateFlags() error {
107124
return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"}
108125
case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1:
109126
return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"}
127+
case cmd.Strategy.Name != constant.DeploymentStrategyCanary && cmd.InstanceSteps != "":
128+
return translatableerror.RequiredFlagsError{Arg1: "--instance-steps", Arg2: "--strategy=canary"}
129+
case len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps):
130+
return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"}
110131
}
111132

112133
return nil

command/v7/restage_command_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,27 @@ var _ = Describe("restage Command", func() {
120120
})
121121
})
122122

123+
When("canary strategy is provided", func() {
124+
BeforeEach(func() {
125+
cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary}
126+
cmd.InstanceSteps = "1,2,4"
127+
})
128+
129+
It("starts the app with the current droplet", func() {
130+
Expect(executeErr).ToNot(HaveOccurred())
131+
Expect(fakeAppStager.StageAndStartCallCount()).To(Equal(1))
132+
133+
inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StageAndStartArgsForCall(0)
134+
Expect(inputApp).To(Equal(app))
135+
Expect(inputDropletGuid).To(Equal(""))
136+
Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace()))
137+
Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization()))
138+
Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary))
139+
Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting))
140+
Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}}))
141+
})
142+
})
143+
123144
It("displays that it's restaging", func() {
124145
Expect(testUI.Out).To(Say("Restaging app some-app in org some-org / space some-space as steve..."))
125146
})
@@ -226,5 +247,34 @@ var _ = Describe("restage Command", func() {
226247
translatableerror.IncorrectUsageError{
227248
Message: "--max-in-flight must be greater than or equal to 1",
228249
}),
250+
251+
Entry("instance-steps provided with rolling deployment",
252+
func() {
253+
cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}
254+
cmd.InstanceSteps = "1,2,3"
255+
},
256+
translatableerror.RequiredFlagsError{
257+
Arg1: "--instance-steps",
258+
Arg2: "--strategy=canary",
259+
}),
260+
261+
Entry("instance-steps no strategy provided",
262+
func() {
263+
cmd.InstanceSteps = "1,2,3"
264+
},
265+
translatableerror.RequiredFlagsError{
266+
Arg1: "--instance-steps",
267+
Arg2: "--strategy=canary",
268+
}),
269+
270+
Entry("instance-steps a valid list of ints",
271+
func() {
272+
cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary}
273+
cmd.InstanceSteps = "some,thing,not,right"
274+
},
275+
translatableerror.ParseArgumentError{
276+
ArgumentName: "--instance-steps",
277+
ExpectedType: "list of weights",
278+
}),
229279
)
230280
})

integration/v7/isolated/restage_command_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ var _ = Describe("restage command", func() {
3131
Eventually(session).Should(Say("ALIAS:"))
3232
Eventually(session).Should(Say("rg"))
3333
Eventually(session).Should(Say("OPTIONS:"))
34-
Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`))
34+
Eventually(session).Should(Say("--instance-steps"))
3535
Eventually(session).Should(Say("--max-in-flight"))
3636
Eventually(session).Should(Say(`--no-wait\s+Exit when the first instance of the web process is healthy`))
37+
Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`))
3738
Eventually(session).Should(Say("ENVIRONMENT:"))
3839
Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`))
3940
Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`))
@@ -246,10 +247,10 @@ applications:
246247
})
247248
})
248249

249-
When("strategy canary is given with a non-default max-in-flight value", func() {
250+
When("strategy canary is given with a non-default max-in-flight value and instance-weights", func() {
250251
It("restages successfully and notes the max-in-flight value", func() {
251252
userName, _ := helpers.GetCredentials()
252-
session := helpers.CF("restage", appName, "--strategy", "canary", "--max-in-flight", "2")
253+
session := helpers.CF("restage", appName, "--strategy", "canary", "--max-in-flight", "2", "--instance-weights", "1,20")
253254
Consistently(session.Err).ShouldNot(Say(`This action will cause app downtime\.`))
254255
Eventually(session).Should(Say(`Restaging app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
255256
Eventually(session).Should(Say(`Creating deployment for app %s\.\.\.`, appName))

0 commit comments

Comments
 (0)