Skip to content

Commit

Permalink
Allow user to rename app if buildpack is deleted (#2321)
Browse files Browse the repository at this point in the history
* Add new UpdateApplicationName functions for actor and client. Change RenameApplicationByNameAndSpaceGuid to use new UpdateApplicationName function.
* Add UpdateApplicationName to cloud-controller-client interface.
* create ApplicationNameOnly struct that marshalls correctly

Co-authored-by: Ryker Reed <[email protected]>
  • Loading branch information
moleske and reedr3 authored Sep 20, 2022
1 parent c67c88e commit eb52f6e
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 11 deletions.
15 changes: 13 additions & 2 deletions actor/v7action/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ func (actor Actor) UpdateApplication(app resources.Application) (resources.Appli
return updatedApp, Warnings(warnings), nil
}

// UpdateApplicationName updates the name of an application
func (actor Actor) UpdateApplicationName(newAppName string, appGUID string) (resources.Application, Warnings, error) {

updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplicationName(newAppName, appGUID)
if err != nil {
return resources.Application{}, Warnings(warnings), err
}

return updatedApp, Warnings(warnings), nil
}

func (actor Actor) getDeployment(deploymentGUID string) (resources.Deployment, Warnings, error) {
deployment, warnings, err := actor.CloudControllerClient.GetDeployment(deploymentGUID)
if err != nil {
Expand Down Expand Up @@ -444,8 +455,8 @@ func (actor Actor) RenameApplicationByNameAndSpaceGUID(appName, newAppName, spac
if err != nil {
return resources.Application{}, allWarnings, err
}
application.Name = newAppName
application, warnings, err = actor.UpdateApplication(application)
appGUID := application.GUID
application, warnings, err = actor.UpdateApplicationName(newAppName, appGUID)
allWarnings = append(allWarnings, warnings...)
if err != nil {
return resources.Application{}, allWarnings, err
Expand Down
84 changes: 75 additions & 9 deletions actor/v7action/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,76 @@ var _ = Describe("Application Actions", func() {
})
})

Describe("UpdateApplicationName", func() {
var (
resultApp resources.Application
newAppName, appGUID string
warnings Warnings
err error
)

JustBeforeEach(func() {
newAppName = "some-new-app-name"
appGUID = "some-app-guid"

resultApp, warnings, err = actor.UpdateApplicationName(newAppName, appGUID)
})

When("the app successfully gets updated", func() {
var apiResponseApp resources.Application

BeforeEach(func() {
apiResponseApp = resources.Application{
GUID: "response-app-guid",
StackName: "response-stack-name",
Name: "response-app-name",
LifecycleType: constant.AppLifecycleTypeBuildpack,
LifecycleBuildpacks: []string{"response-buildpack-1", "response-buildpack-2"},
}
fakeCloudControllerClient.UpdateApplicationNameReturns(
apiResponseApp,
ccv3.Warnings{"some-warning"},
nil,
)
})

It("creates and returns the application and warnings", func() {
Expect(err).ToNot(HaveOccurred())
Expect(resultApp).To(Equal(resources.Application{
Name: apiResponseApp.Name,
GUID: apiResponseApp.GUID,
StackName: apiResponseApp.StackName,
LifecycleType: apiResponseApp.LifecycleType,
LifecycleBuildpacks: apiResponseApp.LifecycleBuildpacks,
}))
Expect(warnings).To(ConsistOf("some-warning"))

Expect(fakeCloudControllerClient.UpdateApplicationNameCallCount()).To(Equal(1))
appName, appGuid := fakeCloudControllerClient.UpdateApplicationNameArgsForCall(0)
Expect(appName).To(Equal("some-new-app-name"))
Expect(appGuid).To(Equal("some-app-guid"))
})
})

When("the cc client returns an error", func() {
var expectedError error

BeforeEach(func() {
expectedError = errors.New("I am a CloudControllerClient Error")
fakeCloudControllerClient.UpdateApplicationNameReturns(
resources.Application{},
ccv3.Warnings{"some-warning"},
expectedError,
)
})

It("raises the error and warnings", func() {
Expect(err).To(MatchError(expectedError))
Expect(warnings).To(ConsistOf("some-warning"))
})
})
})

Describe("PollStart", func() {
var (
app resources.Application
Expand Down Expand Up @@ -2016,7 +2086,7 @@ var _ = Describe("Application Actions", func() {
},
ccv3.Warnings{"get-app-warning"},
nil)
fakeCloudControllerClient.UpdateApplicationReturns(
fakeCloudControllerClient.UpdateApplicationNameReturns(
resources.Application{},
ccv3.Warnings{"update-app-warning"},
expectedError)
Expand All @@ -2042,7 +2112,7 @@ var _ = Describe("Application Actions", func() {
nil,
)

fakeCloudControllerClient.UpdateApplicationReturns(
fakeCloudControllerClient.UpdateApplicationNameReturns(
resources.Application{
Name: "new-app-name",
GUID: "old-app-guid",
Expand All @@ -2060,13 +2130,9 @@ var _ = Describe("Application Actions", func() {
GUID: "old-app-guid",
}))
Expect(warnings).To(ConsistOf("get-app-warning", "update-app-warning"))

Expect(fakeCloudControllerClient.UpdateApplicationArgsForCall(0)).To(Equal(
resources.Application{
Name: "new-app-name",
GUID: "old-app-guid",
}))

appName, appGuid := fakeCloudControllerClient.UpdateApplicationNameArgsForCall(0)
Expect(appName).To(Equal("new-app-name"))
Expect(appGuid).To(Equal("old-app-guid"))
})
})

Expand Down
1 change: 1 addition & 0 deletions actor/v7action/cloud_controller_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ type CloudControllerClient interface {
UnshareServiceInstanceFromSpace(serviceInstanceGUID string, sharedToSpaceGUID string) (ccv3.Warnings, error)
UpdateAppFeature(appGUID string, enabled bool, featureName string) (ccv3.Warnings, error)
UpdateApplication(app resources.Application) (resources.Application, ccv3.Warnings, error)
UpdateApplicationName(newAppName string, appGUID string) (resources.Application, ccv3.Warnings, error)
UpdateApplicationApplyManifest(appGUID string, rawManifest []byte) (ccv3.JobURL, ccv3.Warnings, error)
UpdateApplicationEnvironmentVariables(appGUID string, envVars resources.EnvironmentVariables) (resources.EnvironmentVariables, ccv3.Warnings, error)
UpdateApplicationRestart(appGUID string) (resources.Application, ccv3.Warnings, error)
Expand Down
85 changes: 85 additions & 0 deletions actor/v7action/v7actionfakes/fake_cloud_controller_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions api/cloudcontroller/ccv3/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ func (client *Client) UpdateApplication(app resources.Application) (resources.Ap
return responseBody, warnings, err
}

// UpdateApplicationName updates an application with the new name given
func (client *Client) UpdateApplicationName(newAppName string, appGUID string) (resources.Application, Warnings, error) {
var responseBody resources.Application
_, warnings, err := client.MakeRequest(RequestParams{
RequestName: internal.PatchApplicationRequest,
URIParams: internal.Params{"app_guid": appGUID},
RequestBody: resources.ApplicationNameOnly{Name: newAppName},
ResponseBody: &responseBody,
})

return responseBody, warnings, err
}

// UpdateApplicationRestart restarts the given application.
func (client *Client) UpdateApplicationRestart(appGUID string) (resources.Application, Warnings, error) {
var responseBody resources.Application
Expand Down
98 changes: 98 additions & 0 deletions api/cloudcontroller/ccv3/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,104 @@ var _ = Describe("Application", func() {
})
})

Describe("UpdateApplicationName", func() {
var (
newAppName string
appGUID string

updatedApp resources.Application
warnings Warnings
executeErr error
)

JustBeforeEach(func() {
newAppName = "some-new-app-name"
appGUID = "some-app-guid"

updatedApp, warnings, executeErr = client.UpdateApplicationName(newAppName, appGUID)
})

When("the application successfully is updated", func() {
BeforeEach(func() {
requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
requestParams.ResponseBody.(*resources.Application).GUID = appGUID
requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.ApplicationNameOnly).Name
requestParams.ResponseBody.(*resources.Application).StackName = "some-stack-name"
requestParams.ResponseBody.(*resources.Application).LifecycleType = constant.AppLifecycleTypeBuildpack
requestParams.ResponseBody.(*resources.Application).LifecycleBuildpacks = []string{"some-buildpack"}
requestParams.ResponseBody.(*resources.Application).SpaceGUID = "some-space-guid"
return "", Warnings{"this is a warning"}, nil
})
})

It("makes the correct request", func() {
Expect(requester.MakeRequestCallCount()).To(Equal(1))
actualParams := requester.MakeRequestArgsForCall(0)
Expect(actualParams.RequestName).To(Equal(internal.PatchApplicationRequest))
Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
Expect(actualParams.RequestBody).To(Equal(resources.ApplicationNameOnly{Name: newAppName}))
_, ok := actualParams.ResponseBody.(*resources.Application)
Expect(ok).To(BeTrue())
})

It("returns the updated app and warnings", func() {
Expect(executeErr).NotTo(HaveOccurred())
Expect(warnings).To(ConsistOf("this is a warning"))

Expect(updatedApp).To(Equal(resources.Application{
GUID: "some-app-guid",
StackName: "some-stack-name",
LifecycleBuildpacks: []string{"some-buildpack"},
LifecycleType: constant.AppLifecycleTypeBuildpack,
Name: "some-new-app-name",
SpaceGUID: "some-space-guid",
}))
})
})

When("cc returns back an error or warnings", func() {
BeforeEach(func() {
errors := []ccerror.V3Error{
{
Code: 10008,
Detail: "The request is semantically invalid: command presence",
Title: "CF-UnprocessableEntity",
},
{
Code: 10010,
Detail: "App not found",
Title: "CF-ResourceNotFound",
},
}

requester.MakeRequestReturns(
"",
Warnings{"this is a warning"},
ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
)
})

It("returns the error and all warnings", func() {
Expect(executeErr).To(MatchError(ccerror.MultiError{
ResponseCode: http.StatusTeapot,
Errors: []ccerror.V3Error{
{
Code: 10008,
Detail: "The request is semantically invalid: command presence",
Title: "CF-UnprocessableEntity",
},
{
Code: 10010,
Detail: "App not found",
Title: "CF-ResourceNotFound",
},
},
}))
Expect(warnings).To(ConsistOf("this is a warning"))
})
})
})

Describe("UpdateApplicationStop", func() {
var (
responseApp resources.Application
Expand Down
6 changes: 6 additions & 0 deletions resources/application_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type Application struct {
State constant.ApplicationState
}

// ApplicationNameOnly represents only the name field of a Cloud Controller V3 Application
type ApplicationNameOnly struct {
// Name is the name given to the application.
Name string `json:"name,omitempty"`
}

// MarshalJSON converts an Application into a Cloud Controller Application.
func (a Application) MarshalJSON() ([]byte, error) {
ccApp := ccApplication{
Expand Down

0 comments on commit eb52f6e

Please sign in to comment.