diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index 5a75ce37aa0..d102b7a460f 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -138,6 +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) 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.go b/actor/v7action/stack.go index 43c891947fd..d4e9a5eae3d 100644 --- a/actor/v7action/stack.go +++ b/actor/v7action/stack.go @@ -46,3 +46,12 @@ 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) + if err != nil { + return resources.Stack{}, Warnings(warnings), err + } + + return stack, Warnings(warnings), nil +} diff --git a/actor/v7action/stack_test.go b/actor/v7action/stack_test.go index 84efcd99631..c2d11f880a8 100644 --- a/actor/v7action/stack_test.go +++ b/actor/v7action/stack_test.go @@ -69,7 +69,7 @@ var _ = Describe("Stack", func() { Context("there are no errors", func() { - When("the stack exists", func() { + When("the stack exists without state", func() { expectedStack := resources.Stack{ GUID: "some-stack-guid", Name: "some-stack-name", @@ -98,6 +98,43 @@ 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("")) + Expect(err).To(BeNil()) + Expect(warnings).To(ConsistOf("warning-1", "warning-2")) + }) + }) + + When("the stack exists with state", func() { + expectedStack := resources.Stack{ + GUID: "some-stack-guid", + Name: "some-stack-name", + Description: "Some stack desc", + State: "ACTIVE", + } + + expectedParams := []ccv3.Query{ + {Key: ccv3.NameFilter, Values: []string{"some-stack-name"}}, + {Key: ccv3.PerPage, Values: []string{"1"}}, + {Key: ccv3.Page, Values: []string{"1"}}, + } + + BeforeEach(func() { + fakeCloudControllerClient.GetStacksReturns( + []resources.Stack{expectedStack}, + ccv3.Warnings{"warning-1", "warning-2"}, + nil, + ) + }) + + It("returns the desired stack with state", func() { + + actualParams := fakeCloudControllerClient.GetStacksArgsForCall(0) + Expect(actualParams).To(Equal(expectedParams)) + Expect(fakeCloudControllerClient.GetStacksCallCount()).To(Equal(1)) + 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(err).To(BeNil()) Expect(warnings).To(ConsistOf("warning-1", "warning-2")) }) @@ -196,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 da11432a92e..44eb02a3bbf 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2709,6 +2709,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 + } UpdateTaskCancelStub func(string) (resources.Task, ccv3.Warnings, error) updateTaskCancelMutex sync.RWMutex updateTaskCancelArgsForCall []struct { @@ -14785,6 +14801,74 @@ func (fake *FakeCloudControllerClient) UpdateSpaceQuotaReturnsOnCall(i int, resu }{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) UpdateTaskCancel(arg1 string) (resources.Task, ccv3.Warnings, error) { fake.updateTaskCancelMutex.Lock() ret, specificReturn := fake.updateTaskCancelReturnsOnCall[len(fake.updateTaskCancelArgsForCall)] @@ -15197,376 +15281,6 @@ func (fake *FakeCloudControllerClient) WhoAmIReturnsOnCall(i int, result1 resour func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.applyOrganizationQuotaMutex.RLock() - defer fake.applyOrganizationQuotaMutex.RUnlock() - fake.applySpaceQuotaMutex.RLock() - defer fake.applySpaceQuotaMutex.RUnlock() - fake.cancelDeploymentMutex.RLock() - defer fake.cancelDeploymentMutex.RUnlock() - fake.checkRouteMutex.RLock() - defer fake.checkRouteMutex.RUnlock() - fake.continueDeploymentMutex.RLock() - defer fake.continueDeploymentMutex.RUnlock() - fake.copyPackageMutex.RLock() - defer fake.copyPackageMutex.RUnlock() - fake.createApplicationMutex.RLock() - defer fake.createApplicationMutex.RUnlock() - fake.createApplicationDeploymentMutex.RLock() - defer fake.createApplicationDeploymentMutex.RUnlock() - fake.createApplicationProcessScaleMutex.RLock() - defer fake.createApplicationProcessScaleMutex.RUnlock() - fake.createApplicationTaskMutex.RLock() - defer fake.createApplicationTaskMutex.RUnlock() - fake.createBuildMutex.RLock() - defer fake.createBuildMutex.RUnlock() - fake.createBuildpackMutex.RLock() - defer fake.createBuildpackMutex.RUnlock() - fake.createDomainMutex.RLock() - defer fake.createDomainMutex.RUnlock() - fake.createDropletMutex.RLock() - defer fake.createDropletMutex.RUnlock() - fake.createIsolationSegmentMutex.RLock() - defer fake.createIsolationSegmentMutex.RUnlock() - fake.createOrganizationMutex.RLock() - defer fake.createOrganizationMutex.RUnlock() - fake.createOrganizationQuotaMutex.RLock() - defer fake.createOrganizationQuotaMutex.RUnlock() - fake.createPackageMutex.RLock() - defer fake.createPackageMutex.RUnlock() - fake.createRoleMutex.RLock() - defer fake.createRoleMutex.RUnlock() - fake.createRouteMutex.RLock() - defer fake.createRouteMutex.RUnlock() - fake.createRouteBindingMutex.RLock() - defer fake.createRouteBindingMutex.RUnlock() - fake.createSecurityGroupMutex.RLock() - defer fake.createSecurityGroupMutex.RUnlock() - fake.createServiceBrokerMutex.RLock() - defer fake.createServiceBrokerMutex.RUnlock() - fake.createServiceCredentialBindingMutex.RLock() - defer fake.createServiceCredentialBindingMutex.RUnlock() - fake.createServiceInstanceMutex.RLock() - defer fake.createServiceInstanceMutex.RUnlock() - fake.createSpaceMutex.RLock() - defer fake.createSpaceMutex.RUnlock() - fake.createSpaceQuotaMutex.RLock() - defer fake.createSpaceQuotaMutex.RUnlock() - fake.createUserMutex.RLock() - defer fake.createUserMutex.RUnlock() - fake.deleteApplicationMutex.RLock() - defer fake.deleteApplicationMutex.RUnlock() - fake.deleteApplicationProcessInstanceMutex.RLock() - defer fake.deleteApplicationProcessInstanceMutex.RUnlock() - fake.deleteBuildpackMutex.RLock() - defer fake.deleteBuildpackMutex.RUnlock() - fake.deleteDomainMutex.RLock() - defer fake.deleteDomainMutex.RUnlock() - fake.deleteIsolationSegmentMutex.RLock() - defer fake.deleteIsolationSegmentMutex.RUnlock() - fake.deleteIsolationSegmentOrganizationMutex.RLock() - defer fake.deleteIsolationSegmentOrganizationMutex.RUnlock() - fake.deleteOrganizationMutex.RLock() - defer fake.deleteOrganizationMutex.RUnlock() - fake.deleteOrganizationQuotaMutex.RLock() - defer fake.deleteOrganizationQuotaMutex.RUnlock() - fake.deleteOrphanedRoutesMutex.RLock() - defer fake.deleteOrphanedRoutesMutex.RUnlock() - fake.deleteRoleMutex.RLock() - defer fake.deleteRoleMutex.RUnlock() - fake.deleteRouteMutex.RLock() - defer fake.deleteRouteMutex.RUnlock() - fake.deleteRouteBindingMutex.RLock() - defer fake.deleteRouteBindingMutex.RUnlock() - fake.deleteSecurityGroupMutex.RLock() - defer fake.deleteSecurityGroupMutex.RUnlock() - fake.deleteServiceBrokerMutex.RLock() - defer fake.deleteServiceBrokerMutex.RUnlock() - fake.deleteServiceCredentialBindingMutex.RLock() - defer fake.deleteServiceCredentialBindingMutex.RUnlock() - fake.deleteServiceInstanceMutex.RLock() - defer fake.deleteServiceInstanceMutex.RUnlock() - fake.deleteServicePlanVisibilityMutex.RLock() - defer fake.deleteServicePlanVisibilityMutex.RUnlock() - fake.deleteSpaceMutex.RLock() - defer fake.deleteSpaceMutex.RUnlock() - fake.deleteSpaceQuotaMutex.RLock() - defer fake.deleteSpaceQuotaMutex.RUnlock() - fake.deleteUserMutex.RLock() - defer fake.deleteUserMutex.RUnlock() - fake.downloadDropletMutex.RLock() - defer fake.downloadDropletMutex.RUnlock() - fake.entitleIsolationSegmentToOrganizationsMutex.RLock() - defer fake.entitleIsolationSegmentToOrganizationsMutex.RUnlock() - fake.getAppFeatureMutex.RLock() - defer fake.getAppFeatureMutex.RUnlock() - fake.getApplicationByNameAndSpaceMutex.RLock() - defer fake.getApplicationByNameAndSpaceMutex.RUnlock() - fake.getApplicationDropletCurrentMutex.RLock() - defer fake.getApplicationDropletCurrentMutex.RUnlock() - fake.getApplicationEnvironmentMutex.RLock() - defer fake.getApplicationEnvironmentMutex.RUnlock() - fake.getApplicationManifestMutex.RLock() - defer fake.getApplicationManifestMutex.RUnlock() - fake.getApplicationProcessByTypeMutex.RLock() - defer fake.getApplicationProcessByTypeMutex.RUnlock() - fake.getApplicationProcessesMutex.RLock() - defer fake.getApplicationProcessesMutex.RUnlock() - fake.getApplicationRevisionsMutex.RLock() - defer fake.getApplicationRevisionsMutex.RUnlock() - fake.getApplicationRevisionsDeployedMutex.RLock() - defer fake.getApplicationRevisionsDeployedMutex.RUnlock() - fake.getApplicationRoutesMutex.RLock() - defer fake.getApplicationRoutesMutex.RUnlock() - fake.getApplicationTasksMutex.RLock() - defer fake.getApplicationTasksMutex.RUnlock() - fake.getApplicationsMutex.RLock() - defer fake.getApplicationsMutex.RUnlock() - fake.getBuildMutex.RLock() - defer fake.getBuildMutex.RUnlock() - fake.getBuildpacksMutex.RLock() - defer fake.getBuildpacksMutex.RUnlock() - fake.getDefaultDomainMutex.RLock() - defer fake.getDefaultDomainMutex.RUnlock() - fake.getDeploymentMutex.RLock() - defer fake.getDeploymentMutex.RUnlock() - fake.getDeploymentsMutex.RLock() - defer fake.getDeploymentsMutex.RUnlock() - fake.getDomainMutex.RLock() - defer fake.getDomainMutex.RUnlock() - fake.getDomainsMutex.RLock() - defer fake.getDomainsMutex.RUnlock() - fake.getDropletMutex.RLock() - defer fake.getDropletMutex.RUnlock() - fake.getDropletsMutex.RLock() - defer fake.getDropletsMutex.RUnlock() - fake.getEnvironmentVariableGroupMutex.RLock() - defer fake.getEnvironmentVariableGroupMutex.RUnlock() - fake.getEnvironmentVariablesByURLMutex.RLock() - defer fake.getEnvironmentVariablesByURLMutex.RUnlock() - fake.getEventsMutex.RLock() - defer fake.getEventsMutex.RUnlock() - fake.getFeatureFlagMutex.RLock() - defer fake.getFeatureFlagMutex.RUnlock() - fake.getFeatureFlagsMutex.RLock() - defer fake.getFeatureFlagsMutex.RUnlock() - fake.getInfoMutex.RLock() - defer fake.getInfoMutex.RUnlock() - fake.getIsolationSegmentMutex.RLock() - defer fake.getIsolationSegmentMutex.RUnlock() - fake.getIsolationSegmentOrganizationsMutex.RLock() - defer fake.getIsolationSegmentOrganizationsMutex.RUnlock() - fake.getIsolationSegmentsMutex.RLock() - defer fake.getIsolationSegmentsMutex.RUnlock() - fake.getNewApplicationProcessesMutex.RLock() - defer fake.getNewApplicationProcessesMutex.RUnlock() - fake.getOrganizationMutex.RLock() - defer fake.getOrganizationMutex.RUnlock() - fake.getOrganizationDefaultIsolationSegmentMutex.RLock() - defer fake.getOrganizationDefaultIsolationSegmentMutex.RUnlock() - fake.getOrganizationDomainsMutex.RLock() - defer fake.getOrganizationDomainsMutex.RUnlock() - fake.getOrganizationQuotaMutex.RLock() - defer fake.getOrganizationQuotaMutex.RUnlock() - fake.getOrganizationQuotasMutex.RLock() - defer fake.getOrganizationQuotasMutex.RUnlock() - fake.getOrganizationsMutex.RLock() - defer fake.getOrganizationsMutex.RUnlock() - fake.getPackageMutex.RLock() - defer fake.getPackageMutex.RUnlock() - fake.getPackageDropletsMutex.RLock() - defer fake.getPackageDropletsMutex.RUnlock() - fake.getPackagesMutex.RLock() - defer fake.getPackagesMutex.RUnlock() - fake.getProcessMutex.RLock() - defer fake.getProcessMutex.RUnlock() - fake.getProcessInstancesMutex.RLock() - defer fake.getProcessInstancesMutex.RUnlock() - fake.getProcessSidecarsMutex.RLock() - defer fake.getProcessSidecarsMutex.RUnlock() - fake.getProcessesMutex.RLock() - defer fake.getProcessesMutex.RUnlock() - fake.getRolesMutex.RLock() - defer fake.getRolesMutex.RUnlock() - fake.getRootMutex.RLock() - defer fake.getRootMutex.RUnlock() - fake.getRouteBindingsMutex.RLock() - defer fake.getRouteBindingsMutex.RUnlock() - fake.getRouteDestinationsMutex.RLock() - defer fake.getRouteDestinationsMutex.RUnlock() - fake.getRoutesMutex.RLock() - defer fake.getRoutesMutex.RUnlock() - fake.getRunningSecurityGroupsMutex.RLock() - defer fake.getRunningSecurityGroupsMutex.RUnlock() - fake.getSSHEnabledMutex.RLock() - defer fake.getSSHEnabledMutex.RUnlock() - fake.getSecurityGroupsMutex.RLock() - defer fake.getSecurityGroupsMutex.RUnlock() - fake.getServiceBrokersMutex.RLock() - defer fake.getServiceBrokersMutex.RUnlock() - fake.getServiceCredentialBindingDetailsMutex.RLock() - defer fake.getServiceCredentialBindingDetailsMutex.RUnlock() - fake.getServiceCredentialBindingsMutex.RLock() - defer fake.getServiceCredentialBindingsMutex.RUnlock() - fake.getServiceInstanceByNameAndSpaceMutex.RLock() - defer fake.getServiceInstanceByNameAndSpaceMutex.RUnlock() - fake.getServiceInstanceParametersMutex.RLock() - defer fake.getServiceInstanceParametersMutex.RUnlock() - fake.getServiceInstanceSharedSpacesMutex.RLock() - defer fake.getServiceInstanceSharedSpacesMutex.RUnlock() - fake.getServiceInstanceUsageSummaryMutex.RLock() - defer fake.getServiceInstanceUsageSummaryMutex.RUnlock() - fake.getServiceInstancesMutex.RLock() - defer fake.getServiceInstancesMutex.RUnlock() - fake.getServiceOfferingByGUIDMutex.RLock() - defer fake.getServiceOfferingByGUIDMutex.RUnlock() - fake.getServiceOfferingByNameAndBrokerMutex.RLock() - defer fake.getServiceOfferingByNameAndBrokerMutex.RUnlock() - fake.getServiceOfferingsMutex.RLock() - defer fake.getServiceOfferingsMutex.RUnlock() - fake.getServicePlanByGUIDMutex.RLock() - defer fake.getServicePlanByGUIDMutex.RUnlock() - fake.getServicePlanVisibilityMutex.RLock() - defer fake.getServicePlanVisibilityMutex.RUnlock() - fake.getServicePlansMutex.RLock() - defer fake.getServicePlansMutex.RUnlock() - fake.getServicePlansWithOfferingsMutex.RLock() - defer fake.getServicePlansWithOfferingsMutex.RUnlock() - fake.getServicePlansWithSpaceAndOrganizationMutex.RLock() - defer fake.getServicePlansWithSpaceAndOrganizationMutex.RUnlock() - fake.getSpaceFeatureMutex.RLock() - defer fake.getSpaceFeatureMutex.RUnlock() - fake.getSpaceIsolationSegmentMutex.RLock() - defer fake.getSpaceIsolationSegmentMutex.RUnlock() - fake.getSpaceManifestDiffMutex.RLock() - defer fake.getSpaceManifestDiffMutex.RUnlock() - fake.getSpaceQuotaMutex.RLock() - defer fake.getSpaceQuotaMutex.RUnlock() - fake.getSpaceQuotasMutex.RLock() - defer fake.getSpaceQuotasMutex.RUnlock() - fake.getSpacesMutex.RLock() - defer fake.getSpacesMutex.RUnlock() - fake.getStacksMutex.RLock() - defer fake.getStacksMutex.RUnlock() - fake.getStagingSecurityGroupsMutex.RLock() - defer fake.getStagingSecurityGroupsMutex.RUnlock() - fake.getTaskMutex.RLock() - defer fake.getTaskMutex.RUnlock() - fake.getUserMutex.RLock() - defer fake.getUserMutex.RUnlock() - fake.getUsersMutex.RLock() - defer fake.getUsersMutex.RUnlock() - fake.makeRequestSendReceiveRawMutex.RLock() - defer fake.makeRequestSendReceiveRawMutex.RUnlock() - fake.mapRouteMutex.RLock() - defer fake.mapRouteMutex.RUnlock() - fake.moveRouteMutex.RLock() - defer fake.moveRouteMutex.RUnlock() - fake.pollJobMutex.RLock() - defer fake.pollJobMutex.RUnlock() - fake.pollJobForStateMutex.RLock() - defer fake.pollJobForStateMutex.RUnlock() - fake.pollJobToEventStreamMutex.RLock() - defer fake.pollJobToEventStreamMutex.RUnlock() - fake.purgeServiceOfferingMutex.RLock() - defer fake.purgeServiceOfferingMutex.RUnlock() - fake.resourceMatchMutex.RLock() - defer fake.resourceMatchMutex.RUnlock() - fake.rootResponseMutex.RLock() - defer fake.rootResponseMutex.RUnlock() - fake.setApplicationDropletMutex.RLock() - defer fake.setApplicationDropletMutex.RUnlock() - fake.sharePrivateDomainToOrgsMutex.RLock() - defer fake.sharePrivateDomainToOrgsMutex.RUnlock() - fake.shareRouteMutex.RLock() - defer fake.shareRouteMutex.RUnlock() - fake.shareServiceInstanceToSpacesMutex.RLock() - defer fake.shareServiceInstanceToSpacesMutex.RUnlock() - fake.targetCFMutex.RLock() - defer fake.targetCFMutex.RUnlock() - fake.unbindSecurityGroupRunningSpaceMutex.RLock() - defer fake.unbindSecurityGroupRunningSpaceMutex.RUnlock() - fake.unbindSecurityGroupStagingSpaceMutex.RLock() - defer fake.unbindSecurityGroupStagingSpaceMutex.RUnlock() - fake.unmapRouteMutex.RLock() - defer fake.unmapRouteMutex.RUnlock() - fake.unsetSpaceQuotaMutex.RLock() - defer fake.unsetSpaceQuotaMutex.RUnlock() - fake.unsharePrivateDomainFromOrgMutex.RLock() - defer fake.unsharePrivateDomainFromOrgMutex.RUnlock() - fake.unshareRouteMutex.RLock() - defer fake.unshareRouteMutex.RUnlock() - fake.unshareServiceInstanceFromSpaceMutex.RLock() - defer fake.unshareServiceInstanceFromSpaceMutex.RUnlock() - fake.updateAppFeatureMutex.RLock() - defer fake.updateAppFeatureMutex.RUnlock() - fake.updateApplicationMutex.RLock() - defer fake.updateApplicationMutex.RUnlock() - fake.updateApplicationApplyManifestMutex.RLock() - defer fake.updateApplicationApplyManifestMutex.RUnlock() - fake.updateApplicationEnvironmentVariablesMutex.RLock() - defer fake.updateApplicationEnvironmentVariablesMutex.RUnlock() - fake.updateApplicationNameMutex.RLock() - defer fake.updateApplicationNameMutex.RUnlock() - fake.updateApplicationRestartMutex.RLock() - defer fake.updateApplicationRestartMutex.RUnlock() - fake.updateApplicationStartMutex.RLock() - defer fake.updateApplicationStartMutex.RUnlock() - fake.updateApplicationStopMutex.RLock() - defer fake.updateApplicationStopMutex.RUnlock() - fake.updateBuildpackMutex.RLock() - defer fake.updateBuildpackMutex.RUnlock() - fake.updateDestinationMutex.RLock() - defer fake.updateDestinationMutex.RUnlock() - fake.updateEnvironmentVariableGroupMutex.RLock() - defer fake.updateEnvironmentVariableGroupMutex.RUnlock() - fake.updateFeatureFlagMutex.RLock() - defer fake.updateFeatureFlagMutex.RUnlock() - fake.updateOrganizationMutex.RLock() - defer fake.updateOrganizationMutex.RUnlock() - fake.updateOrganizationDefaultIsolationSegmentRelationshipMutex.RLock() - defer fake.updateOrganizationDefaultIsolationSegmentRelationshipMutex.RUnlock() - fake.updateOrganizationQuotaMutex.RLock() - defer fake.updateOrganizationQuotaMutex.RUnlock() - fake.updateProcessMutex.RLock() - defer fake.updateProcessMutex.RUnlock() - fake.updateResourceMetadataMutex.RLock() - defer fake.updateResourceMetadataMutex.RUnlock() - fake.updateRouteMutex.RLock() - defer fake.updateRouteMutex.RUnlock() - fake.updateSecurityGroupMutex.RLock() - defer fake.updateSecurityGroupMutex.RUnlock() - fake.updateSecurityGroupRunningSpaceMutex.RLock() - defer fake.updateSecurityGroupRunningSpaceMutex.RUnlock() - fake.updateSecurityGroupStagingSpaceMutex.RLock() - defer fake.updateSecurityGroupStagingSpaceMutex.RUnlock() - fake.updateServiceBrokerMutex.RLock() - defer fake.updateServiceBrokerMutex.RUnlock() - fake.updateServiceInstanceMutex.RLock() - defer fake.updateServiceInstanceMutex.RUnlock() - fake.updateServicePlanVisibilityMutex.RLock() - defer fake.updateServicePlanVisibilityMutex.RUnlock() - fake.updateSpaceMutex.RLock() - defer fake.updateSpaceMutex.RUnlock() - fake.updateSpaceApplyManifestMutex.RLock() - defer fake.updateSpaceApplyManifestMutex.RUnlock() - fake.updateSpaceFeatureMutex.RLock() - defer fake.updateSpaceFeatureMutex.RUnlock() - fake.updateSpaceIsolationSegmentRelationshipMutex.RLock() - defer fake.updateSpaceIsolationSegmentRelationshipMutex.RUnlock() - fake.updateSpaceQuotaMutex.RLock() - defer fake.updateSpaceQuotaMutex.RUnlock() - fake.updateTaskCancelMutex.RLock() - defer fake.updateTaskCancelMutex.RUnlock() - fake.uploadBitsPackageMutex.RLock() - defer fake.uploadBitsPackageMutex.RUnlock() - fake.uploadBuildpackMutex.RLock() - defer fake.uploadBuildpackMutex.RUnlock() - fake.uploadDropletBitsMutex.RLock() - defer fake.uploadDropletBitsMutex.RUnlock() - fake.uploadPackageMutex.RLock() - defer fake.uploadPackageMutex.RUnlock() - fake.whoAmIMutex.RLock() - defer fake.whoAmIMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/api/cloudcontroller/ccv3/stack.go b/api/cloudcontroller/ccv3/stack.go index d403f44030c..958eb10f6ed 100644 --- a/api/cloudcontroller/ccv3/stack.go +++ b/api/cloudcontroller/ccv3/stack.go @@ -21,3 +21,21 @@ 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) { + var responseStack resources.Stack + + type StackUpdate struct { + State string `json:"state"` + } + + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.PatchStackRequest, + URIParams: internal.Params{"stack_guid": stackGUID}, + RequestBody: StackUpdate{State: state}, + ResponseBody: &responseStack, + }) + + return responseStack, warnings, err +} diff --git a/api/cloudcontroller/ccv3/stack_test.go b/api/cloudcontroller/ccv3/stack_test.go index 0485525646b..1787178ef99 100644 --- a/api/cloudcontroller/ccv3/stack_test.go +++ b/api/cloudcontroller/ccv3/stack_test.go @@ -42,9 +42,10 @@ var _ = Describe("Stacks", func() { }, "resources": [ { - "name": "stack-name-1", - "guid": "stack-guid-1", - "description": "stack desc 1" + "name": "stack-name-1", + "guid": "stack-guid-1", + "description": "stack desc 1", + "state": "ACTIVE" }, { "name": "stack-name-2", @@ -61,7 +62,8 @@ var _ = Describe("Stacks", func() { { "name": "stack-name-3", "guid": "stack-guid-3", - "description": "stack desc 3" + "description": "stack desc 3", + "state": "DEPRECATED" } ] }` @@ -89,9 +91,9 @@ var _ = Describe("Stacks", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(stacks).To(ConsistOf( - resources.Stack{Name: "stack-name-1", GUID: "stack-guid-1", Description: "stack desc 1"}, - resources.Stack{Name: "stack-name-2", GUID: "stack-guid-2", Description: "stack desc 2"}, - resources.Stack{Name: "stack-name-3", GUID: "stack-guid-3", Description: "stack desc 3"}, + resources.Stack{Name: "stack-name-1", GUID: "stack-guid-1", Description: "stack desc 1", State: "ACTIVE"}, + resources.Stack{Name: "stack-name-2", GUID: "stack-guid-2", Description: "stack desc 2", State: ""}, + resources.Stack{Name: "stack-name-3", GUID: "stack-guid-3", Description: "stack desc 3", State: "DEPRECATED"}, )) Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) }) @@ -141,4 +143,69 @@ var _ = Describe("Stacks", func() { }) }) }) + + Describe("UpdateStack", func() { + var ( + stackGUID string + state string + stack resources.Stack + warnings Warnings + err error + ) + + BeforeEach(func() { + stackGUID = "some-stack-guid" + state = "DEPRECATED" + }) + + JustBeforeEach(func() { + stack, warnings, err = client.UpdateStack(stackGUID, state) + }) + + When("the request succeeds", func() { + BeforeEach(func() { + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodPatch, "/v3/stacks/some-stack-guid"), + VerifyJSONRepresenting(map[string]string{ + "state": "DEPRECATED", + }), + RespondWith(http.StatusOK, `{ + "guid": "some-stack-guid", + "name": "some-stack", + "description": "some description", + "state": "DEPRECATED" + }`, http.Header{"X-Cf-Warnings": {"this is a warning"}}), + ), + ) + }) + + It("returns the updated stack 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", + })) + }) + }) + + When("the cloud controller returns an error", func() { + BeforeEach(func() { + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodPatch, "/v3/stacks/some-stack-guid"), + RespondWith(http.StatusNotFound, `{"errors":[{"detail":"Stack not found"}]}`, http.Header{"X-Cf-Warnings": {"this is a warning"}}), + ), + ) + }) + + It("returns the error and warnings", func() { + Expect(err).To(HaveOccurred()) + Expect(warnings).To(ConsistOf("this is a warning")) + }) + }) + }) }) diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 6ab37305d7c..661b92c5b99 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -167,8 +167,8 @@ type commandList struct { SpaceSSHAllowed v7.SpaceSSHAllowedCommand `command:"space-ssh-allowed" description:"Reports whether SSH is allowed in a space"` SpaceUsers v7.SpaceUsersCommand `command:"space-users" description:"Show space users by role"` Spaces v7.SpacesCommand `command:"spaces" description:"List all spaces in an org"` - Stack v7.StackCommand `command:"stack" description:"Show information for a stack (a stack is a pre-built file system, including an operating system, that can run apps)"` - Stacks v7.StacksCommand `command:"stacks" description:"List all stacks (a stack is a pre-built file system, including an operating system, that can run apps)"` + Stack v7.StackCommand `command:"stack" description:"Show information for a stack (a stack is a pre-built file system, including an operating system, that can run apps) and current state"` + Stacks v7.StacksCommand `command:"stacks" description:"List all stacks (a stack is a pre-built file system, including an operating system, that can run apps) and current state"` StagingEnvironmentVariableGroup v7.StagingEnvironmentVariableGroupCommand `command:"staging-environment-variable-group" alias:"sevg" description:"Retrieve the contents of the staging environment variable group"` StagingSecurityGroups v7.StagingSecurityGroupsCommand `command:"staging-security-groups" description:"List security groups globally configured for staging applications"` Start v7.StartCommand `command:"start" alias:"st" description:"Start an app"` @@ -201,6 +201,7 @@ type commandList struct { UpgradeService v7.UpgradeServiceCommand `command:"upgrade-service" description:"Upgrade a service instance to the latest available version of its current service plan"` UpdateServiceBroker v7.UpdateServiceBrokerCommand `command:"update-service-broker" description:"Update a service broker"` UpdateSpaceQuota v7.UpdateSpaceQuotaCommand `command:"update-space-quota" description:"Update an existing space quota"` + UpdateStack v7.UpdateStackCommand `command:"update-stack" description:"Transition a stack between the defined states"` UpdateUserProvidedService v7.UpdateUserProvidedServiceCommand `command:"update-user-provided-service" alias:"uups" description:"Update user-provided service instance"` Version VersionCommand `command:"version" description:"Print the version"` } diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 9bbd28714a6..3fdf589f33b 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"}, - {"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/translatableerror/invalid_stack_state_error.go b/command/translatableerror/invalid_stack_state_error.go new file mode 100644 index 00000000000..75b87b12a31 --- /dev/null +++ b/command/translatableerror/invalid_stack_state_error.go @@ -0,0 +1,15 @@ +package translatableerror + +type InvalidStackStateError struct { + State string +} + +func (InvalidStackStateError) Error() string { + return "Invalid stack state: {{.State}}. Expected one of: ACTIVE, RESTRICTED, DEPRECATED, DISABLED" +} + +func (e InvalidStackStateError) Translate(translate func(string, ...interface{}) string) string { + return translate(e.Error(), map[string]interface{}{ + "State": e.State, + }) +} diff --git a/command/v7/actor.go b/command/v7/actor.go index 31b26c3741d..87b984cffa2 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -179,6 +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) 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 fd2cd3777ed..ff9f57f742c 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 { @@ -60,9 +60,17 @@ func (cmd *StackCommand) displayStackInfo() error { return err } - cmd.UI.DisplayKeyValueTable("", [][]string{ + // Build display table + displayTable := [][]string{ {cmd.UI.TranslateText("name:"), stack.Name}, {cmd.UI.TranslateText("description:"), stack.Description}, - }, 3) + } + + // Add state only if it's present + if stack.State != "" { + displayTable = append(displayTable, []string{cmd.UI.TranslateText("state:"), stack.State}) + } + + cmd.UI.DisplayKeyValueTable("", displayTable, 3) return nil } diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 685e09ae796..953bf12519e 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -127,6 +127,41 @@ var _ = Describe("Stack Command", func() { }) }) + Context("When the stack has no state", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + } + 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)) + }) + }) + + 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) + }) + + 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)) + }) + }) + When("The Stack does not Exist", func() { expectedError := actionerror.StackNotFoundError{Name: "some-stack-name"} BeforeEach(func() { diff --git a/command/v7/stacks_command.go b/command/v7/stacks_command.go index 88414f54913..940ead844d3 100644 --- a/command/v7/stacks_command.go +++ b/command/v7/stacks_command.go @@ -12,7 +12,7 @@ type StacksCommand struct { BaseCommand usage interface{} `usage:"CF_NAME stacks [--labels SELECTOR]\n\nEXAMPLES:\n CF_NAME stacks\n CF_NAME stacks --labels 'environment in (production,staging),tier in (backend)'\n CF_NAME stacks --labels 'env=dev,!chargeback-code,tier in (backend,worker)'"` - relatedCommands interface{} `related_commands:"create-buildpack, delete-buildpack, stack, update-buildpack"` + relatedCommands interface{} `related_commands:"create-buildpack, delete-buildpack, stack, update-buildpack, update-stack"` Labels string `long:"labels" description:"Selector to filter stacks by labels"` } @@ -47,11 +47,34 @@ func (cmd StacksCommand) Execute(args []string) error { func (cmd StacksCommand) displayTable(stacks []resources.Stack) { if len(stacks) > 0 { - var keyValueTable = [][]string{ - {"name", "description"}, + // Check if any stack has a state value + hasState := false + for _, stack := range stacks { + if stack.State != "" { + hasState = true + break + } + } + + // Build the header based on whether state is present + var keyValueTable [][]string + if hasState { + keyValueTable = [][]string{ + {"name", "description", "state"}, + } + } else { + keyValueTable = [][]string{ + {"name", "description"}, + } } + + // Build the rows for _, stack := range stacks { - keyValueTable = append(keyValueTable, []string{stack.Name, stack.Description}) + if hasState { + keyValueTable = append(keyValueTable, []string{stack.Name, stack.Description, stack.State}) + } else { + keyValueTable = append(keyValueTable, []string{stack.Name, stack.Description}) + } } cmd.UI.DisplayTableWithHeader("", keyValueTable, ui.DefaultTableSpacePadding) diff --git a/command/v7/stacks_command_test.go b/command/v7/stacks_command_test.go index f182d57b0e3..7a2f4265343 100644 --- a/command/v7/stacks_command_test.go +++ b/command/v7/stacks_command_test.go @@ -96,38 +96,66 @@ var _ = Describe("stacks Command", func() { }) When("StacksActor does not return an error", func() { - BeforeEach(func() { - stacks := []resources.Stack{ - {Name: "Stack2", Description: "desc2"}, - {Name: "stack1", Description: "desc1"}, - } - fakeActor.GetStacksReturns(stacks, v7action.Warnings{"warning-1", "warning-2"}, nil) - }) - - When("the --labels flag is given", func() { - labelsFlagValue := "some-label-selector" + Context("when stacks do not have state", func() { BeforeEach(func() { - cmd.Labels = labelsFlagValue + stacks := []resources.Stack{ + {Name: "Stack2", Description: "desc2"}, + {Name: "stack1", Description: "desc1"}, + } + fakeActor.GetStacksReturns(stacks, v7action.Warnings{"warning-1", "warning-2"}, nil) }) - It("passes the label selector to GetStacks", func() { - labelSelector := fakeActor.GetStacksArgsForCall(0) - Expect(labelSelector).To(Equal(labelsFlagValue)) + + When("the --labels flag is given", func() { + labelsFlagValue := "some-label-selector" + BeforeEach(func() { + cmd.Labels = labelsFlagValue + }) + It("passes the label selector to GetStacks", func() { + labelSelector := fakeActor.GetStacksArgsForCall(0) + Expect(labelSelector).To(Equal(labelsFlagValue)) + }) }) - }) - It("prints warnings", func() { - Expect(testUI.Err).To(Say(`warning-1`)) - Expect(testUI.Err).To(Say(`warning-2`)) - }) + It("prints warnings", func() { + Expect(testUI.Err).To(Say(`warning-1`)) + Expect(testUI.Err).To(Say(`warning-2`)) + }) + + It("prints the list of stacks in alphabetical order without state column", func() { + Expect(testUI.Out).To(Say(tableHeaders)) + Expect(testUI.Out).To(Say(`stack1\s+desc1`)) + Expect(testUI.Out).To(Say(`Stack2\s+desc2`)) + Expect(testUI.Out).ToNot(Say(`state`)) + }) - It("prints the list of stacks in alphabetical order", func() { - Expect(testUI.Out).To(Say(tableHeaders)) - Expect(testUI.Out).To(Say(`stack1\s+desc1`)) - Expect(testUI.Out).To(Say(`Stack2\s+desc2`)) + It("prints the flavor text", func() { + Expect(testUI.Out).To(Say("Getting stacks as banana\\.\\.\\.")) + }) }) - It("prints the flavor text", func() { - Expect(testUI.Out).To(Say("Getting stacks as banana\\.\\.\\.")) + Context("when stacks have state", func() { + BeforeEach(func() { + stacks := []resources.Stack{ + {Name: "Stack2", Description: "desc2", State: "DEPRECATED"}, + {Name: "stack1", Description: "desc1", State: "ACTIVE"}, + } + fakeActor.GetStacksReturns(stacks, v7action.Warnings{"warning-1", "warning-2"}, nil) + }) + + It("prints warnings", func() { + Expect(testUI.Err).To(Say(`warning-1`)) + Expect(testUI.Err).To(Say(`warning-2`)) + }) + + It("prints the list of stacks in alphabetical order with state column", func() { + Expect(testUI.Out).To(Say(`name\s+description\s+state`)) + Expect(testUI.Out).To(Say(`stack1\s+desc1\s+ACTIVE`)) + Expect(testUI.Out).To(Say(`Stack2\s+desc2\s+DEPRECATED`)) + }) + + It("prints the flavor text", func() { + Expect(testUI.Out).To(Say("Getting stacks as banana\\.\\.\\.")) + }) }) }) }) diff --git a/command/v7/update_stack_command.go b/command/v7/update_stack_command.go new file mode 100644 index 00000000000..3330e3afa5c --- /dev/null +++ b/command/v7/update_stack_command.go @@ -0,0 +1,77 @@ +package v7 + +import ( + "slices" + "strings" + + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/resources" +) + +type UpdateStackCommand struct { + BaseCommand + + 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"` + relatedCommands interface{} `related_commands:"stack, stacks"` +} + +func (cmd UpdateStackCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(false, false) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + // Validate and capitalize the state + stateValue := strings.ToUpper(cmd.State) + + // Validate against known states + if !slices.Contains(resources.ValidStackStates, stateValue) { + return invalidStackStateError{State: cmd.State} + } + + cmd.UI.DisplayTextWithFlavor("Updating stack {{.StackName}} as {{.Username}}...", map[string]interface{}{ + "StackName": cmd.RequiredArgs.StackName, + "Username": user.Name, + }) + + // Get the stack to find its GUID + stack, warnings, err := cmd.Actor.GetStackByName(cmd.RequiredArgs.StackName) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + // Update the stack + updatedStack, warnings, err := cmd.Actor.UpdateStack(stack.GUID, stateValue) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + cmd.UI.DisplayOK() + cmd.UI.DisplayNewline() + + // Display the updated stack info + cmd.UI.DisplayKeyValueTable("", [][]string{ + {cmd.UI.TranslateText("name:"), updatedStack.Name}, + {cmd.UI.TranslateText("description:"), updatedStack.Description}, + {cmd.UI.TranslateText("state:"), updatedStack.State}, + }, 3) + + return nil +} + +type invalidStackStateError struct { + State string +} + +func (e invalidStackStateError) Error() string { + return "Invalid state: " + e.State + ". Must be one of: " + strings.Join(resources.ValidStackStatesLowercase(), ", ") +} diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go new file mode 100644 index 00000000000..3f1c78293cc --- /dev/null +++ b/command/v7/update_stack_command_test.go @@ -0,0 +1,198 @@ +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" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("update-stack Command", func() { + var ( + cmd UpdateStackCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + executeErr error + args []string + binaryName string + ) + + JustBeforeEach(func() { + executeErr = cmd.Execute(args) + }) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + args = nil + + cmd = UpdateStackCommand{ + BaseCommand: BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + } + + binaryName = "faceman" + fakeConfig.BinaryNameReturns(binaryName) + }) + + Context("When the environment is not setup correctly", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: binaryName}) + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeFalse()) + Expect(checkTargetedSpace).To(BeFalse()) + }) + }) + + Context("When the environment is setup correctly", func() { + BeforeEach(func() { + fakeActor.GetCurrentUserReturns(configv3.User{Name: "banana"}, nil) + cmd.RequiredArgs.StackName = "some-stack" + }) + + Context("when the state flag is invalid", func() { + BeforeEach(func() { + cmd.State = "invalid" + }) + + It("returns an error", func() { + Expect(executeErr).To(HaveOccurred()) + Expect(executeErr.Error()).To(ContainSubstring("Invalid state")) + }) + }) + + Context("when the state flag is valid", func() { + BeforeEach(func() { + cmd.State = "deprecated" + }) + + Context("when getting the stack fails", func() { + BeforeEach(func() { + fakeActor.GetStackByNameReturns(resources.Stack{}, v7action.Warnings{"warning-1"}, errors.New("get-stack-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(executeErr).To(MatchError("get-stack-error")) + Expect(testUI.Err).To(Say("warning-1")) + }) + }) + + Context("when getting the stack succeeds", func() { + BeforeEach(func() { + fakeActor.GetStackByNameReturns(resources.Stack{ + GUID: "stack-guid", + Name: "some-stack", + }, v7action.Warnings{"warning-1"}, nil) + }) + + Context("when updating the stack fails", func() { + BeforeEach(func() { + fakeActor.UpdateStackReturns(resources.Stack{}, v7action.Warnings{"warning-2"}, errors.New("update-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(executeErr).To(MatchError("update-error")) + Expect(testUI.Err).To(Say("warning-1")) + Expect(testUI.Err).To(Say("warning-2")) + Expect(testUI.Out).To(Say("Updating stack some-stack as banana...")) + }) + }) + + Context("when updating the stack succeeds", func() { + BeforeEach(func() { + fakeActor.UpdateStackReturns(resources.Stack{ + GUID: "stack-guid", + Name: "some-stack", + Description: "some description", + State: resources.StackStateDeprecated, + }, v7action.Warnings{"warning-2"}, nil) + }) + + It("displays the updated stack info", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(testUI.Out).To(Say("Updating stack some-stack as banana...")) + Expect(testUI.Out).To(Say("OK")) + Expect(testUI.Out).To(Say(`name:\s+some-stack`)) + Expect(testUI.Out).To(Say(`description:\s+some description`)) + Expect(testUI.Out).To(Say(`state:\s+DEPRECATED`)) + + Expect(testUI.Err).To(Say("warning-1")) + Expect(testUI.Err).To(Say("warning-2")) + + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack")) + + Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) + guid, state := fakeActor.UpdateStackArgsForCall(0) + Expect(guid).To(Equal("stack-guid")) + Expect(state).To(Equal(resources.StackStateDeprecated)) + }) + }) + }) + }) + + 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) + + 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) + + executeErr = cmd.Execute(args) + + 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) + + executeErr = cmd.Execute(args) + + 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 c0aaff6ed5e..ab4f00d448e 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3604,6 +3604,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 + } UpdateStackLabelsByStackNameStub func(string, map[string]types.NullString) (v7action.Warnings, error) updateStackLabelsByStackNameMutex sync.RWMutex updateStackLabelsByStackNameArgsForCall []struct { @@ -19427,6 +19443,74 @@ func (fake *FakeActor) UpdateSpaceQuotaReturnsOnCall(i int, result1 v7action.War }{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) UpdateStackLabelsByStackName(arg1 string, arg2 map[string]types.NullString) (v7action.Warnings, error) { fake.updateStackLabelsByStackNameMutex.Lock() ret, specificReturn := fake.updateStackLabelsByStackNameReturnsOnCall[len(fake.updateStackLabelsByStackNameArgsForCall)] @@ -19903,494 +19987,6 @@ func (fake *FakeActor) UploadDropletReturnsOnCall(i int, result1 v7action.Warnin func (fake *FakeActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.applyOrganizationQuotaByNameMutex.RLock() - defer fake.applyOrganizationQuotaByNameMutex.RUnlock() - fake.applySpaceQuotaByNameMutex.RLock() - defer fake.applySpaceQuotaByNameMutex.RUnlock() - fake.assignIsolationSegmentToSpaceByNameAndSpaceMutex.RLock() - defer fake.assignIsolationSegmentToSpaceByNameAndSpaceMutex.RUnlock() - fake.authenticateMutex.RLock() - defer fake.authenticateMutex.RUnlock() - fake.bindSecurityGroupToSpacesMutex.RLock() - defer fake.bindSecurityGroupToSpacesMutex.RUnlock() - fake.cancelDeploymentMutex.RLock() - defer fake.cancelDeploymentMutex.RUnlock() - fake.checkRouteMutex.RLock() - defer fake.checkRouteMutex.RUnlock() - fake.clearTargetMutex.RLock() - defer fake.clearTargetMutex.RUnlock() - fake.continueDeploymentMutex.RLock() - defer fake.continueDeploymentMutex.RUnlock() - fake.copyPackageMutex.RLock() - defer fake.copyPackageMutex.RUnlock() - fake.createAndUploadBitsPackageByApplicationNameAndSpaceMutex.RLock() - defer fake.createAndUploadBitsPackageByApplicationNameAndSpaceMutex.RUnlock() - fake.createApplicationDropletMutex.RLock() - defer fake.createApplicationDropletMutex.RUnlock() - fake.createApplicationInSpaceMutex.RLock() - defer fake.createApplicationInSpaceMutex.RUnlock() - fake.createBitsPackageByApplicationMutex.RLock() - defer fake.createBitsPackageByApplicationMutex.RUnlock() - fake.createBuildpackMutex.RLock() - defer fake.createBuildpackMutex.RUnlock() - fake.createDeploymentMutex.RLock() - defer fake.createDeploymentMutex.RUnlock() - fake.createDockerPackageByApplicationMutex.RLock() - defer fake.createDockerPackageByApplicationMutex.RUnlock() - fake.createDockerPackageByApplicationNameAndSpaceMutex.RLock() - defer fake.createDockerPackageByApplicationNameAndSpaceMutex.RUnlock() - fake.createIsolationSegmentByNameMutex.RLock() - defer fake.createIsolationSegmentByNameMutex.RUnlock() - fake.createManagedServiceInstanceMutex.RLock() - defer fake.createManagedServiceInstanceMutex.RUnlock() - fake.createOrgRoleMutex.RLock() - defer fake.createOrgRoleMutex.RUnlock() - fake.createOrganizationMutex.RLock() - defer fake.createOrganizationMutex.RUnlock() - fake.createOrganizationQuotaMutex.RLock() - defer fake.createOrganizationQuotaMutex.RUnlock() - fake.createPrivateDomainMutex.RLock() - defer fake.createPrivateDomainMutex.RUnlock() - fake.createRouteMutex.RLock() - defer fake.createRouteMutex.RUnlock() - fake.createRouteBindingMutex.RLock() - defer fake.createRouteBindingMutex.RUnlock() - fake.createSecurityGroupMutex.RLock() - defer fake.createSecurityGroupMutex.RUnlock() - fake.createServiceAppBindingMutex.RLock() - defer fake.createServiceAppBindingMutex.RUnlock() - fake.createServiceBrokerMutex.RLock() - defer fake.createServiceBrokerMutex.RUnlock() - fake.createServiceKeyMutex.RLock() - defer fake.createServiceKeyMutex.RUnlock() - fake.createSharedDomainMutex.RLock() - defer fake.createSharedDomainMutex.RUnlock() - fake.createSpaceMutex.RLock() - defer fake.createSpaceMutex.RUnlock() - fake.createSpaceQuotaMutex.RLock() - defer fake.createSpaceQuotaMutex.RUnlock() - fake.createSpaceRoleMutex.RLock() - defer fake.createSpaceRoleMutex.RUnlock() - fake.createUserMutex.RLock() - defer fake.createUserMutex.RUnlock() - fake.createUserProvidedServiceInstanceMutex.RLock() - defer fake.createUserProvidedServiceInstanceMutex.RUnlock() - fake.deleteApplicationByNameAndSpaceMutex.RLock() - defer fake.deleteApplicationByNameAndSpaceMutex.RUnlock() - fake.deleteBuildpackByNameAndStackAndLifecycleMutex.RLock() - defer fake.deleteBuildpackByNameAndStackAndLifecycleMutex.RUnlock() - fake.deleteDomainMutex.RLock() - defer fake.deleteDomainMutex.RUnlock() - fake.deleteInstanceByApplicationNameSpaceProcessTypeAndIndexMutex.RLock() - defer fake.deleteInstanceByApplicationNameSpaceProcessTypeAndIndexMutex.RUnlock() - fake.deleteIsolationSegmentByNameMutex.RLock() - defer fake.deleteIsolationSegmentByNameMutex.RUnlock() - fake.deleteIsolationSegmentOrganizationByNameMutex.RLock() - defer fake.deleteIsolationSegmentOrganizationByNameMutex.RUnlock() - fake.deleteOrgRoleMutex.RLock() - defer fake.deleteOrgRoleMutex.RUnlock() - fake.deleteOrganizationMutex.RLock() - defer fake.deleteOrganizationMutex.RUnlock() - fake.deleteOrganizationQuotaMutex.RLock() - defer fake.deleteOrganizationQuotaMutex.RUnlock() - fake.deleteOrphanedRoutesMutex.RLock() - defer fake.deleteOrphanedRoutesMutex.RUnlock() - fake.deleteRouteMutex.RLock() - defer fake.deleteRouteMutex.RUnlock() - fake.deleteRouteBindingMutex.RLock() - defer fake.deleteRouteBindingMutex.RUnlock() - fake.deleteSecurityGroupMutex.RLock() - defer fake.deleteSecurityGroupMutex.RUnlock() - fake.deleteServiceAppBindingMutex.RLock() - defer fake.deleteServiceAppBindingMutex.RUnlock() - fake.deleteServiceBrokerMutex.RLock() - defer fake.deleteServiceBrokerMutex.RUnlock() - fake.deleteServiceInstanceMutex.RLock() - defer fake.deleteServiceInstanceMutex.RUnlock() - fake.deleteServiceKeyByServiceInstanceAndNameMutex.RLock() - defer fake.deleteServiceKeyByServiceInstanceAndNameMutex.RUnlock() - fake.deleteSpaceByNameAndOrganizationNameMutex.RLock() - defer fake.deleteSpaceByNameAndOrganizationNameMutex.RUnlock() - fake.deleteSpaceQuotaByNameMutex.RLock() - defer fake.deleteSpaceQuotaByNameMutex.RUnlock() - fake.deleteSpaceRoleMutex.RLock() - defer fake.deleteSpaceRoleMutex.RUnlock() - fake.deleteUserMutex.RLock() - defer fake.deleteUserMutex.RUnlock() - fake.diffSpaceManifestMutex.RLock() - defer fake.diffSpaceManifestMutex.RUnlock() - fake.disableFeatureFlagMutex.RLock() - defer fake.disableFeatureFlagMutex.RUnlock() - fake.disableServiceAccessMutex.RLock() - defer fake.disableServiceAccessMutex.RUnlock() - fake.downloadCurrentDropletByAppNameMutex.RLock() - defer fake.downloadCurrentDropletByAppNameMutex.RUnlock() - fake.downloadDropletByGUIDAndAppNameMutex.RLock() - defer fake.downloadDropletByGUIDAndAppNameMutex.RUnlock() - fake.enableFeatureFlagMutex.RLock() - defer fake.enableFeatureFlagMutex.RUnlock() - fake.enableServiceAccessMutex.RLock() - defer fake.enableServiceAccessMutex.RUnlock() - fake.entitleIsolationSegmentToOrganizationByNameMutex.RLock() - defer fake.entitleIsolationSegmentToOrganizationByNameMutex.RUnlock() - fake.getAppFeatureMutex.RLock() - defer fake.getAppFeatureMutex.RUnlock() - fake.getAppSummariesForSpaceMutex.RLock() - defer fake.getAppSummariesForSpaceMutex.RUnlock() - fake.getApplicationByNameAndSpaceMutex.RLock() - defer fake.getApplicationByNameAndSpaceMutex.RUnlock() - fake.getApplicationDropletsMutex.RLock() - defer fake.getApplicationDropletsMutex.RUnlock() - fake.getApplicationLabelsMutex.RLock() - defer fake.getApplicationLabelsMutex.RUnlock() - fake.getApplicationMapForRouteMutex.RLock() - defer fake.getApplicationMapForRouteMutex.RUnlock() - fake.getApplicationPackagesMutex.RLock() - defer fake.getApplicationPackagesMutex.RUnlock() - fake.getApplicationProcessHealthChecksByNameAndSpaceMutex.RLock() - defer fake.getApplicationProcessHealthChecksByNameAndSpaceMutex.RUnlock() - fake.getApplicationProcessReadinessHealthChecksByNameAndSpaceMutex.RLock() - defer fake.getApplicationProcessReadinessHealthChecksByNameAndSpaceMutex.RUnlock() - fake.getApplicationRevisionsDeployedMutex.RLock() - defer fake.getApplicationRevisionsDeployedMutex.RUnlock() - fake.getApplicationRoutesMutex.RLock() - defer fake.getApplicationRoutesMutex.RUnlock() - fake.getApplicationTasksMutex.RLock() - defer fake.getApplicationTasksMutex.RUnlock() - fake.getApplicationsByGUIDsMutex.RLock() - defer fake.getApplicationsByGUIDsMutex.RUnlock() - fake.getApplicationsByNamesAndSpaceMutex.RLock() - defer fake.getApplicationsByNamesAndSpaceMutex.RUnlock() - fake.getBuildpackLabelsMutex.RLock() - defer fake.getBuildpackLabelsMutex.RUnlock() - fake.getBuildpacksMutex.RLock() - defer fake.getBuildpacksMutex.RUnlock() - fake.getCurrentUserMutex.RLock() - defer fake.getCurrentUserMutex.RUnlock() - fake.getDefaultDomainMutex.RLock() - defer fake.getDefaultDomainMutex.RUnlock() - fake.getDetailedAppSummaryMutex.RLock() - defer fake.getDetailedAppSummaryMutex.RUnlock() - fake.getDomainMutex.RLock() - defer fake.getDomainMutex.RUnlock() - fake.getDomainByNameMutex.RLock() - defer fake.getDomainByNameMutex.RUnlock() - fake.getDomainLabelsMutex.RLock() - defer fake.getDomainLabelsMutex.RUnlock() - fake.getEffectiveIsolationSegmentBySpaceMutex.RLock() - defer fake.getEffectiveIsolationSegmentBySpaceMutex.RUnlock() - fake.getEnvironmentVariableGroupMutex.RLock() - defer fake.getEnvironmentVariableGroupMutex.RUnlock() - fake.getEnvironmentVariableGroupByRevisionMutex.RLock() - defer fake.getEnvironmentVariableGroupByRevisionMutex.RUnlock() - fake.getEnvironmentVariablesByApplicationNameAndSpaceMutex.RLock() - defer fake.getEnvironmentVariablesByApplicationNameAndSpaceMutex.RUnlock() - fake.getFeatureFlagByNameMutex.RLock() - defer fake.getFeatureFlagByNameMutex.RUnlock() - fake.getFeatureFlagsMutex.RLock() - defer fake.getFeatureFlagsMutex.RUnlock() - fake.getGlobalRunningSecurityGroupsMutex.RLock() - defer fake.getGlobalRunningSecurityGroupsMutex.RUnlock() - fake.getGlobalStagingSecurityGroupsMutex.RLock() - defer fake.getGlobalStagingSecurityGroupsMutex.RUnlock() - fake.getInfoResponseMutex.RLock() - defer fake.getInfoResponseMutex.RUnlock() - fake.getIsolationSegmentByNameMutex.RLock() - defer fake.getIsolationSegmentByNameMutex.RUnlock() - fake.getIsolationSegmentSummariesMutex.RLock() - defer fake.getIsolationSegmentSummariesMutex.RUnlock() - fake.getIsolationSegmentsByOrganizationMutex.RLock() - defer fake.getIsolationSegmentsByOrganizationMutex.RUnlock() - fake.getLatestActiveDeploymentForAppMutex.RLock() - defer fake.getLatestActiveDeploymentForAppMutex.RUnlock() - fake.getLoginPromptsMutex.RLock() - defer fake.getLoginPromptsMutex.RUnlock() - fake.getNewestReadyPackageForApplicationMutex.RLock() - defer fake.getNewestReadyPackageForApplicationMutex.RUnlock() - fake.getOrgUsersByRoleTypeMutex.RLock() - defer fake.getOrgUsersByRoleTypeMutex.RUnlock() - fake.getOrganizationByNameMutex.RLock() - defer fake.getOrganizationByNameMutex.RUnlock() - fake.getOrganizationDomainsMutex.RLock() - defer fake.getOrganizationDomainsMutex.RUnlock() - fake.getOrganizationLabelsMutex.RLock() - defer fake.getOrganizationLabelsMutex.RUnlock() - fake.getOrganizationQuotaByNameMutex.RLock() - defer fake.getOrganizationQuotaByNameMutex.RUnlock() - fake.getOrganizationQuotasMutex.RLock() - defer fake.getOrganizationQuotasMutex.RUnlock() - fake.getOrganizationSpacesMutex.RLock() - defer fake.getOrganizationSpacesMutex.RUnlock() - fake.getOrganizationSpacesWithLabelSelectorMutex.RLock() - defer fake.getOrganizationSpacesWithLabelSelectorMutex.RUnlock() - fake.getOrganizationSummaryByNameMutex.RLock() - defer fake.getOrganizationSummaryByNameMutex.RUnlock() - fake.getOrganizationsMutex.RLock() - defer fake.getOrganizationsMutex.RUnlock() - fake.getProcessByTypeAndApplicationMutex.RLock() - defer fake.getProcessByTypeAndApplicationMutex.RUnlock() - fake.getRawApplicationManifestByNameAndSpaceMutex.RLock() - defer fake.getRawApplicationManifestByNameAndSpaceMutex.RUnlock() - fake.getRecentEventsByApplicationNameAndSpaceMutex.RLock() - defer fake.getRecentEventsByApplicationNameAndSpaceMutex.RUnlock() - fake.getRecentLogsForApplicationByNameAndSpaceMutex.RLock() - defer fake.getRecentLogsForApplicationByNameAndSpaceMutex.RUnlock() - fake.getRevisionByApplicationAndVersionMutex.RLock() - defer fake.getRevisionByApplicationAndVersionMutex.RUnlock() - fake.getRevisionsByApplicationNameAndSpaceMutex.RLock() - defer fake.getRevisionsByApplicationNameAndSpaceMutex.RUnlock() - fake.getRootResponseMutex.RLock() - defer fake.getRootResponseMutex.RUnlock() - fake.getRouteByAttributesMutex.RLock() - defer fake.getRouteByAttributesMutex.RUnlock() - fake.getRouteDestinationByAppGUIDMutex.RLock() - defer fake.getRouteDestinationByAppGUIDMutex.RUnlock() - fake.getRouteLabelsMutex.RLock() - defer fake.getRouteLabelsMutex.RUnlock() - fake.getRouteSummariesMutex.RLock() - defer fake.getRouteSummariesMutex.RUnlock() - fake.getRouterGroupsMutex.RLock() - defer fake.getRouterGroupsMutex.RUnlock() - fake.getRoutesByOrgMutex.RLock() - defer fake.getRoutesByOrgMutex.RUnlock() - fake.getRoutesBySpaceMutex.RLock() - defer fake.getRoutesBySpaceMutex.RUnlock() - fake.getSSHEnabledMutex.RLock() - defer fake.getSSHEnabledMutex.RUnlock() - fake.getSSHEnabledByAppNameMutex.RLock() - defer fake.getSSHEnabledByAppNameMutex.RUnlock() - fake.getSSHPasscodeMutex.RLock() - defer fake.getSSHPasscodeMutex.RUnlock() - fake.getSecureShellConfigurationByApplicationNameSpaceProcessTypeAndIndexMutex.RLock() - defer fake.getSecureShellConfigurationByApplicationNameSpaceProcessTypeAndIndexMutex.RUnlock() - fake.getSecurityGroupMutex.RLock() - defer fake.getSecurityGroupMutex.RUnlock() - fake.getSecurityGroupSummaryMutex.RLock() - defer fake.getSecurityGroupSummaryMutex.RUnlock() - fake.getSecurityGroupsMutex.RLock() - defer fake.getSecurityGroupsMutex.RUnlock() - fake.getServiceAccessMutex.RLock() - defer fake.getServiceAccessMutex.RUnlock() - fake.getServiceBrokerByNameMutex.RLock() - defer fake.getServiceBrokerByNameMutex.RUnlock() - fake.getServiceBrokerLabelsMutex.RLock() - defer fake.getServiceBrokerLabelsMutex.RUnlock() - fake.getServiceBrokersMutex.RLock() - defer fake.getServiceBrokersMutex.RUnlock() - fake.getServiceInstanceByNameAndSpaceMutex.RLock() - defer fake.getServiceInstanceByNameAndSpaceMutex.RUnlock() - fake.getServiceInstanceDetailsMutex.RLock() - defer fake.getServiceInstanceDetailsMutex.RUnlock() - fake.getServiceInstanceLabelsMutex.RLock() - defer fake.getServiceInstanceLabelsMutex.RUnlock() - fake.getServiceInstanceParametersMutex.RLock() - defer fake.getServiceInstanceParametersMutex.RUnlock() - fake.getServiceInstancesForSpaceMutex.RLock() - defer fake.getServiceInstancesForSpaceMutex.RUnlock() - fake.getServiceKeyByServiceInstanceAndNameMutex.RLock() - defer fake.getServiceKeyByServiceInstanceAndNameMutex.RUnlock() - fake.getServiceKeyDetailsByServiceInstanceAndNameMutex.RLock() - defer fake.getServiceKeyDetailsByServiceInstanceAndNameMutex.RUnlock() - fake.getServiceKeysByServiceInstanceMutex.RLock() - defer fake.getServiceKeysByServiceInstanceMutex.RUnlock() - fake.getServiceOfferingLabelsMutex.RLock() - defer fake.getServiceOfferingLabelsMutex.RUnlock() - fake.getServicePlanByNameOfferingAndBrokerMutex.RLock() - defer fake.getServicePlanByNameOfferingAndBrokerMutex.RUnlock() - fake.getServicePlanLabelsMutex.RLock() - defer fake.getServicePlanLabelsMutex.RUnlock() - fake.getSpaceByNameAndOrganizationMutex.RLock() - defer fake.getSpaceByNameAndOrganizationMutex.RUnlock() - fake.getSpaceFeatureMutex.RLock() - defer fake.getSpaceFeatureMutex.RUnlock() - fake.getSpaceLabelsMutex.RLock() - defer fake.getSpaceLabelsMutex.RUnlock() - fake.getSpaceQuotaByNameMutex.RLock() - defer fake.getSpaceQuotaByNameMutex.RUnlock() - fake.getSpaceQuotasByOrgGUIDMutex.RLock() - defer fake.getSpaceQuotasByOrgGUIDMutex.RUnlock() - fake.getSpaceSummaryByNameAndOrganizationMutex.RLock() - defer fake.getSpaceSummaryByNameAndOrganizationMutex.RUnlock() - fake.getSpaceUsersByRoleTypeMutex.RLock() - defer fake.getSpaceUsersByRoleTypeMutex.RUnlock() - fake.getStackByNameMutex.RLock() - defer fake.getStackByNameMutex.RUnlock() - fake.getStackLabelsMutex.RLock() - defer fake.getStackLabelsMutex.RUnlock() - fake.getStacksMutex.RLock() - defer fake.getStacksMutex.RUnlock() - fake.getStreamingLogsForApplicationByNameAndSpaceMutex.RLock() - defer fake.getStreamingLogsForApplicationByNameAndSpaceMutex.RUnlock() - fake.getTaskBySequenceIDAndApplicationMutex.RLock() - defer fake.getTaskBySequenceIDAndApplicationMutex.RUnlock() - fake.getUAAAPIVersionMutex.RLock() - defer fake.getUAAAPIVersionMutex.RUnlock() - fake.getUnstagedNewestPackageGUIDMutex.RLock() - defer fake.getUnstagedNewestPackageGUIDMutex.RUnlock() - fake.getUserMutex.RLock() - defer fake.getUserMutex.RUnlock() - fake.makeCurlRequestMutex.RLock() - defer fake.makeCurlRequestMutex.RUnlock() - fake.mapRouteMutex.RLock() - defer fake.mapRouteMutex.RUnlock() - fake.marketplaceMutex.RLock() - defer fake.marketplaceMutex.RUnlock() - fake.moveRouteMutex.RLock() - defer fake.moveRouteMutex.RUnlock() - fake.parseAccessTokenMutex.RLock() - defer fake.parseAccessTokenMutex.RUnlock() - fake.pollBuildMutex.RLock() - defer fake.pollBuildMutex.RUnlock() - fake.pollPackageMutex.RLock() - defer fake.pollPackageMutex.RUnlock() - fake.pollStartMutex.RLock() - defer fake.pollStartMutex.RUnlock() - fake.pollStartForDeploymentMutex.RLock() - defer fake.pollStartForDeploymentMutex.RUnlock() - fake.pollTaskMutex.RLock() - defer fake.pollTaskMutex.RUnlock() - fake.pollUploadBuildpackJobMutex.RLock() - defer fake.pollUploadBuildpackJobMutex.RUnlock() - fake.prepareBuildpackBitsMutex.RLock() - defer fake.prepareBuildpackBitsMutex.RUnlock() - fake.purgeServiceInstanceMutex.RLock() - defer fake.purgeServiceInstanceMutex.RUnlock() - fake.purgeServiceOfferingByNameAndBrokerMutex.RLock() - defer fake.purgeServiceOfferingByNameAndBrokerMutex.RUnlock() - fake.refreshAccessTokenMutex.RLock() - defer fake.refreshAccessTokenMutex.RUnlock() - fake.renameApplicationByNameAndSpaceGUIDMutex.RLock() - defer fake.renameApplicationByNameAndSpaceGUIDMutex.RUnlock() - fake.renameOrganizationMutex.RLock() - defer fake.renameOrganizationMutex.RUnlock() - fake.renameServiceInstanceMutex.RLock() - defer fake.renameServiceInstanceMutex.RUnlock() - fake.renameSpaceByNameAndOrganizationGUIDMutex.RLock() - defer fake.renameSpaceByNameAndOrganizationGUIDMutex.RUnlock() - fake.resetOrganizationDefaultIsolationSegmentMutex.RLock() - defer fake.resetOrganizationDefaultIsolationSegmentMutex.RUnlock() - fake.resetSpaceIsolationSegmentMutex.RLock() - defer fake.resetSpaceIsolationSegmentMutex.RUnlock() - fake.resourceMatchMutex.RLock() - defer fake.resourceMatchMutex.RUnlock() - fake.restartApplicationMutex.RLock() - defer fake.restartApplicationMutex.RUnlock() - fake.revokeAccessAndRefreshTokensMutex.RLock() - defer fake.revokeAccessAndRefreshTokensMutex.RUnlock() - fake.runTaskMutex.RLock() - defer fake.runTaskMutex.RUnlock() - fake.scaleProcessByApplicationMutex.RLock() - defer fake.scaleProcessByApplicationMutex.RUnlock() - fake.scheduleTokenRefreshMutex.RLock() - defer fake.scheduleTokenRefreshMutex.RUnlock() - fake.setApplicationDropletMutex.RLock() - defer fake.setApplicationDropletMutex.RUnlock() - fake.setApplicationDropletByApplicationNameAndSpaceMutex.RLock() - defer fake.setApplicationDropletByApplicationNameAndSpaceMutex.RUnlock() - fake.setApplicationManifestMutex.RLock() - defer fake.setApplicationManifestMutex.RUnlock() - fake.setApplicationProcessHealthCheckTypeByNameAndSpaceMutex.RLock() - defer fake.setApplicationProcessHealthCheckTypeByNameAndSpaceMutex.RUnlock() - fake.setEnvironmentVariableByApplicationNameAndSpaceMutex.RLock() - defer fake.setEnvironmentVariableByApplicationNameAndSpaceMutex.RUnlock() - fake.setEnvironmentVariableGroupMutex.RLock() - defer fake.setEnvironmentVariableGroupMutex.RUnlock() - fake.setOrganizationDefaultIsolationSegmentMutex.RLock() - defer fake.setOrganizationDefaultIsolationSegmentMutex.RUnlock() - fake.setSpaceManifestMutex.RLock() - defer fake.setSpaceManifestMutex.RUnlock() - fake.setTargetMutex.RLock() - defer fake.setTargetMutex.RUnlock() - fake.sharePrivateDomainMutex.RLock() - defer fake.sharePrivateDomainMutex.RUnlock() - fake.shareRouteMutex.RLock() - defer fake.shareRouteMutex.RUnlock() - fake.shareServiceInstanceToSpaceAndOrgMutex.RLock() - defer fake.shareServiceInstanceToSpaceAndOrgMutex.RUnlock() - fake.stageApplicationPackageMutex.RLock() - defer fake.stageApplicationPackageMutex.RUnlock() - fake.stagePackageMutex.RLock() - defer fake.stagePackageMutex.RUnlock() - fake.startApplicationMutex.RLock() - defer fake.startApplicationMutex.RUnlock() - fake.stopApplicationMutex.RLock() - defer fake.stopApplicationMutex.RUnlock() - fake.terminateTaskMutex.RLock() - defer fake.terminateTaskMutex.RUnlock() - fake.unbindSecurityGroupMutex.RLock() - defer fake.unbindSecurityGroupMutex.RUnlock() - fake.unmapRouteMutex.RLock() - defer fake.unmapRouteMutex.RUnlock() - fake.unsetEnvironmentVariableByApplicationNameAndSpaceMutex.RLock() - defer fake.unsetEnvironmentVariableByApplicationNameAndSpaceMutex.RUnlock() - fake.unsetSpaceQuotaMutex.RLock() - defer fake.unsetSpaceQuotaMutex.RUnlock() - fake.unsharePrivateDomainMutex.RLock() - defer fake.unsharePrivateDomainMutex.RUnlock() - fake.unshareRouteMutex.RLock() - defer fake.unshareRouteMutex.RUnlock() - fake.unshareServiceInstanceFromSpaceAndOrgMutex.RLock() - defer fake.unshareServiceInstanceFromSpaceAndOrgMutex.RUnlock() - fake.updateAppFeatureMutex.RLock() - defer fake.updateAppFeatureMutex.RUnlock() - fake.updateApplicationMutex.RLock() - defer fake.updateApplicationMutex.RUnlock() - fake.updateApplicationLabelsByApplicationNameMutex.RLock() - defer fake.updateApplicationLabelsByApplicationNameMutex.RUnlock() - fake.updateBuildpackByNameAndStackAndLifecycleMutex.RLock() - defer fake.updateBuildpackByNameAndStackAndLifecycleMutex.RUnlock() - fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.RLock() - defer fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.RUnlock() - fake.updateDestinationMutex.RLock() - defer fake.updateDestinationMutex.RUnlock() - fake.updateDomainLabelsByDomainNameMutex.RLock() - defer fake.updateDomainLabelsByDomainNameMutex.RUnlock() - fake.updateManagedServiceInstanceMutex.RLock() - defer fake.updateManagedServiceInstanceMutex.RUnlock() - fake.updateOrganizationLabelsByOrganizationNameMutex.RLock() - defer fake.updateOrganizationLabelsByOrganizationNameMutex.RUnlock() - fake.updateOrganizationQuotaMutex.RLock() - defer fake.updateOrganizationQuotaMutex.RUnlock() - fake.updateProcessByTypeAndApplicationMutex.RLock() - defer fake.updateProcessByTypeAndApplicationMutex.RUnlock() - fake.updateRouteMutex.RLock() - defer fake.updateRouteMutex.RUnlock() - fake.updateRouteLabelsMutex.RLock() - defer fake.updateRouteLabelsMutex.RUnlock() - fake.updateSecurityGroupMutex.RLock() - defer fake.updateSecurityGroupMutex.RUnlock() - fake.updateSecurityGroupGloballyEnabledMutex.RLock() - defer fake.updateSecurityGroupGloballyEnabledMutex.RUnlock() - fake.updateServiceBrokerMutex.RLock() - defer fake.updateServiceBrokerMutex.RUnlock() - fake.updateServiceBrokerLabelsByServiceBrokerNameMutex.RLock() - defer fake.updateServiceBrokerLabelsByServiceBrokerNameMutex.RUnlock() - fake.updateServiceInstanceLabelsMutex.RLock() - defer fake.updateServiceInstanceLabelsMutex.RUnlock() - fake.updateServiceOfferingLabelsMutex.RLock() - defer fake.updateServiceOfferingLabelsMutex.RUnlock() - fake.updateServicePlanLabelsMutex.RLock() - defer fake.updateServicePlanLabelsMutex.RUnlock() - fake.updateSpaceFeatureMutex.RLock() - defer fake.updateSpaceFeatureMutex.RUnlock() - fake.updateSpaceLabelsBySpaceNameMutex.RLock() - defer fake.updateSpaceLabelsBySpaceNameMutex.RUnlock() - fake.updateSpaceQuotaMutex.RLock() - defer fake.updateSpaceQuotaMutex.RUnlock() - fake.updateStackLabelsByStackNameMutex.RLock() - defer fake.updateStackLabelsByStackNameMutex.RUnlock() - fake.updateUserPasswordMutex.RLock() - defer fake.updateUserPasswordMutex.RUnlock() - fake.updateUserProvidedServiceInstanceMutex.RLock() - defer fake.updateUserProvidedServiceInstanceMutex.RUnlock() - fake.upgradeManagedServiceInstanceMutex.RLock() - defer fake.upgradeManagedServiceInstanceMutex.RUnlock() - fake.uploadBitsPackageMutex.RLock() - defer fake.uploadBitsPackageMutex.RUnlock() - fake.uploadBuildpackMutex.RLock() - defer fake.uploadBuildpackMutex.RUnlock() - fake.uploadDropletMutex.RLock() - defer fake.uploadDropletMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/integration/v7/isolated/stack_command_test.go b/integration/v7/isolated/stack_command_test.go index f394e5dad02..d007fdeb10e 100644 --- a/integration/v7/isolated/stack_command_test.go +++ b/integration/v7/isolated/stack_command_test.go @@ -103,27 +103,56 @@ var _ = Describe("stack command", func() { When("the stack exists", func() { var stackGUID string - 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)) + 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", 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(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)) + }) }) When("the stack exists and the --guid flag is passed", func() { diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go new file mode 100644 index 00000000000..054956e9b0a --- /dev/null +++ b/integration/v7/isolated/update_stack_command_test.go @@ -0,0 +1,232 @@ +package isolated + +import ( + "fmt" + "regexp" + + . "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" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("update-stack command", func() { + var ( + orgName string + spaceName string + stackName string + stackDescription string + stackGUID string + username string + ) + + BeforeEach(func() { + orgName = helpers.NewOrgName() + spaceName = helpers.NewSpaceName() + stackName = helpers.PrefixedRandomName("stack") + stackDescription = "test stack for update" + }) + + Describe("help", func() { + When("--help flag is set", func() { + It("appears in cf help -a", func() { + session := helpers.CF("help", "-a") + Eventually(session).Should(Exit(0)) + Expect(session).To(HaveCommandInCategoryWithDescription("update-stack", "APPS", "Transition a stack between the defined states")) + }) + + It("displays command usage to output", func() { + session := helpers.CF("update-stack", "--help") + + 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(`EXAMPLES:`)) + Eventually(session).Should(Say(`cf update-stack cflinuxfs3 --state disabled`)) + Eventually(session).Should(Say(`OPTIONS:`)) + Eventually(session).Should(Say(`--state\s+State to transition the stack to`)) + Eventually(session).Should(Say(`SEE ALSO:`)) + Eventually(session).Should(Say(`stack, stacks`)) + + Eventually(session).Should(Exit(0)) + }) + }) + }) + + When("the stack name is not provided", func() { + It("tells the user that the stack name is required, prints help text, and exits 1", func() { + session := helpers.CF("update-stack", "--state", "active") + + Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `STACK_NAME` was not provided")) + Eventually(session).Should(Say("NAME:")) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the state flag is not provided", func() { + It("tells the user that the state flag is required, prints help text, and exits 1", func() { + session := helpers.CF("update-stack", "some-stack") + + Eventually(session.Err).Should(Say("Incorrect Usage: the required flag `--state' was not specified")) + Eventually(session).Should(Say("NAME:")) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the environment is not setup correctly", func() { + It("fails with the appropriate errors", func() { + helpers.CheckEnvironmentTargetedCorrectly(false, false, ReadOnlyOrg, "update-stack", stackName, "--state", "active") + }) + }) + + When("the environment is set up correctly", func() { + BeforeEach(func() { + helpers.SetupCF(orgName, spaceName) + username, _ = helpers.GetCredentials() + }) + + AfterEach(func() { + helpers.QuickDeleteOrg(orgName) + }) + + When("the stack does not exist", func() { + It("fails with stack not found error", func() { + session := helpers.CF("update-stack", stackName, "--state", "active") + + Eventually(session).Should(Say(`Updating stack %s as %s\.\.\.`, stackName, username)) + Eventually(session.Err).Should(Say("Stack '%s' not found", stackName)) + Eventually(session).Should(Say("FAILED")) + Eventually(session).Should(Exit(1)) + }) + }) + + When("an invalid state is provided", func() { + BeforeEach(func() { + // Create a stack first + 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() { + if stackGUID != "" { + session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) + Eventually(session).Should(Exit(0)) + } + }) + + It("fails with invalid state error", func() { + session := helpers.CF("update-stack", stackName, "--state", "invalid-state") + + Eventually(session.Err).Should(Say("Invalid state: invalid-state")) + Eventually(session.Err).Should(Say("Must be one of: active, restricted, deprecated, disabled")) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the stack exists", func() { + BeforeEach(func() { + // Create a stack with initial state + 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() { + if stackGUID != "" { + session := helpers.CF("curl", "-X", "DELETE", fmt.Sprintf("/v3/stacks/%s", stackGUID)) + Eventually(session).Should(Exit(0)) + } + }) + + When("updating to deprecated state", func() { + It("successfully updates the stack state", 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(`description:\s+%s`, stackDescription)) + Eventually(session).Should(Say(`state:\s+DEPRECATED`)) + Eventually(session).Should(Exit(0)) + + // Verify the state was actually updated + verifySession := helpers.CF("stack", stackName) + Eventually(verifySession).Should(Say(`state:\s+DEPRECATED`)) + Eventually(verifySession).Should(Exit(0)) + }) + }) + + When("updating to restricted state", func() { + It("successfully updates the stack state", func() { + session := helpers.CF("update-stack", stackName, "--state", "restricted") + + Eventually(session).Should(Say(`Updating stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Say(`state:\s+RESTRICTED`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("updating to disabled state", func() { + It("successfully updates the stack state", func() { + session := helpers.CF("update-stack", stackName, "--state", "disabled") + + Eventually(session).Should(Say(`Updating stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Say(`state:\s+DISABLED`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("updating back to active state", func() { + BeforeEach(func() { + // First set it to deprecated + session := helpers.CF("update-stack", stackName, "--state", "deprecated") + Eventually(session).Should(Exit(0)) + }) + + It("successfully updates the stack back to active", func() { + session := helpers.CF("update-stack", stackName, "--state", "active") + + Eventually(session).Should(Say(`Updating stack %s as %s\.\.\.`, stackName, username)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Say(`state:\s+ACTIVE`)) + 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") + Eventually(session).Should(Exit(0)) + Eventually(session).Should(Say(`state:\s+DEPRECATED`)) + }) + + It("accepts uppercase state value", func() { + session := helpers.CF("update-stack", stackName, "--state", "RESTRICTED") + Eventually(session).Should(Exit(0)) + Eventually(session).Should(Say(`state:\s+RESTRICTED`)) + }) + + It("accepts mixed case state value", func() { + session := helpers.CF("update-stack", stackName, "--state", "Disabled") + Eventually(session).Should(Exit(0)) + Eventually(session).Should(Say(`state:\s+DISABLED`)) + }) + }) + }) + }) +}) + diff --git a/resources/stack_resource.go b/resources/stack_resource.go index e312504110d..a26746b0210 100644 --- a/resources/stack_resource.go +++ b/resources/stack_resource.go @@ -1,5 +1,37 @@ package resources +import "strings" + +// Valid stack states +const ( + StackStateActive = "ACTIVE" + StackStateRestricted = "RESTRICTED" + StackStateDeprecated = "DEPRECATED" + StackStateDisabled = "DISABLED" +) + +// ValidStackStates contains all valid stack state values +var ValidStackStates = []string{ + StackStateActive, + StackStateRestricted, + StackStateDeprecated, + StackStateDisabled, +} + +// ValidStackStatesLowercase returns the valid stack states in lowercase +func ValidStackStatesLowercase() []string { + lowercase := make([]string, len(ValidStackStates)) + for i, state := range ValidStackStates { + lowercase[i] = strings.ToLower(state) + } + 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"` @@ -7,6 +39,8 @@ type Stack struct { Name string `json:"name"` // Description is the description for the stack Description string `json:"description"` + // State is the state of the stack (ACTIVE, RESTRICTED, DEPRECATED, DISABLED) + State string `json:"state,omitempty"` // Metadata is used for custom tagging of API resources Metadata *Metadata `json:"metadata,omitempty"`