Skip to content

Commit 912fc63

Browse files
committed
Add --instance-steps to rollback
Signed-off-by: João Pereira <[email protected]>
1 parent 0c614f6 commit 912fc63

File tree

3 files changed

+82
-12
lines changed

3 files changed

+82
-12
lines changed

command/v7/rollback_command.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package v7
22

33
import (
44
"fmt"
5+
"strconv"
6+
"strings"
57

68
"code.cloudfoundry.org/cli/actor/sharedaction"
79
"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
@@ -10,13 +12,15 @@ import (
1012
"code.cloudfoundry.org/cli/command/flag"
1113
"code.cloudfoundry.org/cli/command/translatableerror"
1214
"code.cloudfoundry.org/cli/command/v7/shared"
15+
"code.cloudfoundry.org/cli/resources"
1316
)
1417

1518
type RollbackCommand struct {
1619
BaseCommand
1720

18-
Force bool `short:"f" description:"Force rollback without confirmation"`
1921
RequiredArgs flag.AppName `positional-args:"yes"`
22+
Force bool `short:"f" description:"Force rollback without confirmation"`
23+
InstanceSteps string `long:"instance-steps" description:"An array of percentage steps to deploy when using deployment strategy canary. (e.g. 20,40,60)"`
2024
MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being rolled back."`
2125
Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary or rolling. When not specified, it defaults to rolling."`
2226
Version flag.Revision `long:"version" required:"true" description:"Roll back to the specified revision"`
@@ -120,6 +124,18 @@ func (cmd RollbackCommand) Execute(args []string) error {
120124
opts.Strategy = cmd.Strategy.Name
121125
}
122126

127+
if cmd.InstanceSteps != "" {
128+
if len(cmd.InstanceSteps) > 0 {
129+
for _, v := range strings.Split(cmd.InstanceSteps, ",") {
130+
parsedInt, err := strconv.ParseInt(v, 0, 64)
131+
if err != nil {
132+
return err
133+
}
134+
opts.CanarySteps = append(opts.CanarySteps, resources.CanaryStep{InstanceWeight: parsedInt})
135+
}
136+
}
137+
}
138+
123139
startAppErr := cmd.Stager.StartApp(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), revision.GUID, opts)
124140
if startAppErr != nil {
125141
return startAppErr
@@ -134,6 +150,10 @@ func (cmd RollbackCommand) ValidateFlags() error {
134150
switch {
135151
case cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1:
136152
return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"}
153+
case cmd.Strategy.Name != constant.DeploymentStrategyCanary && cmd.InstanceSteps != "":
154+
return translatableerror.RequiredFlagsError{Arg1: "--instance-steps", Arg2: "--strategy=canary"}
155+
case len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps):
156+
return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"}
137157
}
138158

139159
return nil

command/v7/rollback_command_test.go

+59-10
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,22 @@ import (
2323

2424
var _ = Describe("rollback Command", func() {
2525
var (
26-
app string
26+
appName string
2727
binaryName string
2828
executeErr error
2929
fakeActor *v7fakes.FakeActor
3030
fakeConfig *commandfakes.FakeConfig
3131
fakeSharedActor *commandfakes.FakeSharedActor
3232
input *Buffer
3333
testUI *ui.UI
34+
app resources.Application
3435

3536
fakeAppStager *sharedfakes.FakeAppStager
3637
cmd v7.RollbackCommand
3738
)
3839

3940
BeforeEach(func() {
40-
app = "some-app"
41+
appName = "some-app"
4142
binaryName = "faceman"
4243
fakeActor = new(v7fakes.FakeActor)
4344
fakeAppStager = new(sharedfakes.FakeAppStager)
@@ -50,6 +51,10 @@ var _ = Describe("rollback Command", func() {
5051
resources.Revision{Version: 2},
5152
resources.Revision{Version: 1},
5253
}
54+
app = resources.Application{
55+
GUID: "123",
56+
Name: "some-app",
57+
}
5358

5459
fakeActor.GetRevisionsByApplicationNameAndSpaceReturns(
5560
revisions, v7action.Warnings{"warning-2"}, nil,
@@ -68,7 +73,7 @@ var _ = Describe("rollback Command", func() {
6873
})
6974

7075
cmd = v7.RollbackCommand{
71-
RequiredArgs: flag.AppName{AppName: app},
76+
RequiredArgs: flag.AppName{AppName: appName},
7277
BaseCommand: v7.BaseCommand{
7378
UI: testUI,
7479
Config: fakeConfig,
@@ -150,7 +155,7 @@ var _ = Describe("rollback Command", func() {
150155
When("the app has at least one revision", func() {
151156
BeforeEach(func() {
152157
fakeActor.GetApplicationByNameAndSpaceReturns(
153-
resources.Application{GUID: "123"},
158+
app,
154159
v7action.Warnings{"app-warning-1"},
155160
nil,
156161
)
@@ -169,7 +174,7 @@ var _ = Describe("rollback Command", func() {
169174
It("fetches the app and revision revision", func() {
170175
Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1), "GetApplicationByNameAndSpace call count")
171176
appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0)
172-
Expect(appName).To(Equal(app))
177+
Expect(appName).To(Equal(appName))
173178
Expect(spaceGUID).To(Equal("some-space-guid"))
174179

175180
Expect(fakeActor.GetRevisionByApplicationAndVersionCallCount()).To(Equal(1), "GetRevisionByApplicationAndVersion call count")
@@ -192,7 +197,7 @@ var _ = Describe("rollback Command", func() {
192197
Expect(revisionGUID).To(Equal("some-1-guid"))
193198
Expect(opts.AppAction).To(Equal(constant.ApplicationRollingBack))
194199

195-
Expect(testUI.Out).ToNot(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision '3' will use the settings from revision '1'.", app))
200+
Expect(testUI.Out).ToNot(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision '3' will use the settings from revision '1'.", appName))
196201
Expect(testUI.Out).ToNot(Say("Are you sure you want to continue?"))
197202

198203
Expect(testUI.Out).To(Say("Rolling back to revision 1 for app some-app in org some-org / space some-space as steve..."))
@@ -221,7 +226,7 @@ var _ = Describe("rollback Command", func() {
221226
Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyRolling))
222227
Expect(opts.MaxInFlight).To(Equal(5))
223228

224-
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))
229+
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'.", appName))
225230
Expect(testUI.Out).To(Say("Are you sure you want to continue?"))
226231
Expect(testUI.Out).To(Say("Rolling back to revision 1 for app some-app in org some-org / space some-space as steve..."))
227232

@@ -252,6 +257,31 @@ var _ = Describe("rollback Command", func() {
252257
})
253258
})
254259

260+
When("canary strategy is provided", func() {
261+
BeforeEach(func() {
262+
cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary}
263+
cmd.InstanceSteps = "1,2,4"
264+
265+
_, err := input.Write([]byte("y\n"))
266+
Expect(err).NotTo(HaveOccurred())
267+
})
268+
269+
It("starts the app with the current droplet", func() {
270+
271+
Expect(executeErr).ToNot(HaveOccurred())
272+
Expect(fakeAppStager.StartAppCallCount()).To(Equal(1))
273+
274+
inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StartAppArgsForCall(0)
275+
Expect(inputApp).To(Equal(app))
276+
Expect(inputDropletGuid).To(Equal("some-1-guid"))
277+
Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace()))
278+
Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization()))
279+
Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary))
280+
Expect(opts.AppAction).To(Equal(constant.ApplicationRollingBack))
281+
Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}}))
282+
})
283+
})
284+
255285
When("user says no to prompt", func() {
256286
BeforeEach(func() {
257287
_, err := input.Write([]byte("n\n"))
@@ -261,8 +291,8 @@ var _ = Describe("rollback Command", func() {
261291
It("does not execute the command and outputs warnings", func() {
262292
Expect(fakeAppStager.StartAppCallCount()).To(Equal(0), "GetStartApp call count")
263293

264-
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))
265-
Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", app))
294+
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'.", appName))
295+
Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", appName))
266296

267297
Expect(testUI.Err).To(Say("app-warning-1"))
268298
Expect(testUI.Err).To(Say("revision-warning-3"))
@@ -280,7 +310,7 @@ var _ = Describe("rollback Command", func() {
280310

281311
Expect(fakeAppStager.StartAppCallCount()).To(Equal(0), "GetStartApp call count")
282312

283-
Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", app))
313+
Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", appName))
284314
})
285315
})
286316
})
@@ -305,5 +335,24 @@ var _ = Describe("rollback Command", func() {
305335
translatableerror.IncorrectUsageError{
306336
Message: "--max-in-flight must be greater than or equal to 1",
307337
}),
338+
339+
Entry("instance-steps no strategy provided",
340+
func() {
341+
cmd.InstanceSteps = "1,2,3"
342+
},
343+
translatableerror.RequiredFlagsError{
344+
Arg1: "--instance-steps",
345+
Arg2: "--strategy=canary",
346+
}),
347+
348+
Entry("instance-steps a valid list of ints",
349+
func() {
350+
cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary}
351+
cmd.InstanceSteps = "some,thing,not,right"
352+
},
353+
translatableerror.ParseArgumentError{
354+
ArgumentName: "--instance-steps",
355+
ExpectedType: "list of weights",
356+
}),
308357
)
309358
})

integration/v7/isolated/rollback_command_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var _ = Describe("rollback command", func() {
3535
Expect(session).To(Say(`cf rollback APP_NAME \[--version VERSION\]`))
3636
Expect(session).To(Say("OPTIONS:"))
3737
Expect(session).To(Say(`-f\s+Force rollback without confirmation`))
38+
Expect(session).To(Say("--instance-steps"))
3839
Expect(session).To(Say("--max-in-flight"))
3940
Expect(session).To(Say(`--strategy\s+Deployment strategy can be canary or rolling. When not specified, it defaults to rolling.`))
4041
Expect(session).To(Say(`--version\s+Roll back to the specified revision`))
@@ -158,7 +159,7 @@ applications:
158159
})
159160

160161
It("uses the given strategy to rollback and notes the max-in-flight value", func() {
161-
session := helpers.CFWithStdin(buffer, "rollback", appName, "--version", "1", "--strategy", "canary")
162+
session := helpers.CFWithStdin(buffer, "rollback", appName, "--version", "1", "--strategy", "canary", "--instance-steps", "10,50")
162163
Eventually(session).Should(Exit(0))
163164

164165
Expect(session).To(HaveRollbackPrompt())

0 commit comments

Comments
 (0)