From 5dc0ced9c6de6e5fec45a3dfe0d2cd4a9b682e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:04:07 -0500 Subject: [PATCH] Implements new command `share-route` (#2288) This command is used for sharing a route in between two spaces in order to facilitate the movement of apps in between spaces. --- actor/v7action/cloud_controller_client.go | 1 + actor/v7action/route.go | 5 +- .../fake_cloud_controller_client.go | 80 +++++ .../ccv3/internal/api_routes.go | 2 + api/cloudcontroller/ccv3/route.go | 25 ++ command/common/command_list_v7.go | 1 + command/common/internal/help_all_display.go | 1 + command/v7/actor.go | 1 + command/v7/share_route_command.go | 101 +++++++ command/v7/share_route_command_test.go | 279 ++++++++++++++++++ command/v7/v7fakes/fake_actor.go | 80 +++++ .../v7/isolated/share_route_command_test.go | 204 +++++++++++++ 12 files changed, 779 insertions(+), 1 deletion(-) create mode 100644 command/v7/share_route_command.go create mode 100644 command/v7/share_route_command_test.go create mode 100644 integration/v7/isolated/share_route_command_test.go diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index 5bca12c73b0..756c0528ffd 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -149,6 +149,7 @@ type CloudControllerClient interface { SetApplicationDroplet(appGUID string, dropletGUID string) (resources.Relationship, ccv3.Warnings, error) SharePrivateDomainToOrgs(domainGuid string, sharedOrgs ccv3.SharedOrgs) (ccv3.Warnings, error) ShareServiceInstanceToSpaces(serviceInstanceGUID string, spaceGUIDs []string) (resources.RelationshipList, ccv3.Warnings, error) + ShareRoute(routeGUID string, spaceGUID string) (ccv3.Warnings, error) TargetCF(settings ccv3.TargetSettings) UnbindSecurityGroupRunningSpace(securityGroupGUID string, spaceGUID string) (ccv3.Warnings, error) UnbindSecurityGroupStagingSpace(securityGroupGUID string, spaceGUID string) (ccv3.Warnings, error) diff --git a/actor/v7action/route.go b/actor/v7action/route.go index e91d5434473..16f7bc9f4ce 100644 --- a/actor/v7action/route.go +++ b/actor/v7action/route.go @@ -406,7 +406,10 @@ func (actor Actor) UnmapRoute(routeGUID string, destinationGUID string) (Warning warnings, err := actor.CloudControllerClient.UnmapRoute(routeGUID, destinationGUID) return Warnings(warnings), err } - +func (actor Actor) ShareRoute(routeGUID string, spaceGUID string) (Warnings, error) { + warnings, err := actor.CloudControllerClient.ShareRoute(routeGUID, spaceGUID) + return Warnings(warnings), err +} func (actor Actor) GetApplicationRoutes(appGUID string) ([]resources.Route, Warnings, error) { allWarnings := Warnings{} diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index bc955a70bba..15a378271c2 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2076,6 +2076,20 @@ type FakeCloudControllerClient struct { result1 ccv3.Warnings result2 error } + ShareRouteStub func(string, string) (ccv3.Warnings, error) + shareRouteMutex sync.RWMutex + shareRouteArgsForCall []struct { + arg1 string + arg2 string + } + shareRouteReturns struct { + result1 ccv3.Warnings + result2 error + } + shareRouteReturnsOnCall map[int]struct { + result1 ccv3.Warnings + result2 error + } ShareServiceInstanceToSpacesStub func(string, []string) (resources.RelationshipList, ccv3.Warnings, error) shareServiceInstanceToSpacesMutex sync.RWMutex shareServiceInstanceToSpacesArgsForCall []struct { @@ -11688,6 +11702,70 @@ func (fake *FakeCloudControllerClient) SharePrivateDomainToOrgsReturnsOnCall(i i }{result1, result2} } +func (fake *FakeCloudControllerClient) ShareRoute(arg1 string, arg2 string) (ccv3.Warnings, error) { + fake.shareRouteMutex.Lock() + ret, specificReturn := fake.shareRouteReturnsOnCall[len(fake.shareRouteArgsForCall)] + fake.shareRouteArgsForCall = append(fake.shareRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("ShareRoute", []interface{}{arg1, arg2}) + fake.shareRouteMutex.Unlock() + if fake.ShareRouteStub != nil { + return fake.ShareRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.shareRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeCloudControllerClient) ShareRouteCallCount() int { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + return len(fake.shareRouteArgsForCall) +} + +func (fake *FakeCloudControllerClient) ShareRouteCalls(stub func(string, string) (ccv3.Warnings, error)) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = stub +} + +func (fake *FakeCloudControllerClient) ShareRouteArgsForCall(i int) (string, string) { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + argsForCall := fake.shareRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCloudControllerClient) ShareRouteReturns(result1 ccv3.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + fake.shareRouteReturns = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeCloudControllerClient) ShareRouteReturnsOnCall(i int, result1 ccv3.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + if fake.shareRouteReturnsOnCall == nil { + fake.shareRouteReturnsOnCall = make(map[int]struct { + result1 ccv3.Warnings + result2 error + }) + } + fake.shareRouteReturnsOnCall[i] = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeCloudControllerClient) ShareServiceInstanceToSpaces(arg1 string, arg2 []string) (resources.RelationshipList, ccv3.Warnings, error) { var arg2Copy []string if arg2 != nil { @@ -14659,6 +14737,8 @@ func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} 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() diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index 8f2239316c8..7029d8073fa 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -172,6 +172,7 @@ const ( PostUserRequest = "PostUser" PutTaskCancelRequest = "PutTaskCancel" SharePrivateDomainRequest = "SharePrivateDomainRequest" + ShareRouteRequest = "ShareRouteRequest" UnmapRouteRequest = "UnmapRoute" WhoAmI = "WhoAmI" ) @@ -277,6 +278,7 @@ var APIRoutes = map[string]Route{ MapRouteRequest: {Path: "/v3/routes/:route_guid/destinations", Method: http.MethodPost}, UnmapRouteRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodDelete}, PatchDestinationRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodPatch}, + ShareRouteRequest: {Path: "/v3/routes/:route_guid/relationships/shared_spaces", Method: http.MethodPost}, GetSecurityGroupsRequest: {Path: "/v3/security_groups", Method: http.MethodGet}, PostSecurityGroupRequest: {Path: "/v3/security_groups", Method: http.MethodPost}, DeleteSecurityGroupRequest: {Path: "/v3/security_groups/:security_group_guid", Method: http.MethodDelete}, diff --git a/api/cloudcontroller/ccv3/route.go b/api/cloudcontroller/ccv3/route.go index b40aed980c9..d7ad9a2c182 100644 --- a/api/cloudcontroller/ccv3/route.go +++ b/api/cloudcontroller/ccv3/route.go @@ -148,3 +148,28 @@ func (client Client) UpdateDestination(routeGUID string, destinationGUID string, }) return warnings, err } + +func (client Client) ShareRoute(routeGUID string, spaceGUID string) (Warnings, error) { + type space struct { + GUID string `json:"guid"` + } + + type body struct { + Data []space `json:"data"` + } + + requestBody := body{ + Data: []space{ + {GUID: spaceGUID}, + }, + } + + var responseBody resources.Build + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.ShareRouteRequest, + URIParams: internal.Params{"route_guid": routeGUID}, + RequestBody: &requestBody, + ResponseBody: &responseBody, + }) + return warnings, err +} diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 83300833d79..fada59bcb02 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -157,6 +157,7 @@ type commandList struct { SetStagingEnvironmentVariableGroup v7.SetStagingEnvironmentVariableGroupCommand `command:"set-staging-environment-variable-group" alias:"ssevg" description:"Pass parameters as JSON to create a staging environment variable group"` SharePrivateDomain v7.SharePrivateDomainCommand `command:"share-private-domain" description:"Share a private domain with a specific org"` ShareService v7.ShareServiceCommand `command:"share-service" description:"Share a service instance with another space"` + ShareRoute v7.ShareRouteCommand `command:"share-route" description:"Share a route in between spaces"` Space v7.SpaceCommand `command:"space" description:"Show space info"` SpaceQuota v7.SpaceQuotaCommand `command:"space-quota" description:"Show space quota info"` SpaceQuotas v7.SpaceQuotasCommand `command:"space-quotas" description:"List available space quotas"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index e6d19c3d859..8c6e9957f09 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -68,6 +68,7 @@ var HelpCategoryList = []HelpCategory{ {"create-route", "check-route", "map-route", "unmap-route", "delete-route"}, {"delete-orphaned-routes"}, {"update-destination"}, + {"share-route"}, }, }, { diff --git a/command/v7/actor.go b/command/v7/actor.go index b62365e4a90..64ef63dd1f0 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -216,6 +216,7 @@ type Actor interface { SetTarget(settings v7action.TargetSettings) (v7action.Warnings, error) SharePrivateDomain(domainName string, orgName string) (v7action.Warnings, error) ShareServiceInstanceToSpaceAndOrg(serviceInstanceName, targetedSpaceGUID, targetedOrgGUID string, sharedToDetails v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) + ShareRoute(routeGUID string, spaceGUID string) (v7action.Warnings, error) StageApplicationPackage(pkgGUID string) (resources.Build, v7action.Warnings, error) StagePackage(packageGUID, appName, spaceGUID string) (<-chan resources.Droplet, <-chan v7action.Warnings, <-chan error) StartApplication(appGUID string) (v7action.Warnings, error) diff --git a/command/v7/share_route_command.go b/command/v7/share_route_command.go new file mode 100644 index 00000000000..b1fdb2b6880 --- /dev/null +++ b/command/v7/share_route_command.go @@ -0,0 +1,101 @@ +package v7 + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/command/flag" +) + +type ShareRouteCommand struct { + BaseCommand + + RequireArgs flag.Domain `positional-args:"yes"` + Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` + Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` + DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` + DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` + + relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` +} + +func (cmd ShareRouteCommand) Usage() string { + return ` +Share an existing route in between two spaces: + CF_NAME share-route DOMAIN [--hostname HOSTNAME] [--path PATH] -s OTHER_SPACE [-o OTHER_ORG]` +} + +func (cmd ShareRouteCommand) Examples() string { + return ` +CF_NAME share-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo +CF_NAME share-route example.com --hostname myHost -s TargetSpace # myhost.example.com +CF_NAME share-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com` +} + +func (cmd ShareRouteCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + domain, warnings, err := cmd.Actor.GetDomainByName(cmd.RequireArgs.Domain) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + path := cmd.Path.Path + route, warnings, err := cmd.Actor.GetRouteByAttributes(domain, cmd.Hostname, path, 0) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + if _, ok := err.(actionerror.RouteNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + destinationOrgName := cmd.DestinationOrg + + if destinationOrgName == "" { + destinationOrgName = cmd.Config.TargetedOrganizationName() + } + + destinationOrg, warnings, err := cmd.Actor.GetOrganizationByName(destinationOrgName) + + if err != nil { + if _, ok := err.(actionerror.OrganizationNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + targetedSpace, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.DestinationSpace, destinationOrg.GUID) + if err != nil { + if _, ok := err.(actionerror.SpaceNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + url := desiredURL(domain.Name, cmd.Hostname, path, 0) + cmd.UI.DisplayTextWithFlavor("Sharing route {{.URL}} to space {{.DestinationSpace}} as {{.User}}", + map[string]interface{}{ + "URL": url, + "DestinationSpace": cmd.DestinationSpace, + "User": user.Name, + }) + warnings, err = cmd.Actor.ShareRoute( + route.GUID, + targetedSpace.GUID, + ) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/share_route_command_test.go b/command/v7/share_route_command_test.go new file mode 100644 index 00000000000..220efdafb30 --- /dev/null +++ b/command/v7/share_route_command_test.go @@ -0,0 +1,279 @@ +package v7_test + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/cf/errors" + "code.cloudfoundry.org/cli/command/commandfakes" + "code.cloudfoundry.org/cli/command/flag" + v7 "code.cloudfoundry.org/cli/command/v7" + "code.cloudfoundry.org/cli/command/v7/v7fakes" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" + "code.cloudfoundry.org/cli/util/ui" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("share-route Command", func() { + var ( + cmd v7.ShareRouteCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + binaryName string + executeErr error + domainName string + orgName string + spaceName string + hostname string + path string + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + binaryName = "myBinaryBread" + fakeConfig.BinaryNameReturns(binaryName) + + domainName = "some-domain.com" + orgName = "org-name-a" + spaceName = "space-name-a" + hostname = "myHostname" + path = "myPath" + + cmd = v7.ShareRouteCommand{ + BaseCommand: v7.BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequireArgs: flag.Domain{Domain: domainName}, + Hostname: hostname, + Path: flag.V7RoutePath{Path: path}, + DestinationOrg: orgName, + DestinationSpace: spaceName, + } + + fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space", GUID: "some-space-guid"}) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"}) + fakeConfig.CurrentUserReturns(configv3.User{Name: "some-user"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + It("checks that target", func() { + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) + }) + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("the user is not logged in", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = errors.New("some current user error") + fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) + }) + + It("return an error", func() { + Expect(executeErr).To(Equal(expectedErr)) + }) + }) + + When("the user is logged in and targeted", func() { + When("getting the domain errors", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns(resources.Domain{}, v7action.Warnings{"get-domain-warnings"}, errors.New("get-domain-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-domain-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(0)) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(0)) + + Expect(fakeActor.ShareRouteCallCount()).To(Equal(0)) + }) + }) + + When("getting the domain succeeds", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns( + resources.Domain{Name: domainName, GUID: "domain-guid"}, + v7action.Warnings{"get-domain-warnings"}, + nil, + ) + }) + + When("the requested route does not exist", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.RouteNotFoundError{}, + ) + }) + + It("displays error message", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + }) + }) + + When("the requested route exists", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{GUID: "route-guid"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + When("getting the target space errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.SpaceNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal(orgName)) + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + + Expect(fakeActor.ShareRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target org errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.OrganizationNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.ShareRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target space succeeds", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "space-guid-b"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + It("exits 0 with helpful message that the route is now being shared", func() { + Expect(executeErr).ShouldNot(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + Expect(fakeActor.ShareRouteCallCount()).To(Equal(1)) + }) + }) + }) + }) + }) +}) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 523de5ea272..1a950e4692e 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -2957,6 +2957,20 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + ShareRouteStub func(string, string) (v7action.Warnings, error) + shareRouteMutex sync.RWMutex + shareRouteArgsForCall []struct { + arg1 string + arg2 string + } + shareRouteReturns struct { + result1 v7action.Warnings + result2 error + } + shareRouteReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } ShareServiceInstanceToSpaceAndOrgStub func(string, string, string, v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) shareServiceInstanceToSpaceAndOrgMutex sync.RWMutex shareServiceInstanceToSpaceAndOrgArgsForCall []struct { @@ -16243,6 +16257,70 @@ func (fake *FakeActor) SharePrivateDomainReturnsOnCall(i int, result1 v7action.W }{result1, result2} } +func (fake *FakeActor) ShareRoute(arg1 string, arg2 string) (v7action.Warnings, error) { + fake.shareRouteMutex.Lock() + ret, specificReturn := fake.shareRouteReturnsOnCall[len(fake.shareRouteArgsForCall)] + fake.shareRouteArgsForCall = append(fake.shareRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("ShareRoute", []interface{}{arg1, arg2}) + fake.shareRouteMutex.Unlock() + if fake.ShareRouteStub != nil { + return fake.ShareRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.shareRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) ShareRouteCallCount() int { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + return len(fake.shareRouteArgsForCall) +} + +func (fake *FakeActor) ShareRouteCalls(stub func(string, string) (v7action.Warnings, error)) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = stub +} + +func (fake *FakeActor) ShareRouteArgsForCall(i int) (string, string) { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + argsForCall := fake.shareRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeActor) ShareRouteReturns(result1 v7action.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + fake.shareRouteReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) ShareRouteReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + if fake.shareRouteReturnsOnCall == nil { + fake.shareRouteReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.shareRouteReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) ShareServiceInstanceToSpaceAndOrg(arg1 string, arg2 string, arg3 string, arg4 v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) { fake.shareServiceInstanceToSpaceAndOrgMutex.Lock() ret, specificReturn := fake.shareServiceInstanceToSpaceAndOrgReturnsOnCall[len(fake.shareServiceInstanceToSpaceAndOrgArgsForCall)] @@ -19312,6 +19390,8 @@ func (fake *FakeActor) Invocations() map[string][][]interface{} { 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() diff --git a/integration/v7/isolated/share_route_command_test.go b/integration/v7/isolated/share_route_command_test.go new file mode 100644 index 00000000000..d3f8affe88b --- /dev/null +++ b/integration/v7/isolated/share_route_command_test.go @@ -0,0 +1,204 @@ +package isolated + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("share route command", func() { + Context("Help", func() { + It("appears in cf help -a", func() { + session := helpers.CF("help", "-a") + + Eventually(session).Should(Exit(0)) + Expect(session).To(HaveCommandInCategoryWithDescription("share-route", "ROUTES", "Share a route in between spaces")) + }) + + It("displays the help information", func() { + session := helpers.CF("share-route", "--help") + Eventually(session).Should(Say(`NAME:`)) + Eventually(session).Should(Say("share-route - Share a route in between spaces")) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`USAGE:`)) + Eventually(session).Should(Say(`Share an existing route in between two spaces:`)) + Eventually(session).Should(Say(`cf share-route DOMAIN \[--hostname HOSTNAME\] \[--path PATH\] -s OTHER_SPACE \[-o OTHER_ORG\]`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`EXAMPLES:`)) + Eventually(session).Should(Say(`cf share-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo`)) + Eventually(session).Should(Say(`cf share-route example.com --hostname myHost -s TargetSpace # myhost.example.com`)) + Eventually(session).Should(Say(`cf share-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`OPTIONS:`)) + Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) + Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) + Eventually(session).Should(Say(`-o\s+The org of the destination app \(Default: targeted org\)`)) + Eventually(session).Should(Say(`-s\s+The space of the destination app \(Default: targeted space\)`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`SEE ALSO:`)) + Eventually(session).Should(Say(`create-route, map-route, routes, unmap-route`)) + + Eventually(session).Should(Exit(0)) + }) + }) + + When("the environment is not setup correctly", func() { + It("fails with the appropriate errors", func() { + helpers.CheckEnvironmentTargetedCorrectly(true, false, ReadOnlyOrg, "share-route", "some-domain", "-s SOME_SPACE") + }) + }) + + When("the environment is set up conrrectly", func() { + var ( + userName string + orgName string + spaceName string + ) + + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionHTTP2RoutingV3) + orgName = helpers.NewOrgName() + spaceName = helpers.NewSpaceName() + + helpers.SetupCF(orgName, spaceName) + userName, _ = helpers.GetCredentials() + }) + + AfterEach(func() { + helpers.QuickDeleteOrg(orgName) + }) + + When("the domain extists", func() { + var ( + domainName string + targetSpaceName string + ) + + BeforeEach(func() { + domainName = helpers.NewDomainName() + }) + + When("the route exists", func() { + var ( + domain helpers.Domain + hostname string + ) + When("the target space exists in targeted org", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("shares the route to the destination space", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say(`Sharing route %s.%s to space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the target organization does not exist", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + targetOrgName = helpers.NewOrgName() + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 and an error message", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not share route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the target space exists in another existing org", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + targetOrgName = helpers.NewOrgName() + targetSpaceName = helpers.NewSpaceName() + helpers.CreateOrgAndSpace(targetOrgName, targetSpaceName) + helpers.SetupCF(orgName, spaceName) + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("shared the route to the destination space", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say(`Sharing route %s.%s to space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the space does not exist", func() { + var destinationSpaceName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + destinationSpaceName = "doesNotExistSpace" + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 with an error", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-s", destinationSpaceName) + Eventually(session).Should(Say("Can not share route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + + When("the route does not exist", func() { + var ( + domain helpers.Domain + hostname string + ) + + When("the target space exists", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.Create() + }) + + It("exits with 1 with an error message", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not share route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + }) + }) +})