From 8b5d8484cd4fbe9674197a65b571481296fc082d Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Mon, 1 Dec 2025 14:38:03 -0500 Subject: [PATCH 01/20] Add state field to cf stacks command Signed-off-by: Simon Jones --- actor/v7action/stack_test.go | 67 +-------------- command/v7/stack_command.go | 22 ++++- command/v7/stack_command_test.go | 25 ++++-- integration/v7/isolated/stack_command_test.go | 83 +++++++++++++------ 4 files changed, 93 insertions(+), 104 deletions(-) diff --git a/actor/v7action/stack_test.go b/actor/v7action/stack_test.go index c2d11f880a..666cc31d52 100644 --- a/actor/v7action/stack_test.go +++ b/actor/v7action/stack_test.go @@ -134,7 +134,7 @@ var _ = Describe("Stack", func() { Expect(stack.GUID).To(Equal(expectedStack.GUID)) Expect(stack.Name).To(Equal(expectedStack.Name)) Expect(stack.Description).To(Equal(expectedStack.Description)) - Expect(stack.State).To(Equal(resources.StackStateActive)) + Expect(stack.State).To(Equal("ACTIVE")) Expect(err).To(BeNil()) Expect(warnings).To(ConsistOf("warning-1", "warning-2")) }) @@ -233,69 +233,4 @@ var _ = Describe("Stack", func() { }) }) }) - - Describe("UpdateStack", func() { - var ( - stackGUID string - state string - stack resources.Stack - warnings Warnings - executeErr error - ) - - BeforeEach(func() { - stackGUID = "some-stack-guid" - state = "DEPRECATED" - }) - - JustBeforeEach(func() { - stack, warnings, executeErr = actor.UpdateStack(stackGUID, state) - }) - - When("the cloud controller request is successful", func() { - BeforeEach(func() { - fakeCloudControllerClient.UpdateStackReturns( - resources.Stack{ - GUID: "some-stack-guid", - Name: "some-stack", - Description: "some description", - State: "DEPRECATED", - }, - ccv3.Warnings{"warning-1", "warning-2"}, - nil, - ) - }) - - It("returns the updated stack and warnings", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(warnings).To(ConsistOf("warning-1", "warning-2")) - Expect(stack).To(Equal(resources.Stack{ - GUID: "some-stack-guid", - Name: "some-stack", - Description: "some description", - State: "DEPRECATED", - })) - - Expect(fakeCloudControllerClient.UpdateStackCallCount()).To(Equal(1)) - actualGUID, actualState := fakeCloudControllerClient.UpdateStackArgsForCall(0) - Expect(actualGUID).To(Equal(stackGUID)) - Expect(actualState).To(Equal(state)) - }) - }) - - When("the cloud controller request fails", func() { - BeforeEach(func() { - fakeCloudControllerClient.UpdateStackReturns( - resources.Stack{}, - ccv3.Warnings{"warning-1"}, - errors.New("some-error"), - ) - }) - - It("returns the error and warnings", func() { - Expect(executeErr).To(MatchError("some-error")) - Expect(warnings).To(ConsistOf("warning-1")) - }) - }) - }) }) diff --git a/command/v7/stack_command.go b/command/v7/stack_command.go index ff9f57f742..7e8f018dff 100644 --- a/command/v7/stack_command.go +++ b/command/v7/stack_command.go @@ -1,8 +1,9 @@ package v7 import ( - "code.cloudfoundry.org/cli/v9/command/flag" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v8/command/flag" + "code.cloudfoundry.org/cli/v8/command/translatableerror" + "code.cloudfoundry.org/cli/v8/resources" ) type StackCommand struct { @@ -11,7 +12,7 @@ type StackCommand struct { RequiredArgs flag.StackName `positional-args:"yes"` GUID bool `long:"guid" description:"Retrieve and display the given stack's guid. All other output for the stack is suppressed."` usage interface{} `usage:"CF_NAME stack STACK_NAME"` - relatedCommands interface{} `related_commands:"app, push, stacks, update-stack"` + relatedCommands interface{} `related_commands:"app, push, stacks"` } func (cmd *StackCommand) Execute(args []string) error { @@ -60,6 +61,21 @@ func (cmd *StackCommand) displayStackInfo() error { return err } + // Validate state if present + if stack.State != "" { + validStates := []string{"ACTIVE", "RESTRICTED", "DEPRECATED", "DISABLED"} + isValid := false + for _, validState := range validStates { + if stack.State == validState { + isValid = true + break + } + } + if !isValid { + return translatableerror.InvalidStackStateError{State: stack.State} + } + } + // Build display table displayTable := [][]string{ {cmd.UI.TranslateText("name:"), stack.Name}, diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 212078fda6..57fe5f5ac3 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -141,14 +141,10 @@ var _ = Describe("Stack Command", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).NotTo(Say("state:")) }) }) - Context("When the stack has a state", func() { + Context("When the stack has a valid state", func() { BeforeEach(func() { stack := resources.Stack{ Name: "some-stack-name", @@ -163,10 +159,23 @@ var _ = Describe("Stack Command", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + }) + }) - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).To(Say("state:\\s+ACTIVE")) + Context("When the stack has an invalid state", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "INVALID_STATE", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) + + It("returns an error", func() { + Expect(executeErr).To(HaveOccurred()) + Expect(executeErr.Error()).To(ContainSubstring("Invalid stack state")) }) }) diff --git a/integration/v7/isolated/stack_command_test.go b/integration/v7/isolated/stack_command_test.go index 4a0abc6302..d007fdeb10 100644 --- a/integration/v7/isolated/stack_command_test.go +++ b/integration/v7/isolated/stack_command_test.go @@ -33,7 +33,7 @@ var _ = Describe("stack command", func() { It("appears in cf help -a", func() { session := helpers.CF("help", "-a") Eventually(session).Should(Exit(0)) - Expect(session).To(HaveCommandInCategoryWithDescription("stack", "APPS", "Show information for a stack (a stack is a pre-built file system, including an operating system, that can run apps) and current state")) + Expect(session).To(HaveCommandInCategoryWithDescription("stack", "APPS", "Show information for a stack (a stack is a pre-built file system, including an operating system, that can run apps)")) }) It("Displays command usage to output", func() { @@ -100,42 +100,71 @@ var _ = Describe("stack command", func() { }) }) - When("the stack exists with valid state", func() { + When("the stack exists", func() { var stackGUID string - BeforeEach(func() { - jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s", "state": "ACTIVE"}`, stackName, stackDescription) - session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks") - Eventually(session).Should(Exit(0)) + Context("when the stack has no state", func() { + BeforeEach(func() { + jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s"}`, stackName, stackDescription) + session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks") + Eventually(session).Should(Exit(0)) - r := regexp.MustCompile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`) - stackGUID = string(r.Find(session.Out.Contents())) - }) + r := regexp.MustCompile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`) + stackGUID = string(r.Find(session.Out.Contents())) + }) - AfterEach(func() { - session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) - Eventually(session).Should(Exit(0)) + AfterEach(func() { + session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) + Eventually(session).Should(Exit(0)) + }) + + It("Shows the details for the stack without state", func() { + session := helpers.CF("stack", stackName) + + Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) + Consistently(session).ShouldNot(Say(`state:`)) + Eventually(session).Should(Exit(0)) + }) }) - It("Shows the details for the stack with state", func() { - session := helpers.CF("stack", stackName) + Context("when the stack has a valid state", func() { + BeforeEach(func() { + jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s", "state": "ACTIVE"}`, stackName, stackDescription) + session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks") + Eventually(session).Should(Exit(0)) - Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) - Eventually(session).Should(Say(`name:\s+%s`, stackName)) - Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) - Eventually(session).Should(Say(`state:\s+ACTIVE`)) - Eventually(session).Should(Exit(0)) + r := regexp.MustCompile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`) + stackGUID = string(r.Find(session.Out.Contents())) + }) + + AfterEach(func() { + session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) + Eventually(session).Should(Exit(0)) + }) + + It("Shows the details for the stack with state", func() { + session := helpers.CF("stack", stackName) + + Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) + Eventually(session).Should(Say(`state:\s+ACTIVE`)) + Eventually(session).Should(Exit(0)) + }) }) - It("prints nothing but the guid when --guid flag is passed", func() { - session := helpers.CF("stack", stackName, "--guid") + When("the stack exists and the --guid flag is passed", func() { + It("prints nothing but the guid", func() { + session := helpers.CF("stack", stackName, "--guid") - Consistently(session).ShouldNot(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) - Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName)) - Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription)) - Consistently(session).ShouldNot(Say(`state:`)) - Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`)) - Eventually(session).Should(Exit(0)) + Consistently(session).ShouldNot(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName)) + Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription)) + Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`)) + Eventually(session).Should(Exit(0)) + }) }) }) }) From 50be8a9229cc0f873e31d783e62c17f0441f819f Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Tue, 2 Dec 2025 11:46:22 -0500 Subject: [PATCH 02/20] Remove state validation from cf state command Signed-off-by: Simon Jones --- command/v7/stack_command.go | 16 ---------------- command/v7/stack_command_test.go | 19 +------------------ 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/command/v7/stack_command.go b/command/v7/stack_command.go index 7e8f018dff..910902c61a 100644 --- a/command/v7/stack_command.go +++ b/command/v7/stack_command.go @@ -2,7 +2,6 @@ package v7 import ( "code.cloudfoundry.org/cli/v8/command/flag" - "code.cloudfoundry.org/cli/v8/command/translatableerror" "code.cloudfoundry.org/cli/v8/resources" ) @@ -61,21 +60,6 @@ func (cmd *StackCommand) displayStackInfo() error { return err } - // Validate state if present - if stack.State != "" { - validStates := []string{"ACTIVE", "RESTRICTED", "DEPRECATED", "DISABLED"} - isValid := false - for _, validState := range validStates { - if stack.State == validState { - isValid = true - break - } - } - if !isValid { - return translatableerror.InvalidStackStateError{State: stack.State} - } - } - // Build display table displayTable := [][]string{ {cmd.UI.TranslateText("name:"), stack.Name}, diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 57fe5f5ac3..953bf12519 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -144,7 +144,7 @@ var _ = Describe("Stack Command", func() { }) }) - Context("When the stack has a valid state", func() { + Context("When the stack has a state", func() { BeforeEach(func() { stack := resources.Stack{ Name: "some-stack-name", @@ -162,23 +162,6 @@ var _ = Describe("Stack Command", func() { }) }) - Context("When the stack has an invalid state", func() { - BeforeEach(func() { - stack := resources.Stack{ - Name: "some-stack-name", - GUID: "some-stack-guid", - Description: "some-stack-desc", - State: "INVALID_STATE", - } - fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) - }) - - It("returns an error", func() { - Expect(executeErr).To(HaveOccurred()) - Expect(executeErr.Error()).To(ContainSubstring("Invalid stack state")) - }) - }) - When("The Stack does not Exist", func() { expectedError := actionerror.StackNotFoundError{Name: "some-stack-name"} BeforeEach(func() { From 8a92ec2e7757e1718812f7f38ffe83cf2ee37192 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Mon, 8 Dec 2025 13:08:50 -0500 Subject: [PATCH 03/20] first pass of update-stack command Signed-off-by: Simon Jones --- actor/v7action/stack_test.go | 67 ++++++++++++++- .../fake_cloud_controller_client.go | 84 +++++++++++++++++++ api/cloudcontroller/ccv3/stack_test.go | 6 +- command/v7/stack_command.go | 2 +- command/v7/update_stack_command.go | 15 +--- command/v7/update_stack_command_test.go | 73 ++++++++-------- command/v7/v7fakes/fake_actor.go | 84 +++++++++++++++++++ resources/stack_resource.go | 5 ++ 8 files changed, 283 insertions(+), 53 deletions(-) diff --git a/actor/v7action/stack_test.go b/actor/v7action/stack_test.go index 666cc31d52..c2d11f880a 100644 --- a/actor/v7action/stack_test.go +++ b/actor/v7action/stack_test.go @@ -134,7 +134,7 @@ var _ = Describe("Stack", func() { Expect(stack.GUID).To(Equal(expectedStack.GUID)) Expect(stack.Name).To(Equal(expectedStack.Name)) Expect(stack.Description).To(Equal(expectedStack.Description)) - Expect(stack.State).To(Equal("ACTIVE")) + Expect(stack.State).To(Equal(resources.StackStateActive)) Expect(err).To(BeNil()) Expect(warnings).To(ConsistOf("warning-1", "warning-2")) }) @@ -233,4 +233,69 @@ var _ = Describe("Stack", func() { }) }) }) + + Describe("UpdateStack", func() { + var ( + stackGUID string + state string + stack resources.Stack + warnings Warnings + executeErr error + ) + + BeforeEach(func() { + stackGUID = "some-stack-guid" + state = "DEPRECATED" + }) + + JustBeforeEach(func() { + stack, warnings, executeErr = actor.UpdateStack(stackGUID, state) + }) + + When("the cloud controller request is successful", func() { + BeforeEach(func() { + fakeCloudControllerClient.UpdateStackReturns( + resources.Stack{ + GUID: "some-stack-guid", + Name: "some-stack", + Description: "some description", + State: "DEPRECATED", + }, + ccv3.Warnings{"warning-1", "warning-2"}, + nil, + ) + }) + + It("returns the updated stack and warnings", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf("warning-1", "warning-2")) + Expect(stack).To(Equal(resources.Stack{ + GUID: "some-stack-guid", + Name: "some-stack", + Description: "some description", + State: "DEPRECATED", + })) + + Expect(fakeCloudControllerClient.UpdateStackCallCount()).To(Equal(1)) + actualGUID, actualState := fakeCloudControllerClient.UpdateStackArgsForCall(0) + Expect(actualGUID).To(Equal(stackGUID)) + Expect(actualState).To(Equal(state)) + }) + }) + + When("the cloud controller request fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.UpdateStackReturns( + resources.Stack{}, + ccv3.Warnings{"warning-1"}, + errors.New("some-error"), + ) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError("some-error")) + Expect(warnings).To(ConsistOf("warning-1")) + }) + }) + }) }) diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 44eb02a3bb..6a00bcbb1d 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -1912,6 +1912,22 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + UpdateStackStub func(string, string) (resources.Stack, ccv3.Warnings, error) + updateStackMutex sync.RWMutex + updateStackArgsForCall []struct { + arg1 string + arg2 string + } + updateStackReturns struct { + result1 resources.Stack + result2 ccv3.Warnings + result3 error + } + updateStackReturnsOnCall map[int]struct { + result1 resources.Stack + result2 ccv3.Warnings + result3 error + } GetStagingSecurityGroupsStub func(string, ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error) getStagingSecurityGroupsMutex sync.RWMutex getStagingSecurityGroupsArgsForCall []struct { @@ -11214,6 +11230,74 @@ func (fake *FakeCloudControllerClient) GetStacksReturnsOnCall(i int, result1 []r }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) UpdateStack(arg1 string, arg2 string) (resources.Stack, ccv3.Warnings, error) { + fake.updateStackMutex.Lock() + ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] + fake.updateStackArgsForCall = append(fake.updateStackArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + stub := fake.UpdateStackStub + fakeReturns := fake.updateStackReturns + fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2}) + fake.updateStackMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeCloudControllerClient) UpdateStackCallCount() int { + fake.updateStackMutex.RLock() + defer fake.updateStackMutex.RUnlock() + return len(fake.updateStackArgsForCall) +} + +func (fake *FakeCloudControllerClient) UpdateStackCalls(stub func(string, string) (resources.Stack, ccv3.Warnings, error)) { + fake.updateStackMutex.Lock() + defer fake.updateStackMutex.Unlock() + fake.UpdateStackStub = stub +} + +func (fake *FakeCloudControllerClient) UpdateStackArgsForCall(i int) (string, string) { + fake.updateStackMutex.RLock() + defer fake.updateStackMutex.RUnlock() + argsForCall := fake.updateStackArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCloudControllerClient) UpdateStackReturns(result1 resources.Stack, result2 ccv3.Warnings, result3 error) { + fake.updateStackMutex.Lock() + defer fake.updateStackMutex.Unlock() + fake.UpdateStackStub = nil + fake.updateStackReturns = struct { + result1 resources.Stack + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeCloudControllerClient) UpdateStackReturnsOnCall(i int, result1 resources.Stack, result2 ccv3.Warnings, result3 error) { + fake.updateStackMutex.Lock() + defer fake.updateStackMutex.Unlock() + fake.UpdateStackStub = nil + if fake.updateStackReturnsOnCall == nil { + fake.updateStackReturnsOnCall = make(map[int]struct { + result1 resources.Stack + result2 ccv3.Warnings + result3 error + }) + } + fake.updateStackReturnsOnCall[i] = struct { + result1 resources.Stack + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeCloudControllerClient) GetStagingSecurityGroups(arg1 string, arg2 ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error) { fake.getStagingSecurityGroupsMutex.Lock() ret, specificReturn := fake.getStagingSecurityGroupsReturnsOnCall[len(fake.getStagingSecurityGroupsArgsForCall)] diff --git a/api/cloudcontroller/ccv3/stack_test.go b/api/cloudcontroller/ccv3/stack_test.go index 1787178ef9..b6359d84ee 100644 --- a/api/cloudcontroller/ccv3/stack_test.go +++ b/api/cloudcontroller/ccv3/stack_test.go @@ -155,7 +155,7 @@ var _ = Describe("Stacks", func() { BeforeEach(func() { stackGUID = "some-stack-guid" - state = "DEPRECATED" + state = resources.StackStateDeprecated }) JustBeforeEach(func() { @@ -168,7 +168,7 @@ var _ = Describe("Stacks", func() { CombineHandlers( VerifyRequest(http.MethodPatch, "/v3/stacks/some-stack-guid"), VerifyJSONRepresenting(map[string]string{ - "state": "DEPRECATED", + "state": resources.StackStateDeprecated, }), RespondWith(http.StatusOK, `{ "guid": "some-stack-guid", @@ -187,7 +187,7 @@ var _ = Describe("Stacks", func() { GUID: "some-stack-guid", Name: "some-stack", Description: "some description", - State: "DEPRECATED", + State: resources.StackStateDeprecated, })) }) }) diff --git a/command/v7/stack_command.go b/command/v7/stack_command.go index 910902c61a..d451fa9d49 100644 --- a/command/v7/stack_command.go +++ b/command/v7/stack_command.go @@ -11,7 +11,7 @@ type StackCommand struct { RequiredArgs flag.StackName `positional-args:"yes"` GUID bool `long:"guid" description:"Retrieve and display the given stack's guid. All other output for the stack is suppressed."` usage interface{} `usage:"CF_NAME stack STACK_NAME"` - relatedCommands interface{} `related_commands:"app, push, stacks"` + relatedCommands interface{} `related_commands:"app, push, stacks, update-stack"` } func (cmd *StackCommand) Execute(args []string) error { diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index 35ee1f4167..561093407d 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -4,10 +4,8 @@ import ( "slices" "strings" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" - "code.cloudfoundry.org/cli/v9/command" - "code.cloudfoundry.org/cli/v9/command/flag" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v8/command/flag" + "code.cloudfoundry.org/cli/v8/resources" ) type UpdateStackCommand struct { @@ -15,17 +13,12 @@ type UpdateStackCommand struct { RequiredArgs flag.StackName `positional-args:"yes"` State string `long:"state" description:"State to transition the stack to (active, restricted, deprecated, disabled)" required:"true"` - usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state (active | restricted | deprecated | disabled)]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled"` + usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state active|restricted|deprecated|disabled]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled"` relatedCommands interface{} `related_commands:"stack, stacks"` } func (cmd UpdateStackCommand) Execute(args []string) error { - err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionUpdateStack) - if err != nil { - return err - } - - err = cmd.SharedActor.CheckTarget(false, false) + err := cmd.SharedActor.CheckTarget(false, false) if err != nil { return err } diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index d36de3d869..3ecb4626f5 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -3,14 +3,14 @@ package v7_test import ( "errors" - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/command/commandfakes" - . "code.cloudfoundry.org/cli/v9/command/v7" - "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" - "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/util/configv3" - "code.cloudfoundry.org/cli/v9/util/ui" + "code.cloudfoundry.org/cli/v8/actor/actionerror" + "code.cloudfoundry.org/cli/v8/actor/v7action" + "code.cloudfoundry.org/cli/v8/command/commandfakes" + . "code.cloudfoundry.org/cli/v8/command/v7" + "code.cloudfoundry.org/cli/v8/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v8/resources" + "code.cloudfoundry.org/cli/v8/util/configv3" + "code.cloudfoundry.org/cli/v8/util/ui" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -51,7 +51,6 @@ var _ = Describe("update-stack Command", func() { binaryName = "faceman" fakeConfig.BinaryNameReturns(binaryName) - fakeConfig.APIVersionReturns("3.210.0") }) Context("When the environment is not setup correctly", func() { @@ -157,43 +156,43 @@ var _ = Describe("update-stack Command", func() { }) }) - Context("when state values are provided in different cases", func() { - It("accepts 'active' and capitalizes it", func() { - cmd.State = "active" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) + Context("when state values are provided in different cases", func() { + It("accepts 'active' and capitalizes it", func() { + cmd.State = "active" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateActive)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateActive)) + }) - It("accepts 'RESTRICTED' and keeps it capitalized", func() { - cmd.State = "RESTRICTED" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) + It("accepts 'RESTRICTED' and keeps it capitalized", func() { + cmd.State = "RESTRICTED" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateRestricted)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateRestricted)) + }) - It("accepts 'Disabled' and capitalizes it", func() { - cmd.State = "Disabled" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) + It("accepts 'Disabled' and capitalizes it", func() { + cmd.State = "Disabled" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateDisabled)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateDisabled)) }) }) + }) }) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 8da77a76ee..1708d8d648 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3649,6 +3649,22 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + UpdateStackStub func(string, string) (resources.Stack, v7action.Warnings, error) + updateStackMutex sync.RWMutex + updateStackArgsForCall []struct { + arg1 string + arg2 string + } + updateStackReturns struct { + result1 resources.Stack + result2 v7action.Warnings + result3 error + } + updateStackReturnsOnCall map[int]struct { + result1 resources.Stack + result2 v7action.Warnings + result3 error + } UpdateUserPasswordStub func(string, string, string) error updateUserPasswordMutex sync.RWMutex updateUserPasswordArgsForCall []struct { @@ -19658,6 +19674,74 @@ func (fake *FakeActor) UpdateStackLabelsByStackNameReturnsOnCall(i int, result1 }{result1, result2} } +func (fake *FakeActor) UpdateStack(arg1 string, arg2 string) (resources.Stack, v7action.Warnings, error) { + fake.updateStackMutex.Lock() + ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] + fake.updateStackArgsForCall = append(fake.updateStackArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + stub := fake.UpdateStackStub + fakeReturns := fake.updateStackReturns + fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2}) + fake.updateStackMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeActor) UpdateStackCallCount() int { + fake.updateStackMutex.RLock() + defer fake.updateStackMutex.RUnlock() + return len(fake.updateStackArgsForCall) +} + +func (fake *FakeActor) UpdateStackCalls(stub func(string, string) (resources.Stack, v7action.Warnings, error)) { + fake.updateStackMutex.Lock() + defer fake.updateStackMutex.Unlock() + fake.UpdateStackStub = stub +} + +func (fake *FakeActor) UpdateStackArgsForCall(i int) (string, string) { + fake.updateStackMutex.RLock() + defer fake.updateStackMutex.RUnlock() + argsForCall := fake.updateStackArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeActor) UpdateStackReturns(result1 resources.Stack, result2 v7action.Warnings, result3 error) { + fake.updateStackMutex.Lock() + defer fake.updateStackMutex.Unlock() + fake.UpdateStackStub = nil + fake.updateStackReturns = struct { + result1 resources.Stack + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) UpdateStackReturnsOnCall(i int, result1 resources.Stack, result2 v7action.Warnings, result3 error) { + fake.updateStackMutex.Lock() + defer fake.updateStackMutex.Unlock() + fake.UpdateStackStub = nil + if fake.updateStackReturnsOnCall == nil { + fake.updateStackReturnsOnCall = make(map[int]struct { + result1 resources.Stack + result2 v7action.Warnings + result3 error + }) + } + fake.updateStackReturnsOnCall[i] = struct { + result1 resources.Stack + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeActor) UpdateUserPassword(arg1 string, arg2 string, arg3 string) error { fake.updateUserPasswordMutex.Lock() ret, specificReturn := fake.updateUserPasswordReturnsOnCall[len(fake.updateUserPasswordArgsForCall)] diff --git a/resources/stack_resource.go b/resources/stack_resource.go index 049fb187b5..a26746b021 100644 --- a/resources/stack_resource.go +++ b/resources/stack_resource.go @@ -27,6 +27,11 @@ func ValidStackStatesLowercase() []string { return lowercase } +// ValidStackStatesString returns a pipe-separated string of valid states in lowercase +func ValidStackStatesString() string { + return strings.Join(ValidStackStatesLowercase(), "|") +} + type Stack struct { // GUID is a unique stack identifier. GUID string `json:"guid"` From d092908d9ac08ad252b9dfb84aab6dab9be72d3e Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Tue, 9 Dec 2025 12:19:39 -0500 Subject: [PATCH 04/20] Include reference to state in help text for cf stack & stacks Signed-off-by: Simon Jones --- api/cloudcontroller/ccv3/stack_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/cloudcontroller/ccv3/stack_test.go b/api/cloudcontroller/ccv3/stack_test.go index b6359d84ee..1787178ef9 100644 --- a/api/cloudcontroller/ccv3/stack_test.go +++ b/api/cloudcontroller/ccv3/stack_test.go @@ -155,7 +155,7 @@ var _ = Describe("Stacks", func() { BeforeEach(func() { stackGUID = "some-stack-guid" - state = resources.StackStateDeprecated + state = "DEPRECATED" }) JustBeforeEach(func() { @@ -168,7 +168,7 @@ var _ = Describe("Stacks", func() { CombineHandlers( VerifyRequest(http.MethodPatch, "/v3/stacks/some-stack-guid"), VerifyJSONRepresenting(map[string]string{ - "state": resources.StackStateDeprecated, + "state": "DEPRECATED", }), RespondWith(http.StatusOK, `{ "guid": "some-stack-guid", @@ -187,7 +187,7 @@ var _ = Describe("Stacks", func() { GUID: "some-stack-guid", Name: "some-stack", Description: "some description", - State: resources.StackStateDeprecated, + State: "DEPRECATED", })) }) }) From aa986f59d04abdfa24bad6a0cc7e522bdd51a2d8 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Tue, 9 Dec 2025 12:45:33 -0500 Subject: [PATCH 05/20] Add update stack command integration tests Signed-off-by: Simon Jones --- integration/v7/isolated/update_stack_command_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 6384647266..2e5c0ba893 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -4,9 +4,10 @@ import ( "fmt" "regexp" - . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" + . "code.cloudfoundry.org/cli/v8/cf/util/testhelpers/matchers" - "code.cloudfoundry.org/cli/v9/integration/helpers" + "code.cloudfoundry.org/cli/v8/integration/helpers" + "code.cloudfoundry.org/cli/v8/resources" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" @@ -44,7 +45,7 @@ var _ = Describe("update-stack command", func() { Eventually(session).Should(Say(`NAME:`)) Eventually(session).Should(Say(`update-stack - Transition a stack between the defined states`)) Eventually(session).Should(Say(`USAGE:`)) - Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state \(active \| restricted \| deprecated \| disabled\)\]`)) + Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state active\|restricted\|deprecated\|disabled\]`)) Eventually(session).Should(Say(`EXAMPLES:`)) Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state disabled`)) Eventually(session).Should(Say(`OPTIONS:`)) From b9138e36dea0792181ba2bf4b331220ccc523f85 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Tue, 20 Jan 2026 10:06:16 -0500 Subject: [PATCH 06/20] Stack related fakes generated correctly by counterfeiter Signed-off-by: Simon Jones --- .../fake_cloud_controller_client.go | 84 --------- command/v7/v7fakes/fake_actor.go | 166 ------------------ 2 files changed, 250 deletions(-) diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 6a00bcbb1d..44eb02a3bb 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -1912,22 +1912,6 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } - UpdateStackStub func(string, string) (resources.Stack, ccv3.Warnings, error) - updateStackMutex sync.RWMutex - updateStackArgsForCall []struct { - arg1 string - arg2 string - } - updateStackReturns struct { - result1 resources.Stack - result2 ccv3.Warnings - result3 error - } - updateStackReturnsOnCall map[int]struct { - result1 resources.Stack - result2 ccv3.Warnings - result3 error - } GetStagingSecurityGroupsStub func(string, ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error) getStagingSecurityGroupsMutex sync.RWMutex getStagingSecurityGroupsArgsForCall []struct { @@ -11230,74 +11214,6 @@ func (fake *FakeCloudControllerClient) GetStacksReturnsOnCall(i int, result1 []r }{result1, result2, result3} } -func (fake *FakeCloudControllerClient) UpdateStack(arg1 string, arg2 string) (resources.Stack, ccv3.Warnings, error) { - fake.updateStackMutex.Lock() - ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] - fake.updateStackArgsForCall = append(fake.updateStackArgsForCall, struct { - arg1 string - arg2 string - }{arg1, arg2}) - stub := fake.UpdateStackStub - fakeReturns := fake.updateStackReturns - fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2}) - fake.updateStackMutex.Unlock() - if stub != nil { - return stub(arg1, arg2) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeCloudControllerClient) UpdateStackCallCount() int { - fake.updateStackMutex.RLock() - defer fake.updateStackMutex.RUnlock() - return len(fake.updateStackArgsForCall) -} - -func (fake *FakeCloudControllerClient) UpdateStackCalls(stub func(string, string) (resources.Stack, ccv3.Warnings, error)) { - fake.updateStackMutex.Lock() - defer fake.updateStackMutex.Unlock() - fake.UpdateStackStub = stub -} - -func (fake *FakeCloudControllerClient) UpdateStackArgsForCall(i int) (string, string) { - fake.updateStackMutex.RLock() - defer fake.updateStackMutex.RUnlock() - argsForCall := fake.updateStackArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 -} - -func (fake *FakeCloudControllerClient) UpdateStackReturns(result1 resources.Stack, result2 ccv3.Warnings, result3 error) { - fake.updateStackMutex.Lock() - defer fake.updateStackMutex.Unlock() - fake.UpdateStackStub = nil - fake.updateStackReturns = struct { - result1 resources.Stack - result2 ccv3.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeCloudControllerClient) UpdateStackReturnsOnCall(i int, result1 resources.Stack, result2 ccv3.Warnings, result3 error) { - fake.updateStackMutex.Lock() - defer fake.updateStackMutex.Unlock() - fake.UpdateStackStub = nil - if fake.updateStackReturnsOnCall == nil { - fake.updateStackReturnsOnCall = make(map[int]struct { - result1 resources.Stack - result2 ccv3.Warnings - result3 error - }) - } - fake.updateStackReturnsOnCall[i] = struct { - result1 resources.Stack - result2 ccv3.Warnings - result3 error - }{result1, result2, result3} -} - func (fake *FakeCloudControllerClient) GetStagingSecurityGroups(arg1 string, arg2 ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error) { fake.getStagingSecurityGroupsMutex.Lock() ret, specificReturn := fake.getStagingSecurityGroupsReturnsOnCall[len(fake.getStagingSecurityGroupsArgsForCall)] diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 1708d8d648..ab4f00d448 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -2503,21 +2503,6 @@ type FakeActor struct { result1 resources.User result2 error } - ListServiceAppBindingsStub func(v7action.ListServiceAppBindingParams) ([]resources.ServiceCredentialBinding, v7action.Warnings, error) - listServiceAppBindingsMutex sync.RWMutex - listServiceAppBindingsArgsForCall []struct { - arg1 v7action.ListServiceAppBindingParams - } - listServiceAppBindingsReturns struct { - result1 []resources.ServiceCredentialBinding - result2 v7action.Warnings - result3 error - } - listServiceAppBindingsReturnsOnCall map[int]struct { - result1 []resources.ServiceCredentialBinding - result2 v7action.Warnings - result3 error - } MakeCurlRequestStub func(string, string, []string, string, bool) ([]byte, *http.Response, error) makeCurlRequestMutex sync.RWMutex makeCurlRequestArgsForCall []struct { @@ -3649,22 +3634,6 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - UpdateStackStub func(string, string) (resources.Stack, v7action.Warnings, error) - updateStackMutex sync.RWMutex - updateStackArgsForCall []struct { - arg1 string - arg2 string - } - updateStackReturns struct { - result1 resources.Stack - result2 v7action.Warnings - result3 error - } - updateStackReturnsOnCall map[int]struct { - result1 resources.Stack - result2 v7action.Warnings - result3 error - } UpdateUserPasswordStub func(string, string, string) error updateUserPasswordMutex sync.RWMutex updateUserPasswordArgsForCall []struct { @@ -14575,73 +14544,6 @@ func (fake *FakeActor) GetUserReturnsOnCall(i int, result1 resources.User, resul }{result1, result2} } -func (fake *FakeActor) ListServiceAppBindings(arg1 v7action.ListServiceAppBindingParams) ([]resources.ServiceCredentialBinding, v7action.Warnings, error) { - fake.listServiceAppBindingsMutex.Lock() - ret, specificReturn := fake.listServiceAppBindingsReturnsOnCall[len(fake.listServiceAppBindingsArgsForCall)] - fake.listServiceAppBindingsArgsForCall = append(fake.listServiceAppBindingsArgsForCall, struct { - arg1 v7action.ListServiceAppBindingParams - }{arg1}) - stub := fake.ListServiceAppBindingsStub - fakeReturns := fake.listServiceAppBindingsReturns - fake.recordInvocation("ListServiceAppBindings", []interface{}{arg1}) - fake.listServiceAppBindingsMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeActor) ListServiceAppBindingsCallCount() int { - fake.listServiceAppBindingsMutex.RLock() - defer fake.listServiceAppBindingsMutex.RUnlock() - return len(fake.listServiceAppBindingsArgsForCall) -} - -func (fake *FakeActor) ListServiceAppBindingsCalls(stub func(v7action.ListServiceAppBindingParams) ([]resources.ServiceCredentialBinding, v7action.Warnings, error)) { - fake.listServiceAppBindingsMutex.Lock() - defer fake.listServiceAppBindingsMutex.Unlock() - fake.ListServiceAppBindingsStub = stub -} - -func (fake *FakeActor) ListServiceAppBindingsArgsForCall(i int) v7action.ListServiceAppBindingParams { - fake.listServiceAppBindingsMutex.RLock() - defer fake.listServiceAppBindingsMutex.RUnlock() - argsForCall := fake.listServiceAppBindingsArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeActor) ListServiceAppBindingsReturns(result1 []resources.ServiceCredentialBinding, result2 v7action.Warnings, result3 error) { - fake.listServiceAppBindingsMutex.Lock() - defer fake.listServiceAppBindingsMutex.Unlock() - fake.ListServiceAppBindingsStub = nil - fake.listServiceAppBindingsReturns = struct { - result1 []resources.ServiceCredentialBinding - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeActor) ListServiceAppBindingsReturnsOnCall(i int, result1 []resources.ServiceCredentialBinding, result2 v7action.Warnings, result3 error) { - fake.listServiceAppBindingsMutex.Lock() - defer fake.listServiceAppBindingsMutex.Unlock() - fake.ListServiceAppBindingsStub = nil - if fake.listServiceAppBindingsReturnsOnCall == nil { - fake.listServiceAppBindingsReturnsOnCall = make(map[int]struct { - result1 []resources.ServiceCredentialBinding - result2 v7action.Warnings - result3 error - }) - } - fake.listServiceAppBindingsReturnsOnCall[i] = struct { - result1 []resources.ServiceCredentialBinding - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - func (fake *FakeActor) MakeCurlRequest(arg1 string, arg2 string, arg3 []string, arg4 string, arg5 bool) ([]byte, *http.Response, error) { var arg3Copy []string if arg3 != nil { @@ -19674,74 +19576,6 @@ func (fake *FakeActor) UpdateStackLabelsByStackNameReturnsOnCall(i int, result1 }{result1, result2} } -func (fake *FakeActor) UpdateStack(arg1 string, arg2 string) (resources.Stack, v7action.Warnings, error) { - fake.updateStackMutex.Lock() - ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] - fake.updateStackArgsForCall = append(fake.updateStackArgsForCall, struct { - arg1 string - arg2 string - }{arg1, arg2}) - stub := fake.UpdateStackStub - fakeReturns := fake.updateStackReturns - fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2}) - fake.updateStackMutex.Unlock() - if stub != nil { - return stub(arg1, arg2) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeActor) UpdateStackCallCount() int { - fake.updateStackMutex.RLock() - defer fake.updateStackMutex.RUnlock() - return len(fake.updateStackArgsForCall) -} - -func (fake *FakeActor) UpdateStackCalls(stub func(string, string) (resources.Stack, v7action.Warnings, error)) { - fake.updateStackMutex.Lock() - defer fake.updateStackMutex.Unlock() - fake.UpdateStackStub = stub -} - -func (fake *FakeActor) UpdateStackArgsForCall(i int) (string, string) { - fake.updateStackMutex.RLock() - defer fake.updateStackMutex.RUnlock() - argsForCall := fake.updateStackArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 -} - -func (fake *FakeActor) UpdateStackReturns(result1 resources.Stack, result2 v7action.Warnings, result3 error) { - fake.updateStackMutex.Lock() - defer fake.updateStackMutex.Unlock() - fake.UpdateStackStub = nil - fake.updateStackReturns = struct { - result1 resources.Stack - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeActor) UpdateStackReturnsOnCall(i int, result1 resources.Stack, result2 v7action.Warnings, result3 error) { - fake.updateStackMutex.Lock() - defer fake.updateStackMutex.Unlock() - fake.UpdateStackStub = nil - if fake.updateStackReturnsOnCall == nil { - fake.updateStackReturnsOnCall = make(map[int]struct { - result1 resources.Stack - result2 v7action.Warnings - result3 error - }) - } - fake.updateStackReturnsOnCall[i] = struct { - result1 resources.Stack - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - func (fake *FakeActor) UpdateUserPassword(arg1 string, arg2 string, arg3 string) error { fake.updateUserPasswordMutex.Lock() ret, specificReturn := fake.updateUserPasswordReturnsOnCall[len(fake.updateUserPasswordArgsForCall)] From 48c1ffb2cef97c384836d480f444025b3c1be6b6 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Tue, 20 Jan 2026 10:29:23 -0500 Subject: [PATCH 07/20] Fix import paths from v8 to v9 for module compatibility --- command/v7/stack_command.go | 4 ++-- command/v7/update_stack_command.go | 4 ++-- command/v7/update_stack_command_test.go | 16 ++++++++-------- .../v7/isolated/update_stack_command_test.go | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/command/v7/stack_command.go b/command/v7/stack_command.go index d451fa9d49..ff9f57f742 100644 --- a/command/v7/stack_command.go +++ b/command/v7/stack_command.go @@ -1,8 +1,8 @@ package v7 import ( - "code.cloudfoundry.org/cli/v8/command/flag" - "code.cloudfoundry.org/cli/v8/resources" + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/resources" ) type StackCommand struct { diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index 561093407d..3330e3afa5 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -4,8 +4,8 @@ import ( "slices" "strings" - "code.cloudfoundry.org/cli/v8/command/flag" - "code.cloudfoundry.org/cli/v8/resources" + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/resources" ) type UpdateStackCommand struct { diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index 3ecb4626f5..3f1c78293c 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -3,14 +3,14 @@ package v7_test import ( "errors" - "code.cloudfoundry.org/cli/v8/actor/actionerror" - "code.cloudfoundry.org/cli/v8/actor/v7action" - "code.cloudfoundry.org/cli/v8/command/commandfakes" - . "code.cloudfoundry.org/cli/v8/command/v7" - "code.cloudfoundry.org/cli/v8/command/v7/v7fakes" - "code.cloudfoundry.org/cli/v8/resources" - "code.cloudfoundry.org/cli/v8/util/configv3" - "code.cloudfoundry.org/cli/v8/util/ui" + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + . "code.cloudfoundry.org/cli/v9/command/v7" + "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 2e5c0ba893..054956e9b0 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -4,10 +4,10 @@ import ( "fmt" "regexp" - . "code.cloudfoundry.org/cli/v8/cf/util/testhelpers/matchers" + . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" - "code.cloudfoundry.org/cli/v8/integration/helpers" - "code.cloudfoundry.org/cli/v8/resources" + "code.cloudfoundry.org/cli/v9/integration/helpers" + "code.cloudfoundry.org/cli/v9/resources" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" From 55d39c9b58acf67195b6e876527d0c32b8a3194b Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Tue, 20 Jan 2026 10:35:03 -0500 Subject: [PATCH 08/20] Add update-stack command to help categories --- command/common/internal/help_all_display.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 5ca0633112..3fdf589f33 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -20,9 +20,9 @@ var HelpCategoryList = []HelpCategory{ {"revision", "revisions", "rollback"}, {"droplets", "set-droplet", "download-droplet"}, {"events", "logs"}, - {"env", "set-env", "unset-env"}, - {"stacks", "stack", "update-stack"}, - {"copy-source", "create-app-manifest"}, + {"env", "set-env", "unset-env"}, + {"stacks", "stack", "update-stack"}, + {"copy-source", "create-app-manifest"}, {"get-health-check", "set-health-check", "get-readiness-health-check"}, {"enable-ssh", "disable-ssh", "ssh-enabled", "ssh"}, }, From 897d53df5e997c8a88ea92737dbc651da73b2034 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Wed, 28 Jan 2026 11:35:27 -0500 Subject: [PATCH 09/20] Add parentheses and spaces to update-stack usage command Signed-off-by: Simon Jones --- command/v7/update_stack_command.go | 2 +- integration/v7/isolated/update_stack_command_test.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index 3330e3afa5..be02d49900 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -13,7 +13,7 @@ type UpdateStackCommand struct { RequiredArgs flag.StackName `positional-args:"yes"` State string `long:"state" description:"State to transition the stack to (active, restricted, deprecated, disabled)" required:"true"` - usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state active|restricted|deprecated|disabled]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled"` + usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state (active | restricted | deprecated | disabled)]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled"` relatedCommands interface{} `related_commands:"stack, stacks"` } diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 054956e9b0..6384647266 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -7,7 +7,6 @@ import ( . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" "code.cloudfoundry.org/cli/v9/integration/helpers" - "code.cloudfoundry.org/cli/v9/resources" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" @@ -45,7 +44,7 @@ var _ = Describe("update-stack command", func() { Eventually(session).Should(Say(`NAME:`)) Eventually(session).Should(Say(`update-stack - Transition a stack between the defined states`)) Eventually(session).Should(Say(`USAGE:`)) - Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state active\|restricted\|deprecated\|disabled\]`)) + Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state \(active \| restricted \| deprecated \| disabled\)\]`)) Eventually(session).Should(Say(`EXAMPLES:`)) Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state disabled`)) Eventually(session).Should(Say(`OPTIONS:`)) From a66bb13ef4b98effb1feda216b92eb90ab94c7ff Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Fri, 30 Jan 2026 12:07:21 -0500 Subject: [PATCH 10/20] Configure minimum CC API version for update-stack command Signed-off-by: Simon Jones --- command/v7/update_stack_command.go | 9 ++++++++- command/v7/update_stack_command_test.go | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index be02d49900..35ee1f4167 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -4,6 +4,8 @@ import ( "slices" "strings" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/resources" ) @@ -18,7 +20,12 @@ type UpdateStackCommand struct { } func (cmd UpdateStackCommand) Execute(args []string) error { - err := cmd.SharedActor.CheckTarget(false, false) + err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionUpdateStack) + if err != nil { + return err + } + + err = cmd.SharedActor.CheckTarget(false, false) if err != nil { return err } diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index 3f1c78293c..2b38a672a8 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -51,6 +51,7 @@ var _ = Describe("update-stack Command", func() { binaryName = "faceman" fakeConfig.BinaryNameReturns(binaryName) + fakeConfig.APIVersionReturns("3.210.0") }) Context("When the environment is not setup correctly", func() { From 48418953f981b79359cf6add281a883f1ad6ec88 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Mon, 2 Feb 2026 13:03:50 -0500 Subject: [PATCH 11/20] Add assertions for state output in stack command tests --- command/v7/stack_command_test.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 953bf12519..3b1b87de8c 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -137,11 +137,15 @@ var _ = Describe("Stack Command", func() { fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) }) - It("Displays the stack information without state", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - }) + It("Displays the stack information without state", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).NotTo(Say("state:")) + }) }) Context("When the stack has a state", func() { @@ -155,11 +159,15 @@ var _ = Describe("Stack Command", func() { fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) }) - It("Displays the stack information with state", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - }) + It("Displays the stack information with state", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+ACTIVE")) + }) }) When("The Stack does not Exist", func() { From fbe2b45901b44fd724c8b9a84ef2551140693690 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Mon, 9 Feb 2026 13:23:42 -0500 Subject: [PATCH 12/20] Fix indentation in help_all_display.go APPS section Co-authored-by: Cursor --- command/common/internal/help_all_display.go | 6 +-- command/v7/stack_command_test.go | 34 ++++++------- command/v7/update_stack_command_test.go | 56 ++++++++++----------- resources/stack_resource.go | 5 -- 4 files changed, 48 insertions(+), 53 deletions(-) diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 3fdf589f33..5ca0633112 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -20,9 +20,9 @@ var HelpCategoryList = []HelpCategory{ {"revision", "revisions", "rollback"}, {"droplets", "set-droplet", "download-droplet"}, {"events", "logs"}, - {"env", "set-env", "unset-env"}, - {"stacks", "stack", "update-stack"}, - {"copy-source", "create-app-manifest"}, + {"env", "set-env", "unset-env"}, + {"stacks", "stack", "update-stack"}, + {"copy-source", "create-app-manifest"}, {"get-health-check", "set-health-check", "get-readiness-health-check"}, {"enable-ssh", "disable-ssh", "ssh-enabled", "ssh"}, }, diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 3b1b87de8c..212078fda6 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -137,15 +137,15 @@ var _ = Describe("Stack Command", func() { fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) }) - It("Displays the stack information without state", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).NotTo(Say("state:")) - }) + It("Displays the stack information without state", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).NotTo(Say("state:")) + }) }) Context("When the stack has a state", func() { @@ -159,15 +159,15 @@ var _ = Describe("Stack Command", func() { fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) }) - It("Displays the stack information with state", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + It("Displays the stack information with state", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).To(Say("state:\\s+ACTIVE")) - }) + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+ACTIVE")) + }) }) When("The Stack does not Exist", func() { diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index 2b38a672a8..d36de3d869 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -157,43 +157,43 @@ var _ = Describe("update-stack Command", func() { }) }) - Context("when state values are provided in different cases", func() { - It("accepts 'active' and capitalizes it", func() { - cmd.State = "active" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) + Context("when state values are provided in different cases", func() { + It("accepts 'active' and capitalizes it", func() { + cmd.State = "active" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateActive)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateActive)) + }) - It("accepts 'RESTRICTED' and keeps it capitalized", func() { - cmd.State = "RESTRICTED" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) + It("accepts 'RESTRICTED' and keeps it capitalized", func() { + cmd.State = "RESTRICTED" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateRestricted)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateRestricted)) + }) - It("accepts 'Disabled' and capitalizes it", func() { - cmd.State = "Disabled" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) + It("accepts 'Disabled' and capitalizes it", func() { + cmd.State = "Disabled" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateDisabled)) + Expect(executeErr).ToNot(HaveOccurred()) + _, state := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateDisabled)) + }) }) }) - }) }) diff --git a/resources/stack_resource.go b/resources/stack_resource.go index a26746b021..049fb187b5 100644 --- a/resources/stack_resource.go +++ b/resources/stack_resource.go @@ -27,11 +27,6 @@ func ValidStackStatesLowercase() []string { return lowercase } -// ValidStackStatesString returns a pipe-separated string of valid states in lowercase -func ValidStackStatesString() string { - return strings.Join(ValidStackStatesLowercase(), "|") -} - type Stack struct { // GUID is a unique stack identifier. GUID string `json:"guid"` From 2abe16c241ec0995431ced182f29dd9d23326dea Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Thu, 12 Feb 2026 11:29:53 -0500 Subject: [PATCH 13/20] Update stack and stacks integration test expectations for state support - Update help description expectations to include "and current state" - Add state column to stacks listing assertions - Consolidate stack exists test contexts and add state assertions - Add state exclusion assertion to --guid test Co-authored-by: Cursor --- integration/v7/isolated/stack_command_test.go | 83 ++++++------------- 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/integration/v7/isolated/stack_command_test.go b/integration/v7/isolated/stack_command_test.go index d007fdeb10..4a0abc6302 100644 --- a/integration/v7/isolated/stack_command_test.go +++ b/integration/v7/isolated/stack_command_test.go @@ -33,7 +33,7 @@ var _ = Describe("stack command", func() { It("appears in cf help -a", func() { session := helpers.CF("help", "-a") Eventually(session).Should(Exit(0)) - Expect(session).To(HaveCommandInCategoryWithDescription("stack", "APPS", "Show information for a stack (a stack is a pre-built file system, including an operating system, that can run apps)")) + Expect(session).To(HaveCommandInCategoryWithDescription("stack", "APPS", "Show information for a stack (a stack is a pre-built file system, including an operating system, that can run apps) and current state")) }) It("Displays command usage to output", func() { @@ -100,71 +100,42 @@ var _ = Describe("stack command", func() { }) }) - When("the stack exists", func() { + When("the stack exists with valid state", func() { var stackGUID string - Context("when the stack has no state", func() { - BeforeEach(func() { - jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s"}`, stackName, stackDescription) - session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks") - Eventually(session).Should(Exit(0)) - - r := regexp.MustCompile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`) - stackGUID = string(r.Find(session.Out.Contents())) - }) - - AfterEach(func() { - session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) - Eventually(session).Should(Exit(0)) - }) - - It("Shows the details for the stack without state", func() { - session := helpers.CF("stack", stackName) + BeforeEach(func() { + jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s", "state": "ACTIVE"}`, stackName, stackDescription) + session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks") + Eventually(session).Should(Exit(0)) - Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) - Eventually(session).Should(Say(`name:\s+%s`, stackName)) - Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) - Consistently(session).ShouldNot(Say(`state:`)) - Eventually(session).Should(Exit(0)) - }) + r := regexp.MustCompile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`) + stackGUID = string(r.Find(session.Out.Contents())) }) - Context("when the stack has a valid state", func() { - BeforeEach(func() { - jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s", "state": "ACTIVE"}`, stackName, stackDescription) - session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks") - Eventually(session).Should(Exit(0)) - - r := regexp.MustCompile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`) - stackGUID = string(r.Find(session.Out.Contents())) - }) - - AfterEach(func() { - session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) - Eventually(session).Should(Exit(0)) - }) + AfterEach(func() { + session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) + Eventually(session).Should(Exit(0)) + }) - It("Shows the details for the stack with state", func() { - session := helpers.CF("stack", stackName) + It("Shows the details for the stack with state", func() { + session := helpers.CF("stack", stackName) - Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) - Eventually(session).Should(Say(`name:\s+%s`, stackName)) - Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) - Eventually(session).Should(Say(`state:\s+ACTIVE`)) - Eventually(session).Should(Exit(0)) - }) + Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) + Eventually(session).Should(Say(`state:\s+ACTIVE`)) + Eventually(session).Should(Exit(0)) }) - When("the stack exists and the --guid flag is passed", func() { - It("prints nothing but the guid", func() { - session := helpers.CF("stack", stackName, "--guid") + It("prints nothing but the guid when --guid flag is passed", func() { + session := helpers.CF("stack", stackName, "--guid") - Consistently(session).ShouldNot(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) - Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName)) - Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription)) - Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`)) - Eventually(session).Should(Exit(0)) - }) + Consistently(session).ShouldNot(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName)) + Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription)) + Consistently(session).ShouldNot(Say(`state:`)) + Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`)) + Eventually(session).Should(Exit(0)) }) }) }) From e12af51878eee995f717d6e3a174e6f6288e279a Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Mon, 23 Feb 2026 12:06:29 -0500 Subject: [PATCH 14/20] Update minimum API version for update-stack to 3.211.0 Co-authored-by: Cursor --- api/cloudcontroller/ccversion/minimum_version.go | 2 +- command/v7/update_stack_command_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/cloudcontroller/ccversion/minimum_version.go b/api/cloudcontroller/ccversion/minimum_version.go index aa20b79610..1c0d33e42c 100644 --- a/api/cloudcontroller/ccversion/minimum_version.go +++ b/api/cloudcontroller/ccversion/minimum_version.go @@ -19,5 +19,5 @@ const ( MinVersionServiceBindingStrategy = "3.205.0" - MinVersionUpdateStack = "3.210.0" + MinVersionUpdateStack = "3.211.0" ) diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index d36de3d869..4eb4d0162f 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -51,7 +51,7 @@ var _ = Describe("update-stack Command", func() { binaryName = "faceman" fakeConfig.BinaryNameReturns(binaryName) - fakeConfig.APIVersionReturns("3.210.0") + fakeConfig.APIVersionReturns("3.211.0") }) Context("When the environment is not setup correctly", func() { From e68c23e1aee08d6a01317e0ded473f535d3170c0 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Thu, 5 Feb 2026 11:29:43 -0500 Subject: [PATCH 15/20] Add state_reason field to stack resource and display logic --- command/v7/stack_command.go | 5 ++ command/v7/stack_command_test.go | 80 ++++++++++++++++++++++++++------ resources/stack_resource.go | 2 + 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/command/v7/stack_command.go b/command/v7/stack_command.go index ff9f57f742..7c8eccc6c2 100644 --- a/command/v7/stack_command.go +++ b/command/v7/stack_command.go @@ -69,6 +69,11 @@ func (cmd *StackCommand) displayStackInfo() error { // Add state only if it's present if stack.State != "" { displayTable = append(displayTable, []string{cmd.UI.TranslateText("state:"), stack.State}) + + // Add reason only if state is not ACTIVE and reason is present + if stack.State != resources.StackStateActive && stack.StateReason != "" { + displayTable = append(displayTable, []string{cmd.UI.TranslateText("reason:"), stack.StateReason}) + } } cmd.UI.DisplayKeyValueTable("", displayTable, 3) diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 212078fda6..130abd404b 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -149,24 +149,74 @@ var _ = Describe("Stack Command", func() { }) Context("When the stack has a state", func() { - BeforeEach(func() { - stack := resources.Stack{ - Name: "some-stack-name", - GUID: "some-stack-guid", - Description: "some-stack-desc", - State: "ACTIVE", - } - fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + Context("When the state is ACTIVE", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "ACTIVE", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) + + It("Displays the stack information with state but no reason", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+ACTIVE")) + Expect(testUI.Out).NotTo(Say("reason:")) + }) }) - It("Displays the stack information with state", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + Context("When the state is not ACTIVE and has a reason", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "DEPRECATED", + StateReason: "This stack is being phased out", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).To(Say("state:\\s+ACTIVE")) + It("Displays the stack information with state and reason", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+DEPRECATED")) + Expect(testUI.Out).To(Say("reason:\\s+This stack is being phased out")) + }) + }) + + Context("When the state is not ACTIVE but has no reason", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "RESTRICTED", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) + + It("Displays the stack information with state but no reason", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) + Expect(testUI.Out).NotTo(Say("reason:")) + }) }) }) diff --git a/resources/stack_resource.go b/resources/stack_resource.go index 049fb187b5..395c5896a0 100644 --- a/resources/stack_resource.go +++ b/resources/stack_resource.go @@ -36,6 +36,8 @@ type Stack struct { Description string `json:"description"` // State is the state of the stack (ACTIVE, RESTRICTED, DEPRECATED, DISABLED) State string `json:"state,omitempty"` + // StateReason is the reason for the current state + StateReason string `json:"state_reason,omitempty"` // Metadata is used for custom tagging of API resources Metadata *Metadata `json:"metadata,omitempty"` From d3358a4b6d013e888a0223d6ead729cdf4544199 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Thu, 5 Feb 2026 11:44:29 -0500 Subject: [PATCH 16/20] Add --reason flag to update-stack command --- actor/v7action/stack.go | 4 +- api/cloudcontroller/ccv3/stack.go | 9 +- api/cloudcontroller/ccv3/stack_test.go | 38 ++++++++- command/v7/update_stack_command.go | 16 +++- command/v7/update_stack_command_test.go | 85 ++++++++++++------- .../v7/isolated/update_stack_command_test.go | 3 +- 6 files changed, 114 insertions(+), 41 deletions(-) diff --git a/actor/v7action/stack.go b/actor/v7action/stack.go index d4e9a5eae3..421389d513 100644 --- a/actor/v7action/stack.go +++ b/actor/v7action/stack.go @@ -47,8 +47,8 @@ func (actor Actor) GetStacks(labelSelector string) ([]resources.Stack, Warnings, return stacks, Warnings(warnings), nil } -func (actor Actor) UpdateStack(stackGUID string, state string) (resources.Stack, Warnings, error) { - stack, warnings, err := actor.CloudControllerClient.UpdateStack(stackGUID, state) +func (actor Actor) UpdateStack(stackGUID string, state string, reason string) (resources.Stack, Warnings, error) { + stack, warnings, err := actor.CloudControllerClient.UpdateStack(stackGUID, state, reason) if err != nil { return resources.Stack{}, Warnings(warnings), err } diff --git a/api/cloudcontroller/ccv3/stack.go b/api/cloudcontroller/ccv3/stack.go index 958eb10f6e..f60d6da1f4 100644 --- a/api/cloudcontroller/ccv3/stack.go +++ b/api/cloudcontroller/ccv3/stack.go @@ -22,18 +22,19 @@ func (client *Client) GetStacks(query ...Query) ([]resources.Stack, Warnings, er return stacks, warnings, err } -// UpdateStack updates a stack's state. -func (client *Client) UpdateStack(stackGUID string, state string) (resources.Stack, Warnings, error) { +// UpdateStack updates a stack's state and optionally its state reason. +func (client *Client) UpdateStack(stackGUID string, state string, reason string) (resources.Stack, Warnings, error) { var responseStack resources.Stack type StackUpdate struct { - State string `json:"state"` + State string `json:"state"` + StateReason string `json:"state_reason,omitempty"` } _, warnings, err := client.MakeRequest(RequestParams{ RequestName: internal.PatchStackRequest, URIParams: internal.Params{"stack_guid": stackGUID}, - RequestBody: StackUpdate{State: state}, + RequestBody: StackUpdate{State: state, StateReason: reason}, ResponseBody: &responseStack, }) diff --git a/api/cloudcontroller/ccv3/stack_test.go b/api/cloudcontroller/ccv3/stack_test.go index 1787178ef9..533de51b70 100644 --- a/api/cloudcontroller/ccv3/stack_test.go +++ b/api/cloudcontroller/ccv3/stack_test.go @@ -148,6 +148,7 @@ var _ = Describe("Stacks", func() { var ( stackGUID string state string + reason string stack resources.Stack warnings Warnings err error @@ -156,10 +157,11 @@ var _ = Describe("Stacks", func() { BeforeEach(func() { stackGUID = "some-stack-guid" state = "DEPRECATED" + reason = "" }) JustBeforeEach(func() { - stack, warnings, err = client.UpdateStack(stackGUID, state) + stack, warnings, err = client.UpdateStack(stackGUID, state, reason) }) When("the request succeeds", func() { @@ -192,6 +194,40 @@ var _ = Describe("Stacks", func() { }) }) + When("a reason is provided", func() { + BeforeEach(func() { + reason = "Use cflinuxfs4 instead" + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodPatch, "/v3/stacks/some-stack-guid"), + VerifyJSONRepresenting(map[string]string{ + "state": "DEPRECATED", + "state_reason": "Use cflinuxfs4 instead", + }), + RespondWith(http.StatusOK, `{ + "guid": "some-stack-guid", + "name": "some-stack", + "description": "some description", + "state": "DEPRECATED", + "state_reason": "Use cflinuxfs4 instead" + }`, http.Header{"X-Cf-Warnings": {"this is a warning"}}), + ), + ) + }) + + It("returns the updated stack with reason and warnings", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf("this is a warning")) + Expect(stack).To(Equal(resources.Stack{ + GUID: "some-stack-guid", + Name: "some-stack", + Description: "some description", + State: "DEPRECATED", + StateReason: "Use cflinuxfs4 instead", + })) + }) + }) + When("the cloud controller returns an error", func() { BeforeEach(func() { server.AppendHandlers( diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index 35ee1f4167..64f5ed8ab4 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -15,7 +15,8 @@ type UpdateStackCommand struct { RequiredArgs flag.StackName `positional-args:"yes"` State string `long:"state" description:"State to transition the stack to (active, restricted, deprecated, disabled)" required:"true"` - usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state (active | restricted | deprecated | disabled)]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled"` + Reason string `long:"reason" description:"Optional plain text describing the stack state change"` + usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state (active | restricted | deprecated | disabled)] [--reason REASON]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled\n CF_NAME update-stack cflinuxfs3 --state deprecated --reason 'Use cflinuxfs4 instead'"` relatedCommands interface{} `related_commands:"stack, stacks"` } @@ -56,7 +57,7 @@ func (cmd UpdateStackCommand) Execute(args []string) error { } // Update the stack - updatedStack, warnings, err := cmd.Actor.UpdateStack(stack.GUID, stateValue) + updatedStack, warnings, err := cmd.Actor.UpdateStack(stack.GUID, stateValue, cmd.Reason) cmd.UI.DisplayWarnings(warnings) if err != nil { return err @@ -66,11 +67,18 @@ func (cmd UpdateStackCommand) Execute(args []string) error { cmd.UI.DisplayNewline() // Display the updated stack info - cmd.UI.DisplayKeyValueTable("", [][]string{ + displayTable := [][]string{ {cmd.UI.TranslateText("name:"), updatedStack.Name}, {cmd.UI.TranslateText("description:"), updatedStack.Description}, {cmd.UI.TranslateText("state:"), updatedStack.State}, - }, 3) + } + + // Add reason if it's present + if updatedStack.StateReason != "" { + displayTable = append(displayTable, []string{cmd.UI.TranslateText("reason:"), updatedStack.StateReason}) + } + + cmd.UI.DisplayKeyValueTable("", displayTable, 3) return nil } diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index 4eb4d0162f..fe64ece778 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -149,51 +149,78 @@ var _ = Describe("update-stack Command", func() { Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack")) Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) - guid, state := fakeActor.UpdateStackArgsForCall(0) + guid, state, reason := fakeActor.UpdateStackArgsForCall(0) Expect(guid).To(Equal("stack-guid")) Expect(state).To(Equal(resources.StackStateDeprecated)) + Expect(reason).To(Equal("")) }) }) }) }) - Context("when state values are provided in different cases", func() { - It("accepts 'active' and capitalizes it", func() { - cmd.State = "active" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) + Context("when state values are provided in different cases", func() { + It("accepts 'active' and capitalizes it", func() { + cmd.State = "active" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateActive)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateActive)) + }) - It("accepts 'RESTRICTED' and keeps it capitalized", func() { - cmd.State = "RESTRICTED" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) + It("accepts 'RESTRICTED' and keeps it capitalized", func() { + cmd.State = "RESTRICTED" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateRestricted)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateRestricted)) + }) - It("accepts 'Disabled' and capitalizes it", func() { - cmd.State = "Disabled" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) + It("accepts 'Disabled' and capitalizes it", func() { + cmd.State = "Disabled" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateDisabled)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateDisabled)) }) }) + + Context("when the reason flag is provided", func() { + BeforeEach(func() { + cmd.State = "deprecated" + cmd.Reason = "Use cflinuxfs4 instead" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{ + Name: "some-stack", + Description: "some description", + State: resources.StackStateDeprecated, + StateReason: "Use cflinuxfs4 instead", + }, v7action.Warnings{}, nil) + }) + + It("passes the reason to the actor and displays it", func() { + executeErr = cmd.Execute(args) + + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) + _, _, reason := fakeActor.UpdateStackArgsForCall(0) + Expect(reason).To(Equal("Use cflinuxfs4 instead")) + + Expect(testUI.Out).To(Say(`reason:\s+Use cflinuxfs4 instead`)) + }) + }) + }) }) diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 6384647266..1956bd279f 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -44,9 +44,10 @@ var _ = Describe("update-stack command", func() { Eventually(session).Should(Say(`NAME:`)) Eventually(session).Should(Say(`update-stack - Transition a stack between the defined states`)) Eventually(session).Should(Say(`USAGE:`)) - Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state \(active \| restricted \| deprecated \| disabled\)\]`)) + Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state \(active \| restricted \| deprecated \| disabled\)\] \[--reason REASON\]`)) Eventually(session).Should(Say(`EXAMPLES:`)) Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state disabled`)) + Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state deprecated --reason 'Use cflinuxfs4 instead'`)) Eventually(session).Should(Say(`OPTIONS:`)) Eventually(session).Should(Say(`--state\s+State to transition the stack to`)) Eventually(session).Should(Say(`SEE ALSO:`)) From f0bc9faa96c03cb8ace7609d06c1468f7e406c33 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Thu, 5 Feb 2026 11:52:56 -0500 Subject: [PATCH 17/20] Update --reason flag usage example with detailed migration message --- command/v7/update_stack_command.go | 2 +- integration/v7/isolated/update_stack_command_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index 64f5ed8ab4..264eea8ce3 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -16,7 +16,7 @@ type UpdateStackCommand struct { RequiredArgs flag.StackName `positional-args:"yes"` State string `long:"state" description:"State to transition the stack to (active, restricted, deprecated, disabled)" required:"true"` Reason string `long:"reason" description:"Optional plain text describing the stack state change"` - usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state (active | restricted | deprecated | disabled)] [--reason REASON]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled\n CF_NAME update-stack cflinuxfs3 --state deprecated --reason 'Use cflinuxfs4 instead'"` + usage interface{} `usage:"CF_NAME update-stack STACK_NAME [--state (active | restricted | deprecated | disabled)] [--reason text]\n\nEXAMPLES:\n CF_NAME update-stack cflinuxfs3 --state disabled\n CF_NAME update-stack cflinuxfs3 --state deprecated --reason \"This stack is based on Ubuntu 18.04, which is no longer supported. Please migrate your applications to 'cflinuxfs4'. For more information, see: .\""` relatedCommands interface{} `related_commands:"stack, stacks"` } diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 1956bd279f..607b0dc9bd 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -44,10 +44,10 @@ var _ = Describe("update-stack command", func() { Eventually(session).Should(Say(`NAME:`)) Eventually(session).Should(Say(`update-stack - Transition a stack between the defined states`)) Eventually(session).Should(Say(`USAGE:`)) - Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state \(active \| restricted \| deprecated \| disabled\)\] \[--reason REASON\]`)) + Eventually(session).Should(Say(`cf update-stack STACK_NAME \[--state \(active \| restricted \| deprecated \| disabled\)\] \[--reason text\]`)) Eventually(session).Should(Say(`EXAMPLES:`)) Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state disabled`)) - Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state deprecated --reason 'Use cflinuxfs4 instead'`)) + Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state deprecated --reason "This stack is based on Ubuntu 18.04, which is no longer supported. Please migrate your applications to 'cflinuxfs4'. For more information, see: ."`)) Eventually(session).Should(Say(`OPTIONS:`)) Eventually(session).Should(Say(`--state\s+State to transition the stack to`)) Eventually(session).Should(Say(`SEE ALSO:`)) From 733087f4fc685a87b4d2625b656a7d60fa8dbbb0 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Thu, 12 Feb 2026 11:52:12 -0500 Subject: [PATCH 18/20] 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 - Regenerate fakes for updated interfaces - Fix duplicate Execute call in update-stack reason test Co-authored-by: Cursor --- actor/v7action/cloud_controller_client.go | 2 +- actor/v7action/stack_test.go | 7 +++- .../fake_cloud_controller_client.go | 18 +++++---- command/v7/actor.go | 2 +- command/v7/stack_command.go | 4 +- command/v7/stack_command_test.go | 38 +++++++++---------- command/v7/update_stack_command.go | 4 +- command/v7/update_stack_command_test.go | 2 - command/v7/v7fakes/fake_actor.go | 18 +++++---- 9 files changed, 50 insertions(+), 45 deletions(-) diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index d102b7a460..dbc7392c0b 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -138,7 +138,7 @@ type CloudControllerClient interface { GetAppFeature(appGUID string, featureName string) (resources.ApplicationFeature, ccv3.Warnings, error) GetStacks(query ...ccv3.Query) ([]resources.Stack, ccv3.Warnings, error) GetStagingSecurityGroups(spaceGUID string, queries ...ccv3.Query) ([]resources.SecurityGroup, ccv3.Warnings, error) - UpdateStack(stackGUID string, state string) (resources.Stack, ccv3.Warnings, error) + UpdateStack(stackGUID string, state string, reason string) (resources.Stack, ccv3.Warnings, error) GetTask(guid string) (resources.Task, ccv3.Warnings, error) GetUser(userGUID string) (resources.User, ccv3.Warnings, error) GetUsers(query ...ccv3.Query) ([]resources.User, ccv3.Warnings, error) diff --git a/actor/v7action/stack_test.go b/actor/v7action/stack_test.go index c2d11f880a..972d1d3a88 100644 --- a/actor/v7action/stack_test.go +++ b/actor/v7action/stack_test.go @@ -238,6 +238,7 @@ var _ = Describe("Stack", func() { var ( stackGUID string state string + reason string stack resources.Stack warnings Warnings executeErr error @@ -246,10 +247,11 @@ var _ = Describe("Stack", func() { BeforeEach(func() { stackGUID = "some-stack-guid" state = "DEPRECATED" + reason = "" }) JustBeforeEach(func() { - stack, warnings, executeErr = actor.UpdateStack(stackGUID, state) + stack, warnings, executeErr = actor.UpdateStack(stackGUID, state, reason) }) When("the cloud controller request is successful", func() { @@ -277,9 +279,10 @@ var _ = Describe("Stack", func() { })) Expect(fakeCloudControllerClient.UpdateStackCallCount()).To(Equal(1)) - actualGUID, actualState := fakeCloudControllerClient.UpdateStackArgsForCall(0) + actualGUID, actualState, actualReason := fakeCloudControllerClient.UpdateStackArgsForCall(0) Expect(actualGUID).To(Equal(stackGUID)) Expect(actualState).To(Equal(state)) + Expect(actualReason).To(Equal(reason)) }) }) diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 44eb02a3bb..45fc77f13c 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2709,11 +2709,12 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } - UpdateStackStub func(string, string) (resources.Stack, ccv3.Warnings, error) + UpdateStackStub func(string, string, string) (resources.Stack, ccv3.Warnings, error) updateStackMutex sync.RWMutex updateStackArgsForCall []struct { arg1 string arg2 string + arg3 string } updateStackReturns struct { result1 resources.Stack @@ -14801,19 +14802,20 @@ func (fake *FakeCloudControllerClient) UpdateSpaceQuotaReturnsOnCall(i int, resu }{result1, result2, result3} } -func (fake *FakeCloudControllerClient) UpdateStack(arg1 string, arg2 string) (resources.Stack, ccv3.Warnings, error) { +func (fake *FakeCloudControllerClient) UpdateStack(arg1 string, arg2 string, arg3 string) (resources.Stack, ccv3.Warnings, error) { fake.updateStackMutex.Lock() ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] fake.updateStackArgsForCall = append(fake.updateStackArgsForCall, struct { arg1 string arg2 string - }{arg1, arg2}) + arg3 string + }{arg1, arg2, arg3}) stub := fake.UpdateStackStub fakeReturns := fake.updateStackReturns - fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2}) + fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2, arg3}) fake.updateStackMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2, ret.result3 @@ -14827,17 +14829,17 @@ func (fake *FakeCloudControllerClient) UpdateStackCallCount() int { return len(fake.updateStackArgsForCall) } -func (fake *FakeCloudControllerClient) UpdateStackCalls(stub func(string, string) (resources.Stack, ccv3.Warnings, error)) { +func (fake *FakeCloudControllerClient) UpdateStackCalls(stub func(string, string, string) (resources.Stack, ccv3.Warnings, error)) { fake.updateStackMutex.Lock() defer fake.updateStackMutex.Unlock() fake.UpdateStackStub = stub } -func (fake *FakeCloudControllerClient) UpdateStackArgsForCall(i int) (string, string) { +func (fake *FakeCloudControllerClient) UpdateStackArgsForCall(i int) (string, string, string) { fake.updateStackMutex.RLock() defer fake.updateStackMutex.RUnlock() argsForCall := fake.updateStackArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeCloudControllerClient) UpdateStackReturns(result1 resources.Stack, result2 ccv3.Warnings, result3 error) { diff --git a/command/v7/actor.go b/command/v7/actor.go index eeec684455..c4e590678a 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -179,7 +179,7 @@ type Actor interface { GetStackByName(stackName string) (resources.Stack, v7action.Warnings, error) GetStackLabels(stackName string) (map[string]types.NullString, v7action.Warnings, error) GetStacks(string) ([]resources.Stack, v7action.Warnings, error) - UpdateStack(stackGUID string, state string) (resources.Stack, v7action.Warnings, error) + UpdateStack(stackGUID string, state string, reason string) (resources.Stack, v7action.Warnings, error) GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client sharedaction.LogCacheClient) (<-chan sharedaction.LogMessage, <-chan error, context.CancelFunc, v7action.Warnings, error) GetTaskBySequenceIDAndApplication(sequenceID int, appGUID string) (resources.Task, v7action.Warnings, error) GetUAAAPIVersion() (string, error) diff --git a/command/v7/stack_command.go b/command/v7/stack_command.go index 7c8eccc6c2..4ffcd57790 100644 --- a/command/v7/stack_command.go +++ b/command/v7/stack_command.go @@ -70,8 +70,8 @@ func (cmd *StackCommand) displayStackInfo() error { if stack.State != "" { displayTable = append(displayTable, []string{cmd.UI.TranslateText("state:"), stack.State}) - // Add reason only if state is not ACTIVE and reason is present - if stack.State != resources.StackStateActive && stack.StateReason != "" { + // Add reason whenever state is not ACTIVE + if stack.State != resources.StackStateActive { displayTable = append(displayTable, []string{cmd.UI.TranslateText("reason:"), stack.StateReason}) } } diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 130abd404b..537fc82270 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -196,29 +196,29 @@ var _ = Describe("Stack Command", func() { }) }) - Context("When the state is not ACTIVE but has no reason", func() { - BeforeEach(func() { - stack := resources.Stack{ - Name: "some-stack-name", - GUID: "some-stack-guid", - Description: "some-stack-desc", - State: "RESTRICTED", - } - fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) - }) + Context("When the state is not ACTIVE but has no reason", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "RESTRICTED", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) - It("Displays the stack information with state but no reason", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + It("Displays the stack information with state and empty reason", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) - Expect(testUI.Out).NotTo(Say("reason:")) - }) + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) + Expect(testUI.Out).To(Say("reason:")) }) }) + }) When("The Stack does not Exist", func() { expectedError := actionerror.StackNotFoundError{Name: "some-stack-name"} diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go index 264eea8ce3..36598ff398 100644 --- a/command/v7/update_stack_command.go +++ b/command/v7/update_stack_command.go @@ -73,8 +73,8 @@ func (cmd UpdateStackCommand) Execute(args []string) error { {cmd.UI.TranslateText("state:"), updatedStack.State}, } - // Add reason if it's present - if updatedStack.StateReason != "" { + // Add reason whenever state is not ACTIVE + if updatedStack.State != resources.StackStateActive { displayTable = append(displayTable, []string{cmd.UI.TranslateText("reason:"), updatedStack.StateReason}) } diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index fe64ece778..5b32106a39 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -210,8 +210,6 @@ var _ = Describe("update-stack Command", func() { }) It("passes the reason to the actor and displays it", func() { - executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index ab4f00d448..016e6f25b4 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3604,11 +3604,12 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - UpdateStackStub func(string, string) (resources.Stack, v7action.Warnings, error) + UpdateStackStub func(string, string, string) (resources.Stack, v7action.Warnings, error) updateStackMutex sync.RWMutex updateStackArgsForCall []struct { arg1 string arg2 string + arg3 string } updateStackReturns struct { result1 resources.Stack @@ -19443,19 +19444,20 @@ func (fake *FakeActor) UpdateSpaceQuotaReturnsOnCall(i int, result1 v7action.War }{result1, result2} } -func (fake *FakeActor) UpdateStack(arg1 string, arg2 string) (resources.Stack, v7action.Warnings, error) { +func (fake *FakeActor) UpdateStack(arg1 string, arg2 string, arg3 string) (resources.Stack, v7action.Warnings, error) { fake.updateStackMutex.Lock() ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] fake.updateStackArgsForCall = append(fake.updateStackArgsForCall, struct { arg1 string arg2 string - }{arg1, arg2}) + arg3 string + }{arg1, arg2, arg3}) stub := fake.UpdateStackStub fakeReturns := fake.updateStackReturns - fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2}) + fake.recordInvocation("UpdateStack", []interface{}{arg1, arg2, arg3}) fake.updateStackMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2, ret.result3 @@ -19469,17 +19471,17 @@ func (fake *FakeActor) UpdateStackCallCount() int { return len(fake.updateStackArgsForCall) } -func (fake *FakeActor) UpdateStackCalls(stub func(string, string) (resources.Stack, v7action.Warnings, error)) { +func (fake *FakeActor) UpdateStackCalls(stub func(string, string, string) (resources.Stack, v7action.Warnings, error)) { fake.updateStackMutex.Lock() defer fake.updateStackMutex.Unlock() fake.UpdateStackStub = stub } -func (fake *FakeActor) UpdateStackArgsForCall(i int) (string, string) { +func (fake *FakeActor) UpdateStackArgsForCall(i int) (string, string, string) { fake.updateStackMutex.RLock() defer fake.updateStackMutex.RUnlock() argsForCall := fake.updateStackArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeActor) UpdateStackReturns(result1 resources.Stack, result2 v7action.Warnings, result3 error) { From 981ee31fa791dd16cb1ce08da7e9e711ce4cb853 Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Thu, 12 Feb 2026 12:08:09 -0500 Subject: [PATCH 19/20] 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 --- integration/v7/isolated/stack_command_test.go | 45 +++++++++++++++++++ .../v7/isolated/update_stack_command_test.go | 26 +++++++++++ 2 files changed, 71 insertions(+) diff --git a/integration/v7/isolated/stack_command_test.go b/integration/v7/isolated/stack_command_test.go index 4a0abc6302..63a70d6f30 100644 --- a/integration/v7/isolated/stack_command_test.go +++ b/integration/v7/isolated/stack_command_test.go @@ -124,6 +124,15 @@ var _ = Describe("stack command", func() { Eventually(session).Should(Say(`name:\s+%s`, stackName)) Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) Eventually(session).Should(Say(`state:\s+ACTIVE`)) + Consistently(session).ShouldNot(Say(`reason:`)) + Eventually(session).Should(Exit(0)) + }) + + It("does not show reason for an active stack", func() { + session := helpers.CF("stack", stackName) + + Eventually(session).Should(Say(`state:\s+ACTIVE`)) + Consistently(session).ShouldNot(Say(`reason:`)) Eventually(session).Should(Exit(0)) }) @@ -137,6 +146,42 @@ var _ = Describe("stack command", func() { Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`)) Eventually(session).Should(Exit(0)) }) + + When("the stack is in a non-active state without a reason", func() { + BeforeEach(func() { + session := helpers.CF("update-stack", stackName, "--state", "deprecated") + Eventually(session).Should(Exit(0)) + }) + + It("shows an empty reason field", func() { + session := helpers.CF("stack", stackName) + + Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) + Eventually(session).Should(Say(`state:\s+DEPRECATED`)) + Eventually(session).Should(Say(`reason:\s*$`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the stack is in a non-active state with a reason", func() { + BeforeEach(func() { + session := helpers.CF("update-stack", stackName, "--state", "disabled", "--reason", "This stack is no longer supported.") + Eventually(session).Should(Exit(0)) + }) + + It("shows the reason in the output", func() { + session := helpers.CF("stack", stackName) + + Eventually(session).Should(Say(`Getting info for stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`description:\s+%s`, stackDescription)) + Eventually(session).Should(Say(`state:\s+DISABLED`)) + Eventually(session).Should(Say(`reason:\s+This stack is no longer supported\.`)) + Eventually(session).Should(Exit(0)) + }) + }) }) }) }) diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 607b0dc9bd..ed4cf7668b 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -207,6 +207,32 @@ var _ = Describe("update-stack command", func() { }) }) + When("updating to a non-active state without a reason", func() { + It("shows an empty reason field in the output", func() { + session := helpers.CF("update-stack", stackName, "--state", "deprecated") + + Eventually(session).Should(Say(`Updating stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`state:\s+DEPRECATED`)) + Eventually(session).Should(Say(`reason:\s*$`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("updating with a reason", func() { + It("shows the reason in the update-stack output", func() { + session := helpers.CF("update-stack", stackName, "--state", "disabled", "--reason", "This stack is no longer supported.") + + Eventually(session).Should(Say(`Updating stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Say(`name:\s+%s`, stackName)) + Eventually(session).Should(Say(`state:\s+DISABLED`)) + Eventually(session).Should(Say(`reason:\s+This stack is no longer supported\.`)) + Eventually(session).Should(Exit(0)) + }) + }) + When("state value is provided in different cases", func() { It("accepts lowercase state value", func() { session := helpers.CF("update-stack", stackName, "--state", "deprecated") From fa9ad74bd1a0017671e4a5f7ca39dd64bdab2fdc Mon Sep 17 00:00:00 2001 From: Simon Jones Date: Mon, 23 Feb 2026 12:44:26 -0500 Subject: [PATCH 20/20] Regenerate fakeActor Signed-off-by: Simon Jones --- command/v7/v7fakes/fake_actor.go | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 016e6f25b4..09f1d2235a 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -2503,6 +2503,21 @@ type FakeActor struct { result1 resources.User result2 error } + ListServiceAppBindingsStub func(v7action.ListServiceAppBindingParams) ([]resources.ServiceCredentialBinding, v7action.Warnings, error) + listServiceAppBindingsMutex sync.RWMutex + listServiceAppBindingsArgsForCall []struct { + arg1 v7action.ListServiceAppBindingParams + } + listServiceAppBindingsReturns struct { + result1 []resources.ServiceCredentialBinding + result2 v7action.Warnings + result3 error + } + listServiceAppBindingsReturnsOnCall map[int]struct { + result1 []resources.ServiceCredentialBinding + result2 v7action.Warnings + result3 error + } MakeCurlRequestStub func(string, string, []string, string, bool) ([]byte, *http.Response, error) makeCurlRequestMutex sync.RWMutex makeCurlRequestArgsForCall []struct { @@ -14545,6 +14560,73 @@ func (fake *FakeActor) GetUserReturnsOnCall(i int, result1 resources.User, resul }{result1, result2} } +func (fake *FakeActor) ListServiceAppBindings(arg1 v7action.ListServiceAppBindingParams) ([]resources.ServiceCredentialBinding, v7action.Warnings, error) { + fake.listServiceAppBindingsMutex.Lock() + ret, specificReturn := fake.listServiceAppBindingsReturnsOnCall[len(fake.listServiceAppBindingsArgsForCall)] + fake.listServiceAppBindingsArgsForCall = append(fake.listServiceAppBindingsArgsForCall, struct { + arg1 v7action.ListServiceAppBindingParams + }{arg1}) + stub := fake.ListServiceAppBindingsStub + fakeReturns := fake.listServiceAppBindingsReturns + fake.recordInvocation("ListServiceAppBindings", []interface{}{arg1}) + fake.listServiceAppBindingsMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeActor) ListServiceAppBindingsCallCount() int { + fake.listServiceAppBindingsMutex.RLock() + defer fake.listServiceAppBindingsMutex.RUnlock() + return len(fake.listServiceAppBindingsArgsForCall) +} + +func (fake *FakeActor) ListServiceAppBindingsCalls(stub func(v7action.ListServiceAppBindingParams) ([]resources.ServiceCredentialBinding, v7action.Warnings, error)) { + fake.listServiceAppBindingsMutex.Lock() + defer fake.listServiceAppBindingsMutex.Unlock() + fake.ListServiceAppBindingsStub = stub +} + +func (fake *FakeActor) ListServiceAppBindingsArgsForCall(i int) v7action.ListServiceAppBindingParams { + fake.listServiceAppBindingsMutex.RLock() + defer fake.listServiceAppBindingsMutex.RUnlock() + argsForCall := fake.listServiceAppBindingsArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeActor) ListServiceAppBindingsReturns(result1 []resources.ServiceCredentialBinding, result2 v7action.Warnings, result3 error) { + fake.listServiceAppBindingsMutex.Lock() + defer fake.listServiceAppBindingsMutex.Unlock() + fake.ListServiceAppBindingsStub = nil + fake.listServiceAppBindingsReturns = struct { + result1 []resources.ServiceCredentialBinding + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) ListServiceAppBindingsReturnsOnCall(i int, result1 []resources.ServiceCredentialBinding, result2 v7action.Warnings, result3 error) { + fake.listServiceAppBindingsMutex.Lock() + defer fake.listServiceAppBindingsMutex.Unlock() + fake.ListServiceAppBindingsStub = nil + if fake.listServiceAppBindingsReturnsOnCall == nil { + fake.listServiceAppBindingsReturnsOnCall = make(map[int]struct { + result1 []resources.ServiceCredentialBinding + result2 v7action.Warnings + result3 error + }) + } + fake.listServiceAppBindingsReturnsOnCall[i] = struct { + result1 []resources.ServiceCredentialBinding + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeActor) MakeCurlRequest(arg1 string, arg2 string, arg3 []string, arg4 string, arg5 bool) ([]byte, *http.Response, error) { var arg3Copy []string if arg3 != nil {