Skip to content

Commit 22152cc

Browse files
[V8] RFC-0045: Stack Management Reason (#3718)
* Add state field to cf stacks command Signed-off-by: Simon Jones <simonjones@vmware.com> * Remove state validation from cf state command Signed-off-by: Simon Jones <simonjones@vmware.com> * first pass of update-stack command Signed-off-by: Simon Jones <simonjones@vmware.com> * Include reference to state in help text for cf stack & stacks Signed-off-by: Simon Jones <simonjones@vmware.com> * Add update stack command integration tests Signed-off-by: Simon Jones <simonjones@vmware.com> * Stack related fakes generated correctly by counterfeiter Signed-off-by: Simon Jones <simonjones@vmware.com> * Add update-stack to help categories in APPS section * Add parentheses and spaces to update-stack usage command Signed-off-by: Simon Jones <simonjones@vmware.com> * Add minimum API version check for update-stack command (3.210.0) * Add assertions for state output in stack command tests * Fix indentation in help_all_display.go APPS section Co-authored-by: Cursor <cursoragent@cursor.com> * Update stack and stacks integration test expectations for state support Co-authored-by: Cursor <cursoragent@cursor.com> * Update minimum API version for update-stack to 3.211.0 Co-authored-by: Cursor <cursoragent@cursor.com> * Add state_reason field to stack resource and display logic * Add --reason flag to update-stack command * Update --reason flag usage example with detailed migration message * Show reason field for non-active stack states and fix UpdateStack interface - Display reason: whenever stack state is non-ACTIVE, even if reason is empty - Update CloudControllerClient and Actor interfaces for 3-arg UpdateStack - Add StateReason field to Stack resource - Regenerate fakes for updated interfaces - Fix duplicate Execute call in update-stack reason test Co-authored-by: Cursor <cursoragent@cursor.com> * Add integration tests for stack reason display scenarios - update-stack: test empty reason for non-active state, test reason with --reason flag - stack: test no reason for active state, test empty reason for non-active state, test reason for non-active state with reason Co-authored-by: Cursor <cursoragent@cursor.com> * Regenerate fakeActor Signed-off-by: Simon Jones <simonjones@vmware.com> --------- Signed-off-by: Simon Jones <simonjones@vmware.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 56862a5 commit 22152cc

File tree

16 files changed

+271
-65
lines changed

16 files changed

+271
-65
lines changed

actor/v7action/cloud_controller_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ type CloudControllerClient interface {
138138
GetAppFeature(appGUID string, featureName string) (resources.ApplicationFeature, ccv3.Warnings, error)
139139
GetStacks(query ...ccv3.Query) ([]resources.Stack, ccv3.Warnings, error)
140140
GetStagingSecurityGroups(spaceGUID string, queries ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error)
141-
UpdateStack(stackGUID string, state string) (resources.Stack, ccv3.Warnings, error)
141+
UpdateStack(stackGUID string, state string, reason string) (resources.Stack, ccv3.Warnings, error)
142142
GetTask(guid string) (resources.Task, ccv3.Warnings, error)
143143
GetUser(userGUID string) (resources.User, ccv3.Warnings, error)
144144
GetUsers(query ...ccv3.Query) ([]resources.User, ccv3.Warnings, error)

actor/v7action/stack.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ func (actor Actor) GetStacks(labelSelector string) ([]resources.Stack, Warnings,
4747
return stacks, Warnings(warnings), nil
4848
}
4949

50-
func (actor Actor) UpdateStack(stackGUID string, state string) (resources.Stack, Warnings, error) {
51-
stack, warnings, err := actor.CloudControllerClient.UpdateStack(stackGUID, state)
50+
func (actor Actor) UpdateStack(stackGUID string, state string, reason string) (resources.Stack, Warnings, error) {
51+
stack, warnings, err := actor.CloudControllerClient.UpdateStack(stackGUID, state, reason)
5252
if err != nil {
5353
return resources.Stack{}, Warnings(warnings), err
5454
}

actor/v7action/stack_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ var _ = Describe("Stack", func() {
238238
var (
239239
stackGUID string
240240
state string
241+
reason string
241242
stack resources.Stack
242243
warnings Warnings
243244
executeErr error
@@ -246,10 +247,11 @@ var _ = Describe("Stack", func() {
246247
BeforeEach(func() {
247248
stackGUID = "some-stack-guid"
248249
state = "DEPRECATED"
250+
reason = ""
249251
})
250252

251253
JustBeforeEach(func() {
252-
stack, warnings, executeErr = actor.UpdateStack(stackGUID, state)
254+
stack, warnings, executeErr = actor.UpdateStack(stackGUID, state, reason)
253255
})
254256

255257
When("the cloud controller request is successful", func() {
@@ -277,9 +279,10 @@ var _ = Describe("Stack", func() {
277279
}))
278280

279281
Expect(fakeCloudControllerClient.UpdateStackCallCount()).To(Equal(1))
280-
actualGUID, actualState := fakeCloudControllerClient.UpdateStackArgsForCall(0)
282+
actualGUID, actualState, actualReason := fakeCloudControllerClient.UpdateStackArgsForCall(0)
281283
Expect(actualGUID).To(Equal(stackGUID))
282284
Expect(actualState).To(Equal(state))
285+
Expect(actualReason).To(Equal(reason))
283286
})
284287
})
285288

actor/v7action/v7actionfakes/fake_cloud_controller_client.go

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/cloudcontroller/ccv3/stack.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ func (client *Client) GetStacks(query ...Query) ([]resources.Stack, Warnings, er
2222
return stacks, warnings, err
2323
}
2424

25-
// UpdateStack updates a stack's state.
26-
func (client *Client) UpdateStack(stackGUID string, state string) (resources.Stack, Warnings, error) {
25+
// UpdateStack updates a stack's state and optionally its state reason.
26+
func (client *Client) UpdateStack(stackGUID string, state string, reason string) (resources.Stack, Warnings, error) {
2727
var responseStack resources.Stack
2828

2929
type StackUpdate struct {
30-
State string `json:"state"`
30+
State string `json:"state"`
31+
StateReason string `json:"state_reason,omitempty"`
3132
}
3233

3334
_, warnings, err := client.MakeRequest(RequestParams{
3435
RequestName: internal.PatchStackRequest,
3536
URIParams: internal.Params{"stack_guid": stackGUID},
36-
RequestBody: StackUpdate{State: state},
37+
RequestBody: StackUpdate{State: state, StateReason: reason},
3738
ResponseBody: &responseStack,
3839
})
3940

api/cloudcontroller/ccv3/stack_test.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ var _ = Describe("Stacks", func() {
148148
var (
149149
stackGUID string
150150
state string
151+
reason string
151152
stack resources.Stack
152153
warnings Warnings
153154
err error
@@ -156,10 +157,11 @@ var _ = Describe("Stacks", func() {
156157
BeforeEach(func() {
157158
stackGUID = "some-stack-guid"
158159
state = "DEPRECATED"
160+
reason = ""
159161
})
160162

161163
JustBeforeEach(func() {
162-
stack, warnings, err = client.UpdateStack(stackGUID, state)
164+
stack, warnings, err = client.UpdateStack(stackGUID, state, reason)
163165
})
164166

165167
When("the request succeeds", func() {
@@ -192,6 +194,40 @@ var _ = Describe("Stacks", func() {
192194
})
193195
})
194196

197+
When("a reason is provided", func() {
198+
BeforeEach(func() {
199+
reason = "Use cflinuxfs4 instead"
200+
server.AppendHandlers(
201+
CombineHandlers(
202+
VerifyRequest(http.MethodPatch, "/v3/stacks/some-stack-guid"),
203+
VerifyJSONRepresenting(map[string]string{
204+
"state": "DEPRECATED",
205+
"state_reason": "Use cflinuxfs4 instead",
206+
}),
207+
RespondWith(http.StatusOK, `{
208+
"guid": "some-stack-guid",
209+
"name": "some-stack",
210+
"description": "some description",
211+
"state": "DEPRECATED",
212+
"state_reason": "Use cflinuxfs4 instead"
213+
}`, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
214+
),
215+
)
216+
})
217+
218+
It("returns the updated stack with reason and warnings", func() {
219+
Expect(err).ToNot(HaveOccurred())
220+
Expect(warnings).To(ConsistOf("this is a warning"))
221+
Expect(stack).To(Equal(resources.Stack{
222+
GUID: "some-stack-guid",
223+
Name: "some-stack",
224+
Description: "some description",
225+
State: "DEPRECATED",
226+
StateReason: "Use cflinuxfs4 instead",
227+
}))
228+
})
229+
})
230+
195231
When("the cloud controller returns an error", func() {
196232
BeforeEach(func() {
197233
server.AppendHandlers(

api/cloudcontroller/ccversion/minimum_version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ const (
2525

2626
MinVersionServiceBindingStrategy = "3.205.0"
2727

28-
MinVersionUpdateStack = "3.210.0"
28+
MinVersionUpdateStack = "3.211.0"
2929
)

command/v7/actor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ type Actor interface {
179179
GetStackByName(stackName string) (resources.Stack, v7action.Warnings, error)
180180
GetStackLabels(stackName string) (map[string]types.NullString, v7action.Warnings, error)
181181
GetStacks(string) ([]resources.Stack, v7action.Warnings, error)
182-
UpdateStack(stackGUID string, state string) (resources.Stack, v7action.Warnings, error)
182+
UpdateStack(stackGUID string, state string, reason string) (resources.Stack, v7action.Warnings, error)
183183
GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client sharedaction.LogCacheClient) (<-chan sharedaction.LogMessage, <-chan error, context.CancelFunc, v7action.Warnings, error)
184184
GetTaskBySequenceIDAndApplication(sequenceID int, appGUID string) (resources.Task, v7action.Warnings, error)
185185
GetUAAAPIVersion() (string, error)

command/v7/stack_command.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ func (cmd *StackCommand) displayStackInfo() error {
6969
// Add state only if it's present
7070
if stack.State != "" {
7171
displayTable = append(displayTable, []string{cmd.UI.TranslateText("state:"), stack.State})
72+
73+
// Add reason whenever state is not ACTIVE
74+
if stack.State != resources.StackStateActive {
75+
displayTable = append(displayTable, []string{cmd.UI.TranslateText("reason:"), stack.StateReason})
76+
}
7277
}
7378

7479
cmd.UI.DisplayKeyValueTable("", displayTable, 3)

command/v7/stack_command_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ var _ = Describe("Stack Command", func() {
148148
})
149149
})
150150

151-
Context("When the stack has a state", func() {
151+
Context("When the stack has a state", func() {
152+
Context("When the state is ACTIVE", func() {
152153
BeforeEach(func() {
153154
stack := resources.Stack{
154155
Name: "some-stack-name",
@@ -159,17 +160,66 @@ var _ = Describe("Stack Command", func() {
159160
fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil)
160161
})
161162

162-
It("Displays the stack information with state", func() {
163+
It("Displays the stack information with state but no reason", func() {
163164
Expect(executeErr).ToNot(HaveOccurred())
164165
Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name"))
165166
Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1))
166167

167168
Expect(testUI.Out).To(Say("name:\\s+some-stack-name"))
168169
Expect(testUI.Out).To(Say("description:\\s+some-stack-desc"))
169170
Expect(testUI.Out).To(Say("state:\\s+ACTIVE"))
171+
Expect(testUI.Out).NotTo(Say("reason:"))
170172
})
171173
})
172174

175+
Context("When the state is not ACTIVE and has a reason", func() {
176+
BeforeEach(func() {
177+
stack := resources.Stack{
178+
Name: "some-stack-name",
179+
GUID: "some-stack-guid",
180+
Description: "some-stack-desc",
181+
State: "DEPRECATED",
182+
StateReason: "This stack is being phased out",
183+
}
184+
fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil)
185+
})
186+
187+
It("Displays the stack information with state and reason", func() {
188+
Expect(executeErr).ToNot(HaveOccurred())
189+
Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name"))
190+
Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1))
191+
192+
Expect(testUI.Out).To(Say("name:\\s+some-stack-name"))
193+
Expect(testUI.Out).To(Say("description:\\s+some-stack-desc"))
194+
Expect(testUI.Out).To(Say("state:\\s+DEPRECATED"))
195+
Expect(testUI.Out).To(Say("reason:\\s+This stack is being phased out"))
196+
})
197+
})
198+
199+
Context("When the state is not ACTIVE but has no reason", func() {
200+
BeforeEach(func() {
201+
stack := resources.Stack{
202+
Name: "some-stack-name",
203+
GUID: "some-stack-guid",
204+
Description: "some-stack-desc",
205+
State: "RESTRICTED",
206+
}
207+
fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil)
208+
})
209+
210+
It("Displays the stack information with state and empty reason", func() {
211+
Expect(executeErr).ToNot(HaveOccurred())
212+
Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name"))
213+
Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1))
214+
215+
Expect(testUI.Out).To(Say("name:\\s+some-stack-name"))
216+
Expect(testUI.Out).To(Say("description:\\s+some-stack-desc"))
217+
Expect(testUI.Out).To(Say("state:\\s+RESTRICTED"))
218+
Expect(testUI.Out).To(Say("reason:"))
219+
})
220+
})
221+
})
222+
173223
When("The Stack does not Exist", func() {
174224
expectedError := actionerror.StackNotFoundError{Name: "some-stack-name"}
175225
BeforeEach(func() {

0 commit comments

Comments
 (0)