diff --git a/api/cloudcontroller/ccversion/minimum_version.go b/api/cloudcontroller/ccversion/minimum_version.go index a0092171205..fcee5cdbdc9 100644 --- a/api/cloudcontroller/ccversion/minimum_version.go +++ b/api/cloudcontroller/ccversion/minimum_version.go @@ -16,5 +16,6 @@ const ( MinVersionLogRateLimitingV3 = "3.124.0" // TODO: update this when we have a CAPI release - MinVersionCNB = "3.168.0" + MinVersionCNB = "3.168.0" + MinVersionPerRouteOpts = "3.183.0" ) diff --git a/cf/actors/actorsfakes/fake_route_actor.go b/cf/actors/actorsfakes/fake_route_actor.go index 795612f4250..27324608ed9 100644 --- a/cf/actors/actorsfakes/fake_route_actor.go +++ b/cf/actors/actorsfakes/fake_route_actor.go @@ -62,7 +62,7 @@ type FakeRouteActor struct { result2 models.DomainFields result3 error } - FindOrCreateRouteStub func(string, models.DomainFields, string, int, bool) (models.Route, error) + FindOrCreateRouteStub func(string, models.DomainFields, string, int, bool, string) (models.Route, error) findOrCreateRouteMutex sync.RWMutex findOrCreateRouteArgsForCall []struct { arg1 string @@ -70,6 +70,7 @@ type FakeRouteActor struct { arg3 string arg4 int arg5 bool + arg6 string } findOrCreateRouteReturns struct { result1 models.Route @@ -374,7 +375,7 @@ func (fake *FakeRouteActor) FindDomainReturnsOnCall(i int, result1 string, resul }{result1, result2, result3} } -func (fake *FakeRouteActor) FindOrCreateRoute(arg1 string, arg2 models.DomainFields, arg3 string, arg4 int, arg5 bool) (models.Route, error) { +func (fake *FakeRouteActor) FindOrCreateRoute(arg1 string, arg2 models.DomainFields, arg3 string, arg4 int, arg5 bool, arg6 string) (models.Route, error) { fake.findOrCreateRouteMutex.Lock() ret, specificReturn := fake.findOrCreateRouteReturnsOnCall[len(fake.findOrCreateRouteArgsForCall)] fake.findOrCreateRouteArgsForCall = append(fake.findOrCreateRouteArgsForCall, struct { @@ -383,11 +384,12 @@ func (fake *FakeRouteActor) FindOrCreateRoute(arg1 string, arg2 models.DomainFie arg3 string arg4 int arg5 bool - }{arg1, arg2, arg3, arg4, arg5}) - fake.recordInvocation("FindOrCreateRoute", []interface{}{arg1, arg2, arg3, arg4, arg5}) + arg6 string + }{arg1, arg2, arg3, arg4, arg5, arg6}) + fake.recordInvocation("FindOrCreateRoute", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6}) fake.findOrCreateRouteMutex.Unlock() if fake.FindOrCreateRouteStub != nil { - return fake.FindOrCreateRouteStub(arg1, arg2, arg3, arg4, arg5) + return fake.FindOrCreateRouteStub(arg1, arg2, arg3, arg4, arg5, arg6) } if specificReturn { return ret.result1, ret.result2 @@ -402,17 +404,17 @@ func (fake *FakeRouteActor) FindOrCreateRouteCallCount() int { return len(fake.findOrCreateRouteArgsForCall) } -func (fake *FakeRouteActor) FindOrCreateRouteCalls(stub func(string, models.DomainFields, string, int, bool) (models.Route, error)) { +func (fake *FakeRouteActor) FindOrCreateRouteCalls(stub func(string, models.DomainFields, string, int, bool, string) (models.Route, error)) { fake.findOrCreateRouteMutex.Lock() defer fake.findOrCreateRouteMutex.Unlock() fake.FindOrCreateRouteStub = stub } -func (fake *FakeRouteActor) FindOrCreateRouteArgsForCall(i int) (string, models.DomainFields, string, int, bool) { +func (fake *FakeRouteActor) FindOrCreateRouteArgsForCall(i int) (string, models.DomainFields, string, int, bool, string) { fake.findOrCreateRouteMutex.RLock() defer fake.findOrCreateRouteMutex.RUnlock() argsForCall := fake.findOrCreateRouteArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6 } func (fake *FakeRouteActor) FindOrCreateRouteReturns(result1 models.Route, result2 error) { diff --git a/cf/actors/routes.go b/cf/actors/routes.go index 0b360480504..1f1344344b0 100644 --- a/cf/actors/routes.go +++ b/cf/actors/routes.go @@ -19,7 +19,7 @@ const tcp = "tcp" type RouteActor interface { CreateRandomTCPRoute(domain models.DomainFields) (models.Route, error) - FindOrCreateRoute(hostname string, domain models.DomainFields, path string, port int, useRandomPort bool) (models.Route, error) + FindOrCreateRoute(hostname string, domain models.DomainFields, path string, port int, useRandomPort bool, option string) (models.Route, error) BindRoute(app models.Application, route models.Route) error UnbindAll(app models.Application) error FindDomain(routeName string) (string, models.DomainFields, error) @@ -47,7 +47,7 @@ func (routeActor routeActor) CreateRandomTCPRoute(domain models.DomainFields) (m "Domain": terminal.EntityNameColor(domain.Name), }) + "...") - route, err := routeActor.routeRepo.Create("", domain, "", 0, true) + route, err := routeActor.routeRepo.Create("", domain, "", 0, true, "") if err != nil { return models.Route{}, err } @@ -55,7 +55,7 @@ func (routeActor routeActor) CreateRandomTCPRoute(domain models.DomainFields) (m return route, nil } -func (routeActor routeActor) FindOrCreateRoute(hostname string, domain models.DomainFields, path string, port int, useRandomPort bool) (models.Route, error) { +func (routeActor routeActor) FindOrCreateRoute(hostname string, domain models.DomainFields, path string, port int, useRandomPort bool, option string) (models.Route, error) { var route models.Route var err error //if tcp route use random port should skip route lookup @@ -84,7 +84,7 @@ func (routeActor routeActor) FindOrCreateRoute(hostname string, domain models.Do }), ) - route, err = routeActor.routeRepo.Create(hostname, domain, path, port, false) + route, err = routeActor.routeRepo.Create(hostname, domain, path, port, false, option) } routeActor.ui.Ok() @@ -223,7 +223,7 @@ func (routeActor routeActor) FindAndBindRoute(routeName string, app models.Appli return err } - route, err := routeActor.FindOrCreateRoute(hostname, domain, path, port, appParamsFromContext.UseRandomRoute) + route, err := routeActor.FindOrCreateRoute(hostname, domain, path, port, appParamsFromContext.UseRandomRoute, "") if err != nil { return err } diff --git a/cf/actors/routes_test.go b/cf/actors/routes_test.go index 95176a5042e..44540ff57c9 100644 --- a/cf/actors/routes_test.go +++ b/cf/actors/routes_test.go @@ -22,6 +22,7 @@ var _ = Describe("Routes", func() { expectedRoute models.Route expectedDomain models.DomainFields + option string ) BeforeEach(func() { @@ -47,10 +48,11 @@ var _ = Describe("Routes", func() { It("calls Create on the route repo", func() { routeActor.CreateRandomTCPRoute(expectedDomain) - host, d, path, port, randomPort := fakeRouteRepository.CreateArgsForCall(0) + host, d, path, port, randomPort, option := fakeRouteRepository.CreateArgsForCall(0) Expect(host).To(BeEmpty()) Expect(d).To(Equal(expectedDomain)) Expect(path).To(BeEmpty()) + Expect(option).To(BeEmpty()) Expect(port).To(Equal(0)) Expect(randomPort).To(BeTrue()) }) @@ -83,11 +85,13 @@ var _ = Describe("Routes", func() { var ( expectedHostname string expectedPath string + expectedOption string ) BeforeEach(func() { expectedHostname = "hostname" expectedPath = "path" + expectedOption = "" expectedDomain = models.DomainFields{ Name: "foo.com", @@ -98,6 +102,7 @@ var _ = Describe("Routes", func() { Domain: expectedDomain, Host: expectedHostname, Path: expectedPath, + //Options: map[string]string{}, } }) @@ -107,7 +112,7 @@ var _ = Describe("Routes", func() { }) It("does not create a route", func() { - route, err := routeActor.FindOrCreateRoute(expectedHostname, expectedDomain, expectedPath, 0, false) + route, err := routeActor.FindOrCreateRoute(expectedHostname, expectedDomain, expectedPath, 0, false, "") Expect(route).To(Equal(expectedRoute)) Expect(err).ToNot(HaveOccurred()) @@ -133,17 +138,18 @@ var _ = Describe("Routes", func() { }) It("creates a route with a TCP Route", func() { - route, err := routeActor.FindOrCreateRoute("", expectedDomain, "", 0, true) + route, err := routeActor.FindOrCreateRoute("", expectedDomain, "", 0, true, "") Expect(route).To(Equal(tcpRoute)) Expect(err).ToNot(HaveOccurred()) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - hostname, domain, path, port, randomPort := fakeRouteRepository.CreateArgsForCall(0) + hostname, domain, path, port, randomPort, option := fakeRouteRepository.CreateArgsForCall(0) Expect(hostname).To(BeEmpty()) Expect(domain).To(Equal(expectedDomain)) Expect(path).To(BeEmpty()) Expect(port).To(Equal(0)) Expect(randomPort).To(BeTrue()) + Expect(option).To(BeEmpty()) Expect(fakeUI.SayCallCount()).To(Equal(2)) output, _ := fakeUI.SayArgsForCall(0) @@ -157,17 +163,18 @@ var _ = Describe("Routes", func() { }) It("creates a route ", func() { - route, err := routeActor.FindOrCreateRoute(expectedHostname, expectedDomain, "", 1337, false) + route, err := routeActor.FindOrCreateRoute(expectedHostname, expectedDomain, "", 1337, false, "") Expect(route).To(Equal(expectedRoute)) Expect(err).ToNot(HaveOccurred()) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - hostname, domain, path, port, randomPort := fakeRouteRepository.CreateArgsForCall(0) + hostname, domain, path, port, randomPort, option := fakeRouteRepository.CreateArgsForCall(0) Expect(hostname).To(Equal(expectedHostname)) Expect(domain).To(Equal(expectedDomain)) Expect(path).To(Equal("")) Expect(port).To(Equal(1337)) Expect(randomPort).To(BeFalse()) + Expect(option).To(BeEmpty()) Expect(fakeUI.SayCallCount()).To(Equal(2)) output, _ := fakeUI.SayArgsForCall(0) @@ -181,17 +188,18 @@ var _ = Describe("Routes", func() { }) It("creates a route ", func() { - route, err := routeActor.FindOrCreateRoute(expectedHostname, expectedDomain, expectedPath, 0, false) + route, err := routeActor.FindOrCreateRoute(expectedHostname, expectedDomain, expectedPath, 0, false, expectedOption) Expect(route).To(Equal(expectedRoute)) Expect(err).ToNot(HaveOccurred()) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - hostname, domain, path, port, randomPort := fakeRouteRepository.CreateArgsForCall(0) + hostname, domain, path, port, randomPort, option := fakeRouteRepository.CreateArgsForCall(0) Expect(hostname).To(Equal(expectedHostname)) Expect(domain).To(Equal(expectedDomain)) Expect(path).To(Equal(expectedPath)) Expect(port).To(Equal(0)) Expect(randomPort).To(BeFalse()) + Expect(option).To(BeEmpty()) Expect(fakeUI.SayCallCount()).To(Equal(2)) output, _ := fakeUI.SayArgsForCall(0) @@ -537,6 +545,7 @@ var _ = Describe("Routes", func() { Context("when the route is a HTTP route", func() { var httpDomain models.DomainFields + var option string BeforeEach(func() { httpDomain = models.DomainFields{ @@ -593,12 +602,13 @@ var _ = Describe("Routes", func() { Expect(actualPath).To(Equal("")) Expect(actualPort).To(Equal(0)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("host")) Expect(actualDomain).To(Equal(httpDomain)) Expect(actualPath).To(Equal("")) Expect(actualPort).To(Equal(0)) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) routeGUID, appGUID := fakeRouteRepository.BindArgsForCall(0) Expect(routeGUID).To(Equal("route-guid")) @@ -622,12 +632,13 @@ var _ = Describe("Routes", func() { Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("host")) Expect(actualDomain).To(Equal(httpDomain)) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) routeGUID, appGUID := fakeRouteRepository.BindArgsForCall(0) Expect(routeGUID).To(Equal("route-guid")) @@ -706,8 +717,10 @@ var _ = Describe("Routes", func() { }) Context("when the hostname is present in the original route", func() { + var option string BeforeEach(func() { routeName = "hostname.old-domain.com/path" + option = "" }) It("replace the domain from manifest", func() { @@ -716,22 +729,25 @@ var _ = Describe("Routes", func() { Expect(fakeRouteRepository.FindCallCount()).To(Equal(1)) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("hostname")) Expect(actualDomain.Name).To(Equal("shared-domain.com")) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) }) }) Context("when the hostname is provided as a flag", func() { + var option string BeforeEach(func() { routeName = "old-domain.com/path" appParamsFromContext = models.AppParams{ Domains: []string{"shared-domain.com"}, Hosts: []string{"hostname"}, } + option = "" }) It("replace the domain from manifest", func() { @@ -740,16 +756,18 @@ var _ = Describe("Routes", func() { Expect(fakeRouteRepository.FindCallCount()).To(Equal(1)) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("hostname")) Expect(actualDomain.Name).To(Equal("shared-domain.com")) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) }) }) Context("when the path is provided as a flag", func() { + var option string BeforeEach(func() { routeName = "hostname.old-domain.com/oldpath" path := "path" @@ -757,6 +775,7 @@ var _ = Describe("Routes", func() { Domains: []string{"shared-domain.com"}, RoutePath: &path, } + option = "" }) It("replace the domain and path from manifest", func() { @@ -765,17 +784,19 @@ var _ = Describe("Routes", func() { Expect(fakeRouteRepository.FindCallCount()).To(Equal(1)) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("hostname")) Expect(actualDomain.Name).To(Equal("shared-domain.com")) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) }) }) }) Context("when it is a private domain", func() { + var option string BeforeEach(func() { httpDomain := models.DomainFields{ Name: "private-domain.com", @@ -790,6 +811,7 @@ var _ = Describe("Routes", func() { appParamsFromContext = models.AppParams{ Domains: []string{"private-domain.com"}, } + option = "" }) It("replace the domain from manifest", func() { @@ -798,12 +820,13 @@ var _ = Describe("Routes", func() { Expect(fakeRouteRepository.FindCallCount()).To(Equal(1)) Expect(fakeRouteRepository.CreateCallCount()).To(Equal(1)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(BeEmpty()) Expect(actualDomain.Name).To(Equal("private-domain.com")) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(BeZero()) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) }) }) }) @@ -839,6 +862,7 @@ var _ = Describe("Routes", func() { Context("the route does not have a hostname", func() { BeforeEach(func() { routeName = "domain.com/path" + option = "" }) It("should append a random name ", func() { Expect(findAndBindRouteErr).NotTo(HaveOccurred()) @@ -848,12 +872,13 @@ var _ = Describe("Routes", func() { Expect(actualDomain.Name).To(Equal("domain.com")) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) - actualHost, actualDomain, actualPath, actualPort, useRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, useRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(MatchRegexp("[a-z]-[a-z]")) Expect(actualDomain.Name).To(Equal("domain.com")) Expect(actualPath).To(Equal("path")) Expect(actualPort).To(Equal(0)) Expect(useRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) }) }) @@ -920,12 +945,13 @@ var _ = Describe("Routes", func() { Expect(findAndBindRouteErr).NotTo(HaveOccurred()) Expect(fakeRouteRepository.FindCallCount()).To(Equal(0)) - actualHost, actualDomain, actualPath, actualPort, useRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, useRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("")) Expect(actualDomain.Name).To(Equal("tcp-domain.com")) Expect(actualPath).To(Equal("")) Expect(actualPort).To(Equal(0)) Expect(useRandomPort).To(Equal(true)) + Expect(actualOption).To(Equal(option)) }) }) }) @@ -1084,12 +1110,13 @@ var _ = Describe("Routes", func() { Expect(actualPath).To(Equal("")) Expect(actualPort).To(Equal(3333)) - actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort := fakeRouteRepository.CreateArgsForCall(0) + actualHost, actualDomain, actualPath, actualPort, actualUseRandomPort, actualOption := fakeRouteRepository.CreateArgsForCall(0) Expect(actualHost).To(Equal("")) Expect(actualDomain).To(Equal(tcpDomain)) Expect(actualPath).To(Equal("")) Expect(actualPort).To(Equal(3333)) Expect(actualUseRandomPort).To(BeFalse()) + Expect(actualOption).To(Equal(option)) routeGUID, appGUID := fakeRouteRepository.BindArgsForCall(0) Expect(routeGUID).To(Equal("route-guid")) diff --git a/cf/api/apifakes/fake_route_repository.go b/cf/api/apifakes/fake_route_repository.go index 1400d7ee04a..55fcd8c21f8 100644 --- a/cf/api/apifakes/fake_route_repository.go +++ b/cf/api/apifakes/fake_route_repository.go @@ -36,7 +36,7 @@ type FakeRouteRepository struct { result1 bool result2 error } - CreateStub func(string, models.DomainFields, string, int, bool) (models.Route, error) + CreateStub func(string, models.DomainFields, string, int, bool, string) (models.Route, error) createMutex sync.RWMutex createArgsForCall []struct { arg1 string @@ -44,6 +44,7 @@ type FakeRouteRepository struct { arg3 string arg4 int arg5 bool + arg6 string } createReturns struct { result1 models.Route @@ -53,7 +54,7 @@ type FakeRouteRepository struct { result1 models.Route result2 error } - CreateInSpaceStub func(string, string, string, string, int, bool) (models.Route, error) + CreateInSpaceStub func(string, string, string, string, int, bool, string) (models.Route, error) createInSpaceMutex sync.RWMutex createInSpaceArgsForCall []struct { arg1 string @@ -62,6 +63,7 @@ type FakeRouteRepository struct { arg4 string arg5 int arg6 bool + arg7 string } createInSpaceReturns struct { result1 models.Route @@ -262,7 +264,7 @@ func (fake *FakeRouteRepository) CheckIfExistsReturnsOnCall(i int, result1 bool, }{result1, result2} } -func (fake *FakeRouteRepository) Create(arg1 string, arg2 models.DomainFields, arg3 string, arg4 int, arg5 bool) (models.Route, error) { +func (fake *FakeRouteRepository) Create(arg1 string, arg2 models.DomainFields, arg3 string, arg4 int, arg5 bool, arg6 string) (models.Route, error) { fake.createMutex.Lock() ret, specificReturn := fake.createReturnsOnCall[len(fake.createArgsForCall)] fake.createArgsForCall = append(fake.createArgsForCall, struct { @@ -271,11 +273,12 @@ func (fake *FakeRouteRepository) Create(arg1 string, arg2 models.DomainFields, a arg3 string arg4 int arg5 bool - }{arg1, arg2, arg3, arg4, arg5}) - fake.recordInvocation("Create", []interface{}{arg1, arg2, arg3, arg4, arg5}) + arg6 string + }{arg1, arg2, arg3, arg4, arg5, arg6}) + fake.recordInvocation("Create", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6}) fake.createMutex.Unlock() if fake.CreateStub != nil { - return fake.CreateStub(arg1, arg2, arg3, arg4, arg5) + return fake.CreateStub(arg1, arg2, arg3, arg4, arg5, arg6) } if specificReturn { return ret.result1, ret.result2 @@ -290,17 +293,17 @@ func (fake *FakeRouteRepository) CreateCallCount() int { return len(fake.createArgsForCall) } -func (fake *FakeRouteRepository) CreateCalls(stub func(string, models.DomainFields, string, int, bool) (models.Route, error)) { +func (fake *FakeRouteRepository) CreateCalls(stub func(string, models.DomainFields, string, int, bool, string) (models.Route, error)) { fake.createMutex.Lock() defer fake.createMutex.Unlock() fake.CreateStub = stub } -func (fake *FakeRouteRepository) CreateArgsForCall(i int) (string, models.DomainFields, string, int, bool) { +func (fake *FakeRouteRepository) CreateArgsForCall(i int) (string, models.DomainFields, string, int, bool, string) { fake.createMutex.RLock() defer fake.createMutex.RUnlock() argsForCall := fake.createArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6 } func (fake *FakeRouteRepository) CreateReturns(result1 models.Route, result2 error) { @@ -329,7 +332,7 @@ func (fake *FakeRouteRepository) CreateReturnsOnCall(i int, result1 models.Route }{result1, result2} } -func (fake *FakeRouteRepository) CreateInSpace(arg1 string, arg2 string, arg3 string, arg4 string, arg5 int, arg6 bool) (models.Route, error) { +func (fake *FakeRouteRepository) CreateInSpace(arg1 string, arg2 string, arg3 string, arg4 string, arg5 int, arg6 bool, arg7 string) (models.Route, error) { fake.createInSpaceMutex.Lock() ret, specificReturn := fake.createInSpaceReturnsOnCall[len(fake.createInSpaceArgsForCall)] fake.createInSpaceArgsForCall = append(fake.createInSpaceArgsForCall, struct { @@ -339,11 +342,12 @@ func (fake *FakeRouteRepository) CreateInSpace(arg1 string, arg2 string, arg3 st arg4 string arg5 int arg6 bool - }{arg1, arg2, arg3, arg4, arg5, arg6}) + arg7 string + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) fake.recordInvocation("CreateInSpace", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6}) fake.createInSpaceMutex.Unlock() if fake.CreateInSpaceStub != nil { - return fake.CreateInSpaceStub(arg1, arg2, arg3, arg4, arg5, arg6) + return fake.CreateInSpaceStub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } if specificReturn { return ret.result1, ret.result2 @@ -358,17 +362,17 @@ func (fake *FakeRouteRepository) CreateInSpaceCallCount() int { return len(fake.createInSpaceArgsForCall) } -func (fake *FakeRouteRepository) CreateInSpaceCalls(stub func(string, string, string, string, int, bool) (models.Route, error)) { +func (fake *FakeRouteRepository) CreateInSpaceCalls(stub func(string, string, string, string, int, bool, string) (models.Route, error)) { fake.createInSpaceMutex.Lock() defer fake.createInSpaceMutex.Unlock() fake.CreateInSpaceStub = stub } -func (fake *FakeRouteRepository) CreateInSpaceArgsForCall(i int) (string, string, string, string, int, bool) { +func (fake *FakeRouteRepository) CreateInSpaceArgsForCall(i int) (string, string, string, string, int, bool, string) { fake.createInSpaceMutex.RLock() defer fake.createInSpaceMutex.RUnlock() argsForCall := fake.createInSpaceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7 } func (fake *FakeRouteRepository) CreateInSpaceReturns(result1 models.Route, result2 error) { diff --git a/cf/api/resources/routes.go b/cf/api/resources/routes.go index 55e8ac47497..b7458b3dac9 100644 --- a/cf/api/resources/routes.go +++ b/cf/api/resources/routes.go @@ -15,6 +15,7 @@ type RouteEntity struct { Space SpaceResource `json:"space"` Apps []ApplicationResource `json:"apps"` ServiceInstance ServiceInstanceResource `json:"service_instance"` + Options map[string]string `json:"options"` } func (resource RouteResource) ToFields() (fields models.Route) { @@ -30,6 +31,7 @@ func (resource RouteResource) ToModel() (route models.Route) { route.GUID = resource.Metadata.GUID route.Domain = resource.Entity.Domain.ToFields() route.Space = resource.Entity.Space.ToFields() + route.Options = resource.Entity.Options route.ServiceInstance = resource.Entity.ServiceInstance.ToFields() for _, appResource := range resource.Entity.Apps { route.Apps = append(route.Apps, appResource.ToFields()) diff --git a/cf/api/routes.go b/cf/api/routes.go index 0fc4f405cf8..6bef8387a50 100644 --- a/cf/api/routes.go +++ b/cf/api/routes.go @@ -21,9 +21,9 @@ type RouteRepository interface { ListRoutes(cb func(models.Route) bool) (apiErr error) ListAllRoutes(cb func(models.Route) bool) (apiErr error) Find(host string, domain models.DomainFields, path string, port int) (route models.Route, apiErr error) - Create(host string, domain models.DomainFields, path string, port int, useRandomPort bool) (createdRoute models.Route, apiErr error) + Create(host string, domain models.DomainFields, path string, port int, useRandomPort bool, option string) (createdRoute models.Route, apiErr error) CheckIfExists(host string, domain models.DomainFields, path string) (found bool, apiErr error) - CreateInSpace(host, path, domainGUID, spaceGUID string, port int, randomPort bool) (createdRoute models.Route, apiErr error) + CreateInSpace(host, path, domainGUID, spaceGUID string, port int, randomPort bool, option string) (createdRoute models.Route, apiErr error) Bind(routeGUID, appGUID string) (apiErr error) Unbind(routeGUID, appGUID string) (apiErr error) Delete(routeGUID string) (apiErr error) @@ -123,8 +123,8 @@ func doesNotMatchVersionSpecificAttributes(route models.Route, path string, port return normalizedPath(route.Path) != normalizedPath(path) || route.Port != port } -func (repo CloudControllerRouteRepository) Create(host string, domain models.DomainFields, path string, port int, useRandomPort bool) (createdRoute models.Route, apiErr error) { - return repo.CreateInSpace(host, path, domain.GUID, repo.config.SpaceFields().GUID, port, useRandomPort) +func (repo CloudControllerRouteRepository) Create(host string, domain models.DomainFields, path string, port int, useRandomPort bool, option string) (createdRoute models.Route, apiErr error) { + return repo.CreateInSpace(host, path, domain.GUID, repo.config.SpaceFields().GUID, port, useRandomPort, option) } func (repo CloudControllerRouteRepository) CheckIfExists(host string, domain models.DomainFields, path string) (bool, error) { @@ -154,7 +154,7 @@ func (repo CloudControllerRouteRepository) CheckIfExists(host string, domain mod return true, nil } -func (repo CloudControllerRouteRepository) CreateInSpace(host, path, domainGUID, spaceGUID string, port int, randomPort bool) (models.Route, error) { +func (repo CloudControllerRouteRepository) CreateInSpace(host, path, domainGUID, spaceGUID string, port int, randomPort bool, option string) (models.Route, error) { path = normalizedPath(path) body := struct { @@ -179,6 +179,11 @@ func (repo CloudControllerRouteRepository) CreateInSpace(host, path, domainGUID, uriFragment := "/v2/routes?" + opt.Encode() resource := new(resources.RouteResource) + key, value, found := strings.Cut(option, "=") + if found { + resource.Entity.Options[key] = value + } + err = repo.gateway.CreateResource( repo.config.APIEndpoint(), uriFragment, diff --git a/cf/api/routes_test.go b/cf/api/routes_test.go index e1862374acf..4906f4c38ce 100644 --- a/cf/api/routes_test.go +++ b/cf/api/routes_test.go @@ -291,7 +291,7 @@ var _ = Describe("route repository", func() { }) It("tries to create a route", func() { - repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, false) + repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, false, "") Expect(ccServer.ReceivedRequests()).To(HaveLen(1)) }) @@ -310,7 +310,7 @@ var _ = Describe("route repository", func() { }) It("returns the created route", func() { - createdRoute, err := repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, false) + createdRoute, err := repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, false, "") Expect(err).NotTo(HaveOccurred()) Expect(createdRoute.GUID).To(Equal("my-route-guid")) }) @@ -337,7 +337,7 @@ var _ = Describe("route repository", func() { }) It("tries to create a route", func() { - repo.CreateInSpace("the-host", "", "my-domain-guid", "my-space-guid", 0, false) + repo.CreateInSpace("the-host", "", "my-domain-guid", "my-space-guid", 0, false, "") Expect(ccServer.ReceivedRequests()).To(HaveLen(1)) }) @@ -356,7 +356,7 @@ var _ = Describe("route repository", func() { }) It("returns the created route", func() { - createdRoute, err := repo.CreateInSpace("the-host", "", "my-domain-guid", "my-space-guid", 0, false) + createdRoute, err := repo.CreateInSpace("the-host", "", "my-domain-guid", "my-space-guid", 0, false, "") Expect(err).NotTo(HaveOccurred()) Expect(createdRoute.Host).To(Equal("the-host")) }) @@ -383,7 +383,7 @@ var _ = Describe("route repository", func() { }) It("tries to create a route", func() { - repo.CreateInSpace("", "the-path", "my-domain-guid", "my-space-guid", 0, false) + repo.CreateInSpace("", "the-path", "my-domain-guid", "my-space-guid", 0, false, "") Expect(ccServer.ReceivedRequests()).To(HaveLen(1)) }) @@ -402,7 +402,7 @@ var _ = Describe("route repository", func() { }) It("returns the created route", func() { - createdRoute, err := repo.CreateInSpace("", "the-path", "my-domain-guid", "my-space-guid", 0, false) + createdRoute, err := repo.CreateInSpace("", "the-path", "my-domain-guid", "my-space-guid", 0, false, "") Expect(err).NotTo(HaveOccurred()) Expect(createdRoute.Path).To(Equal("the-path")) }) @@ -415,7 +415,7 @@ var _ = Describe("route repository", func() { }) It("returns an error", func() { - _, err := repo.CreateInSpace("", "the-path", "my-domain-guid", "my-space-guid", 0, false) + _, err := repo.CreateInSpace("", "the-path", "my-domain-guid", "my-space-guid", 0, false, "") Expect(err).To(HaveOccurred()) }) }) @@ -441,7 +441,7 @@ var _ = Describe("route repository", func() { }) It("tries to create a route", func() { - repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 9090, false) + repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 9090, false, "") Expect(ccServer.ReceivedRequests()).To(HaveLen(1)) }) @@ -460,7 +460,7 @@ var _ = Describe("route repository", func() { }) It("returns the created route", func() { - createdRoute, err := repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 9090, false) + createdRoute, err := repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 9090, false, "") Expect(err).NotTo(HaveOccurred()) Expect(createdRoute.Port).To(Equal(9090)) }) @@ -486,7 +486,7 @@ var _ = Describe("route repository", func() { }) It("tries to create a route", func() { - repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, true) + repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, true, "") Expect(ccServer.ReceivedRequests()).To(HaveLen(1)) }) @@ -505,7 +505,7 @@ var _ = Describe("route repository", func() { }) It("returns the created route", func() { - createdRoute, err := repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, true) + createdRoute, err := repo.CreateInSpace("", "", "my-domain-guid", "my-space-guid", 0, true, "") Expect(err).NotTo(HaveOccurred()) Expect(createdRoute.Port).To(Equal(50321)) }) diff --git a/cf/commands/application/push.go b/cf/commands/application/push.go index 8c48a7839a3..9def1270691 100644 --- a/cf/commands/application/push.go +++ b/cf/commands/application/push.go @@ -449,9 +449,9 @@ func (cmd *Push) createAndBindRoute( var route models.Route var err error if routePath != nil { - route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, *routePath, 0, UseRandomPort) + route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, *routePath, 0, UseRandomPort, "") } else { - route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, "", 0, UseRandomPort) + route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, "", 0, UseRandomPort, "") } if err != nil { return err diff --git a/cf/commands/application/push_test.go b/cf/commands/application/push_test.go index 2447c675510..c26bc62ea39 100644 --- a/cf/commands/application/push_test.go +++ b/cf/commands/application/push_test.go @@ -464,7 +464,7 @@ var _ = Describe("Push Command", func() { }), } manifestRepo.ReadManifestReturns(m, nil) - routeRepo.CreateStub = func(host string, domain models.DomainFields, _ string, _ int, _ bool) (models.Route, error) { + routeRepo.CreateStub = func(host string, domain models.DomainFields, _ string, _ int, _ bool, _ string) (models.Route, error) { return models.Route{ GUID: "my-route-guid", Host: host, @@ -487,7 +487,7 @@ var _ = Describe("Push Command", func() { } callCount := 0 - routeActor.FindOrCreateRouteStub = func(hostname string, domain models.DomainFields, path string, _ int, useRandomPort bool) (models.Route, error) { + routeActor.FindOrCreateRouteStub = func(hostname string, domain models.DomainFields, path string, _ int, useRandomPort bool, _ string) (models.Route, error) { callCount = callCount + 1 switch callCount { case 1: @@ -553,7 +553,7 @@ var _ = Describe("Push Command", func() { } callCount := 0 - routeActor.FindOrCreateRouteStub = func(hostname string, domain models.DomainFields, path string, _ int, useRandomPort bool) (models.Route, error) { + routeActor.FindOrCreateRouteStub = func(hostname string, domain models.DomainFields, path string, _ int, useRandomPort bool, _ string) (models.Route, error) { callCount = callCount + 1 switch callCount { case 1: @@ -593,7 +593,7 @@ var _ = Describe("Push Command", func() { Context("when pushing an app", func() { BeforeEach(func() { deps.UI = uiWithContents - routeRepo.CreateStub = func(host string, domain models.DomainFields, _ string, _ int, _ bool) (models.Route, error) { + routeRepo.CreateStub = func(host string, domain models.DomainFields, _ string, _ int, _ bool, _ string) (models.Route, error) { return models.Route{ GUID: "my-route-guid", Host: host, @@ -665,7 +665,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) + host, _, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(host).To(Equal("manifestapp-nam")) }) }) @@ -680,7 +680,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) + host, _, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(host).To(Equal("appname")) }) }) @@ -1043,7 +1043,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) + host, _, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(host).To(Equal("app-name-random-host")) }) }) @@ -1058,7 +1058,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) + host, _, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(host).To(Equal("app-name-random-host")) }) }) @@ -1100,7 +1100,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - _, _, _, _, randomPort := routeActor.FindOrCreateRouteArgsForCall(0) + _, _, _, _, randomPort, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(randomPort).To(BeTrue()) }) }) @@ -1115,7 +1115,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - _, _, _, _, randomPort := routeActor.FindOrCreateRouteArgsForCall(0) + _, _, _, _, randomPort, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(randomPort).To(BeTrue()) }) }) @@ -1403,7 +1403,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - _, domain, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) + _, domain, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(domain.GUID).To(Equal("bar-domain-guid")) }) @@ -1768,7 +1768,7 @@ var _ = Describe("Push Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) - hostname, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) + hostname, _, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) Expect(hostname).To(BeEmpty()) }) }) diff --git a/cf/commands/route/create_route.go b/cf/commands/route/create_route.go index d9c0cd3c707..64ea154151f 100644 --- a/cf/commands/route/create_route.go +++ b/cf/commands/route/create_route.go @@ -1,8 +1,7 @@ package route import ( - "fmt" - + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/cf/api" "code.cloudfoundry.org/cli/cf/commandregistry" "code.cloudfoundry.org/cli/cf/configuration/coreconfig" @@ -11,12 +10,14 @@ import ( "code.cloudfoundry.org/cli/cf/models" "code.cloudfoundry.org/cli/cf/requirements" "code.cloudfoundry.org/cli/cf/terminal" + "code.cloudfoundry.org/cli/command" + "fmt" ) //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Creator type Creator interface { - CreateRoute(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields) (route models.Route, apiErr error) + CreateRoute(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields, option string) (route models.Route, apiErr error) } type CreateRoute struct { @@ -37,6 +38,7 @@ func (cmd *CreateRoute) MetaData() commandregistry.CommandMetadata { fs["path"] = &flags.StringFlag{Name: "path", Usage: T("Path for the HTTP route")} fs["port"] = &flags.IntFlag{Name: "port", Usage: T("Port for the TCP route")} fs["random-port"] = &flags.BoolFlag{Name: "random-port", Usage: T("Create a random port for the TCP route")} + fs["option"] = &flags.StringSliceFlag{Name: "option", ShortName: "o", Usage: T("Set the value of a per-route option")} return commandregistry.CommandMetadata{ Name: "create-route", @@ -52,13 +54,15 @@ func (cmd *CreateRoute) MetaData() commandregistry.CommandMetadata { " CF_NAME create-route ", fmt.Sprintf("%s ", T("SPACE")), fmt.Sprintf("%s ", T("DOMAIN")), - fmt.Sprintf("(--port %s | --random-port)", T("PORT")), + fmt.Sprintf("(--port %s | --random-port) ", T("PORT")), + fmt.Sprintf("[--option %s=%s]\n\n", T("OPTION"), T("VALUE")), }, Examples: []string{ - "CF_NAME create-route my-space example.com # example.com", - "CF_NAME create-route my-space example.com --hostname myapp # myapp.example.com", - "CF_NAME create-route my-space example.com --hostname myapp --path foo # myapp.example.com/foo", - "CF_NAME create-route my-space example.com --port 50000 # example.com:50000", + "CF_NAME create-route my-space example.com # example.com", + "CF_NAME create-route my-space example.com --hostname myapp # myapp.example.com", + "CF_NAME create-route my-space example.com --hostname myapp --path foo # myapp.example.com/foo", + "CF_NAME create-route my-space example.com --port 50000 # example.com:50000", + "CF_NAME create-route my-space example.com -o loadbalancing=round-robin", }, Flags: fs, } @@ -109,8 +113,19 @@ func (cmd *CreateRoute) Execute(c flags.FlagContext) error { path := c.String("path") port := c.Int("port") randomPort := c.Bool("random-port") - - _, err := cmd.CreateRoute(hostName, path, port, randomPort, domain, space.SpaceFields) + option := c.String("o") + + if option != "" { + err := command.MinimumCCAPIVersionCheck(cmd.config.APIVersion(), ccversion.MinVersionPerRouteOpts) + if err != nil { + cmd.ui.Say(T("Your CC API version ({{.APIVersion}}) does not support per route options."+ + "Upgrade to a newer version of the API (minimum version {{.MinSupportedVersion}}). ", map[string]interface{}{ + "APIVersion": cmd.config.APIVersion(), + "MinSupportedVersion": ccversion.MinVersionPerRouteOpts, + })) + } + } + _, err := cmd.CreateRoute(hostName, path, port, randomPort, domain, space.SpaceFields, option) if err != nil { return err } @@ -118,7 +133,7 @@ func (cmd *CreateRoute) Execute(c flags.FlagContext) error { return nil } -func (cmd *CreateRoute) CreateRoute(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields) (models.Route, error) { +func (cmd *CreateRoute) CreateRoute(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields, option string) (models.Route, error) { cmd.ui.Say(T("Creating route {{.URL}} for org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ "URL": terminal.EntityNameColor(domain.URLForHostAndPath(hostName, path, port)), @@ -126,7 +141,7 @@ func (cmd *CreateRoute) CreateRoute(hostName string, path string, port int, rand "SpaceName": terminal.EntityNameColor(space.Name), "Username": terminal.EntityNameColor(cmd.config.Username())})) - route, err := cmd.routeRepo.CreateInSpace(hostName, path, domain.GUID, space.GUID, port, randomPort) + route, err := cmd.routeRepo.CreateInSpace(hostName, path, domain.GUID, space.GUID, port, randomPort, option) if err != nil { var findErr error route, findErr = cmd.routeRepo.Find(hostName, domain, path, port) diff --git a/cf/commands/route/create_route_test.go b/cf/commands/route/create_route_test.go index e68a88bdc52..3a40fe6ba74 100644 --- a/cf/commands/route/create_route_test.go +++ b/cf/commands/route/create_route_test.go @@ -211,12 +211,13 @@ var _ = Describe("CreateRoute", func() { Expect(err).NotTo(HaveOccurred()) Expect(routeRepo.CreateInSpaceCallCount()).To(Equal(1)) - hostname, path, domain, space, port, randomPort := routeRepo.CreateInSpaceArgsForCall(0) + hostname, path, domain, space, port, randomPort, option := routeRepo.CreateInSpaceArgsForCall(0) Expect(hostname).To(Equal("")) Expect(path).To(Equal("")) Expect(domain).To(Equal("domain-guid")) Expect(space).To(Equal("space-guid")) Expect(port).To(Equal(0)) + Expect(option).To(BeEmpty()) Expect(randomPort).To(BeFalse()) }) @@ -230,7 +231,7 @@ var _ = Describe("CreateRoute", func() { Expect(err).NotTo(HaveOccurred()) Expect(routeRepo.CreateInSpaceCallCount()).To(Equal(1)) - _, path, _, _, _, _ := routeRepo.CreateInSpaceArgsForCall(0) + _, path, _, _, _, _, _ := routeRepo.CreateInSpaceArgsForCall(0) Expect(path).To(Equal("some-path")) }) }) @@ -245,7 +246,7 @@ var _ = Describe("CreateRoute", func() { Expect(err).NotTo(HaveOccurred()) Expect(routeRepo.CreateInSpaceCallCount()).To(Equal(1)) - _, _, _, _, _, randomPort := routeRepo.CreateInSpaceArgsForCall(0) + _, _, _, _, _, randomPort, _ := routeRepo.CreateInSpaceArgsForCall(0) Expect(randomPort).To(BeTrue()) }) }) @@ -260,7 +261,7 @@ var _ = Describe("CreateRoute", func() { Expect(err).NotTo(HaveOccurred()) Expect(routeRepo.CreateInSpaceCallCount()).To(Equal(1)) - _, _, _, _, port, _ := routeRepo.CreateInSpaceArgsForCall(0) + _, _, _, _, port, _, _ := routeRepo.CreateInSpaceArgsForCall(0) Expect(port).To(Equal(9090)) }) }) @@ -275,7 +276,7 @@ var _ = Describe("CreateRoute", func() { Expect(err).NotTo(HaveOccurred()) Expect(routeRepo.CreateInSpaceCallCount()).To(Equal(1)) - host, _, _, _, _, _ := routeRepo.CreateInSpaceArgsForCall(0) + host, _, _, _, _, _, _ := routeRepo.CreateInSpaceArgsForCall(0) Expect(host).To(Equal("host")) }) }) @@ -369,15 +370,16 @@ var _ = Describe("CreateRoute", func() { }) It("attempts to create a route in the space", func() { - rc.CreateRoute("hostname", "path", 9090, true, domainFields, spaceFields) + rc.CreateRoute("hostname", "path", 9090, true, domainFields, spaceFields, "loadbalancing=round-robin") Expect(routeRepo.CreateInSpaceCallCount()).To(Equal(1)) - hostname, path, domain, space, port, randomPort := routeRepo.CreateInSpaceArgsForCall(0) + hostname, path, domain, space, port, randomPort, option := routeRepo.CreateInSpaceArgsForCall(0) Expect(hostname).To(Equal("hostname")) Expect(path).To(Equal("path")) Expect(domain).To(Equal(domainFields.GUID)) Expect(space).To(Equal(spaceFields.GUID)) Expect(port).To(Equal(9090)) + Expect(option).To(Equal("loadbalancing=round-robin")) Expect(randomPort).To(BeTrue()) }) @@ -387,7 +389,7 @@ var _ = Describe("CreateRoute", func() { }) It("attempts to find the route", func() { - rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields) + rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields, "loadbalancing=round-robin") Expect(routeRepo.FindCallCount()).To(Equal(1)) }) @@ -397,7 +399,7 @@ var _ = Describe("CreateRoute", func() { }) It("returns the original error", func() { - _, err := rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields) + _, err := rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields, "loadbalancing=round-robin") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("create-error")) }) @@ -405,7 +407,7 @@ var _ = Describe("CreateRoute", func() { Context("when a route with the same space guid, but different domain guid is found", func() { It("returns the original error", func() { - _, err := rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields) + _, err := rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields, "loadbalancing=round-robin") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("create-error")) }) @@ -413,7 +415,7 @@ var _ = Describe("CreateRoute", func() { Context("when a route with the same domain guid, but different space guid is found", func() { It("returns the original error", func() { - _, err := rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields) + _, err := rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields, "loadbalancing=round-robin") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("create-error")) }) @@ -435,7 +437,7 @@ var _ = Describe("CreateRoute", func() { }) It("prints a message that it already exists", func() { - rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields) + rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields, "loadbalancing=round-robin") Expect(ui.Outputs()).To(ContainSubstrings( []string{"OK"}, []string{"Route hostname.domain-name/path already exists"})) @@ -451,7 +453,7 @@ var _ = Describe("CreateRoute", func() { }) It("prints a success message", func() { - rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields) + rc.CreateRoute("hostname", "path", 0, false, domainFields, spaceFields, "loadbalancing=round-robin") Expect(ui.Outputs()).To(ContainSubstrings([]string{"OK"})) }) @@ -465,7 +467,7 @@ var _ = Describe("CreateRoute", func() { }) It("print a success message with created route", func() { - rc.CreateRoute("hostname", "path", 0, true, domainFields, spaceFields) + rc.CreateRoute("hostname", "path", 0, true, domainFields, spaceFields, "loadbalancing=round-robin") Expect(ui.Outputs()).To(ContainSubstrings( []string{"OK"}, []string{"Route domain-name:9090 has been created"}, diff --git a/cf/commands/route/map_route.go b/cf/commands/route/map_route.go index 4c0c1f2a43d..a7bddd55d4c 100644 --- a/cf/commands/route/map_route.go +++ b/cf/commands/route/map_route.go @@ -33,6 +33,7 @@ func (cmd *MapRoute) MetaData() commandregistry.CommandMetadata { fs["path"] = &flags.StringFlag{Name: "path", Usage: T("Path for the HTTP route")} fs["port"] = &flags.IntFlag{Name: "port", Usage: T("Port for the TCP route")} fs["random-port"] = &flags.BoolFlag{Name: "random-port", Usage: T("Create a random port for the TCP route")} + fs["option"] = &flags.StringSliceFlag{Name: "option", ShortName: "o", Usage: T("Set the value of a per-route option")} return commandregistry.CommandMetadata{ Name: "map-route", @@ -43,18 +44,21 @@ func (cmd *MapRoute) MetaData() commandregistry.CommandMetadata { fmt.Sprintf("%s ", T("APP_NAME")), fmt.Sprintf("%s ", T("DOMAIN")), fmt.Sprintf("[--hostname %s] ", T("HOSTNAME")), - fmt.Sprintf("[--path %s]\n\n", T("PATH")), + fmt.Sprintf("[--path %s] ", T("PATH")), + fmt.Sprintf("[--option %s=%s]\n\n", T("OPTION"), T("VALUE")), fmt.Sprintf(" %s:\n", T("Map a TCP route")), " CF_NAME map-route ", fmt.Sprintf("%s ", T("APP_NAME")), fmt.Sprintf("%s ", T("DOMAIN")), - fmt.Sprintf("(--port %s | --random-port)", T("PORT")), + fmt.Sprintf("(--port %s | --random-port) ", T("PORT")), + fmt.Sprintf("[--option %s=%s]\n\n", T("OPTION"), T("VALUE")), }, Examples: []string{ "CF_NAME map-route my-app example.com # example.com", "CF_NAME map-route my-app example.com --hostname myhost # myhost.example.com", "CF_NAME map-route my-app example.com --hostname myhost --path foo # myhost.example.com/foo", "CF_NAME map-route my-app example.com --port 50000 # example.com:50000", + "CF_NAME map-route my-app example.com -o loadbalancing=round-robin", }, Flags: fs, } @@ -114,10 +118,11 @@ func (cmd *MapRoute) Execute(c flags.FlagContext) error { path := c.String("path") domain := cmd.domainReq.GetDomain() app := cmd.appReq.GetApplication() + option := c.String("o") port := c.Int("port") randomPort := c.Bool("random-port") - route, err := cmd.routeCreator.CreateRoute(hostName, path, port, randomPort, domain, cmd.config.SpaceFields()) + route, err := cmd.routeCreator.CreateRoute(hostName, path, port, randomPort, domain, cmd.config.SpaceFields(), option) if err != nil { return errors.New(T("Error resolving route:\n{{.Err}}", map[string]interface{}{"Err": err.Error()})) } diff --git a/cf/commands/route/map_route_test.go b/cf/commands/route/map_route_test.go index ed11581b32a..d7238e2a3e6 100644 --- a/cf/commands/route/map_route_test.go +++ b/cf/commands/route/map_route_test.go @@ -115,10 +115,10 @@ var _ = Describe("MapRoute", func() { It("shows the usage", func() { Expect(usage).To(ContainElement(" Map an HTTP route:")) - Expect(usage).To(ContainElement(" cf map-route APP_NAME DOMAIN [--hostname HOSTNAME] [--path PATH]")) + Expect(usage).To(ContainElement(" cf map-route APP_NAME DOMAIN [--hostname HOSTNAME] [--path PATH] [--option OPTION=VALUE]")) Expect(usage).To(ContainElement(" Map a TCP route:")) - Expect(usage).To(ContainElement(" cf map-route APP_NAME DOMAIN (--port PORT | --random-port)")) + Expect(usage).To(ContainElement(" cf map-route APP_NAME DOMAIN (--port PORT | --random-port) [--option OPTION=VALUE]")) }) }) @@ -269,12 +269,13 @@ var _ = Describe("MapRoute", func() { Expect(ok).To(BeTrue()) Expect(fakeRouteCreator.CreateRouteCallCount()).To(Equal(1)) - host, path, port, randomPort, domain, space := fakeRouteCreator.CreateRouteArgsForCall(0) + host, path, port, randomPort, domain, space, option := fakeRouteCreator.CreateRouteArgsForCall(0) Expect(host).To(Equal("")) Expect(path).To(Equal("")) Expect(port).To(Equal(0)) Expect(randomPort).To(BeFalse()) Expect(domain).To(Equal(fakeDomain)) + Expect(option).To(Equal("")) Expect(space).To(Equal(models.SpaceFields{ Name: "my-space", GUID: "my-space-guid", @@ -294,7 +295,7 @@ var _ = Describe("MapRoute", func() { Expect(err).ToNot(HaveOccurred()) Expect(fakeRouteCreator.CreateRouteCallCount()).To(Equal(1)) - _, _, port, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) + _, _, port, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) Expect(port).To(Equal(60000)) }) }) @@ -312,7 +313,7 @@ var _ = Describe("MapRoute", func() { Expect(err).ToNot(HaveOccurred()) Expect(fakeRouteCreator.CreateRouteCallCount()).To(Equal(1)) - _, _, _, randomPort, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) + _, _, _, randomPort, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) Expect(randomPort).To(BeTrue()) }) }) @@ -389,7 +390,7 @@ var _ = Describe("MapRoute", func() { fakeRouteCreator, ok := fakeCreateRouteCmd.(*routefakes.OldFakeRouteCreator) Expect(ok).To(BeTrue()) Expect(fakeRouteCreator.CreateRouteCallCount()).To(Equal(1)) - hostName, _, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) + hostName, _, _, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) Expect(hostName).To(Equal("the-hostname")) }) }) @@ -406,7 +407,7 @@ var _ = Describe("MapRoute", func() { fakeRouteCreator, ok := fakeCreateRouteCmd.(*routefakes.OldFakeRouteCreator) Expect(ok).To(BeTrue()) Expect(fakeRouteCreator.CreateRouteCallCount()).To(Equal(1)) - hostName, _, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) + hostName, _, _, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) Expect(hostName).To(Equal("")) }) }) @@ -423,7 +424,7 @@ var _ = Describe("MapRoute", func() { fakeRouteCreator, ok := fakeCreateRouteCmd.(*routefakes.OldFakeRouteCreator) Expect(ok).To(BeTrue()) Expect(fakeRouteCreator.CreateRouteCallCount()).To(Equal(1)) - _, path, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) + _, path, _, _, _, _, _ := fakeRouteCreator.CreateRouteArgsForCall(0) Expect(path).To(Equal("the-path")) }) }) diff --git a/cf/commands/route/routefakes/fake_creator.go b/cf/commands/route/routefakes/fake_creator.go index b0118b6c417..1d161e33358 100644 --- a/cf/commands/route/routefakes/fake_creator.go +++ b/cf/commands/route/routefakes/fake_creator.go @@ -9,7 +9,7 @@ import ( ) type FakeCreator struct { - CreateRouteStub func(string, string, int, bool, models.DomainFields, models.SpaceFields) (models.Route, error) + CreateRouteStub func(string, string, int, bool, models.DomainFields, models.SpaceFields, string) (models.Route, error) createRouteMutex sync.RWMutex createRouteArgsForCall []struct { arg1 string @@ -18,6 +18,7 @@ type FakeCreator struct { arg4 bool arg5 models.DomainFields arg6 models.SpaceFields + arg7 string } createRouteReturns struct { result1 models.Route @@ -31,7 +32,7 @@ type FakeCreator struct { invocationsMutex sync.RWMutex } -func (fake *FakeCreator) CreateRoute(arg1 string, arg2 string, arg3 int, arg4 bool, arg5 models.DomainFields, arg6 models.SpaceFields) (models.Route, error) { +func (fake *FakeCreator) CreateRoute(arg1 string, arg2 string, arg3 int, arg4 bool, arg5 models.DomainFields, arg6 models.SpaceFields, arg7 string) (models.Route, error) { fake.createRouteMutex.Lock() ret, specificReturn := fake.createRouteReturnsOnCall[len(fake.createRouteArgsForCall)] fake.createRouteArgsForCall = append(fake.createRouteArgsForCall, struct { @@ -41,11 +42,12 @@ func (fake *FakeCreator) CreateRoute(arg1 string, arg2 string, arg3 int, arg4 bo arg4 bool arg5 models.DomainFields arg6 models.SpaceFields - }{arg1, arg2, arg3, arg4, arg5, arg6}) - fake.recordInvocation("CreateRoute", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6}) + arg7 string + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) + fake.recordInvocation("CreateRoute", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) fake.createRouteMutex.Unlock() if fake.CreateRouteStub != nil { - return fake.CreateRouteStub(arg1, arg2, arg3, arg4, arg5, arg6) + return fake.CreateRouteStub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } if specificReturn { return ret.result1, ret.result2 @@ -60,7 +62,7 @@ func (fake *FakeCreator) CreateRouteCallCount() int { return len(fake.createRouteArgsForCall) } -func (fake *FakeCreator) CreateRouteCalls(stub func(string, string, int, bool, models.DomainFields, models.SpaceFields) (models.Route, error)) { +func (fake *FakeCreator) CreateRouteCalls(stub func(string, string, int, bool, models.DomainFields, models.SpaceFields, string) (models.Route, error)) { fake.createRouteMutex.Lock() defer fake.createRouteMutex.Unlock() fake.CreateRouteStub = stub diff --git a/cf/commands/route/routefakes/old_fake_route_creator.go b/cf/commands/route/routefakes/old_fake_route_creator.go index c95e3c09e21..2802828a713 100644 --- a/cf/commands/route/routefakes/old_fake_route_creator.go +++ b/cf/commands/route/routefakes/old_fake_route_creator.go @@ -11,7 +11,7 @@ import ( ) type OldFakeRouteCreator struct { - CreateRouteStub func(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields) (route models.Route, apiErr error) + CreateRouteStub func(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields, option string) (route models.Route, apiErr error) createRouteMutex sync.RWMutex createRouteArgsForCall []struct { hostName string @@ -20,6 +20,7 @@ type OldFakeRouteCreator struct { randomPort bool domain models.DomainFields space models.SpaceFields + option string } createRouteReturns struct { result1 models.Route @@ -27,7 +28,7 @@ type OldFakeRouteCreator struct { } } -func (fake *OldFakeRouteCreator) CreateRoute(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields) (route models.Route, apiErr error) { +func (fake *OldFakeRouteCreator) CreateRoute(hostName string, path string, port int, randomPort bool, domain models.DomainFields, space models.SpaceFields, option string) (route models.Route, apiErr error) { fake.createRouteMutex.Lock() fake.createRouteArgsForCall = append(fake.createRouteArgsForCall, struct { hostName string @@ -36,10 +37,11 @@ func (fake *OldFakeRouteCreator) CreateRoute(hostName string, path string, port randomPort bool domain models.DomainFields space models.SpaceFields - }{hostName, path, port, randomPort, domain, space}) + option string + }{hostName, path, port, randomPort, domain, space, option}) fake.createRouteMutex.Unlock() if fake.CreateRouteStub != nil { - return fake.CreateRouteStub(hostName, path, port, randomPort, domain, space) + return fake.CreateRouteStub(hostName, path, port, randomPort, domain, space, option) } else { return fake.createRouteReturns.result1, fake.createRouteReturns.result2 } @@ -51,10 +53,10 @@ func (fake *OldFakeRouteCreator) CreateRouteCallCount() int { return len(fake.createRouteArgsForCall) } -func (fake *OldFakeRouteCreator) CreateRouteArgsForCall(i int) (string, string, int, bool, models.DomainFields, models.SpaceFields) { +func (fake *OldFakeRouteCreator) CreateRouteArgsForCall(i int) (string, string, int, bool, models.DomainFields, models.SpaceFields, string) { fake.createRouteMutex.RLock() defer fake.createRouteMutex.RUnlock() - return fake.createRouteArgsForCall[i].hostName, fake.createRouteArgsForCall[i].path, fake.createRouteArgsForCall[i].port, fake.createRouteArgsForCall[i].randomPort, fake.createRouteArgsForCall[i].domain, fake.createRouteArgsForCall[i].space + return fake.createRouteArgsForCall[i].hostName, fake.createRouteArgsForCall[i].path, fake.createRouteArgsForCall[i].port, fake.createRouteArgsForCall[i].randomPort, fake.createRouteArgsForCall[i].domain, fake.createRouteArgsForCall[i].space, fake.createRouteArgsForCall[i].option } func (fake *OldFakeRouteCreator) CreateRouteReturns(result1 models.Route, result2 error) { diff --git a/cf/commands/route/routes.go b/cf/commands/route/routes.go index 1c2c1ed139c..63ae81ae400 100644 --- a/cf/commands/route/routes.go +++ b/cf/commands/route/routes.go @@ -89,7 +89,7 @@ func (cmd *ListRoutes) Execute(c flags.FlagContext) error { })) } - table := cmd.ui.Table([]string{T("space"), T("host"), T("domain"), T("port"), T("path"), T("type"), T("apps"), T("service")}) + table := cmd.ui.Table([]string{T("space"), T("host"), T("domain"), T("port"), T("path"), T("type"), T("apps"), T("service"), T("options")}) d := make(map[string]models.DomainFields) err := cmd.domainRepo.ListDomainsForOrg(cmd.config.OrganizationFields().GUID, func(domain models.DomainFields) bool { @@ -113,6 +113,11 @@ func (cmd *ListRoutes) Execute(c flags.FlagContext) error { appNames = append(appNames, app.Name) } + options := []string{} + for optionKey, optionValue := range route.Options { + options = append(options, fmt.Sprintf("%s: %s", optionKey, optionValue)) + } + var port string if route.Port != 0 { port = fmt.Sprintf("%d", route.Port) @@ -129,6 +134,7 @@ func (cmd *ListRoutes) Execute(c flags.FlagContext) error { domain.RouterGroupType, strings.Join(appNames, ","), route.ServiceInstance.Name, + strings.Join(options, ","), ) return true } diff --git a/cf/commands/route/update_route.go b/cf/commands/route/update_route.go new file mode 100644 index 00000000000..6d070ef0769 --- /dev/null +++ b/cf/commands/route/update_route.go @@ -0,0 +1,182 @@ +package route + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/cf/models" + "code.cloudfoundry.org/cli/command" + "fmt" + "strings" + + "code.cloudfoundry.org/cli/cf/api" + "code.cloudfoundry.org/cli/cf/commandregistry" + "code.cloudfoundry.org/cli/cf/configuration/coreconfig" + "code.cloudfoundry.org/cli/cf/flags" + . "code.cloudfoundry.org/cli/cf/i18n" + "code.cloudfoundry.org/cli/cf/requirements" + "code.cloudfoundry.org/cli/cf/terminal" +) + +type UpdateRoute struct { + ui terminal.UI + config coreconfig.Reader + routeRepo api.RouteRepository + domainRepo api.DomainRepository + domainReq requirements.DomainRequirement +} + +func init() { + commandregistry.Register(&UpdateRoute{}) +} + +func (cmd *UpdateRoute) MetaData() commandregistry.CommandMetadata { + fs := make(map[string]flags.FlagSet) + fs["hostname"] = &flags.StringFlag{Name: "hostname", ShortName: "n", Usage: T("Hostname for the HTTP route (required for shared domains)")} + fs["path"] = &flags.StringFlag{Name: "path", Usage: T("Path for the HTTP route")} + fs["option"] = &flags.StringSliceFlag{Name: "option", ShortName: "o", Usage: T("Set the value of a per-route option")} + fs["remove-option"] = &flags.StringSliceFlag{Name: "remove-option", ShortName: "r", Usage: T("Remove an option with the given name")} + + return commandregistry.CommandMetadata{ + Name: "update-route", + Description: T("Update an existing HTTP route"), + Usage: []string{ + fmt.Sprintf("%s:\n", T("Update an existing HTTP route")), + " CF_NAME update-route ", + fmt.Sprintf("%s ", T("DOMAIN")), + fmt.Sprintf("[--hostname %s] ", T("HOSTNAME")), + fmt.Sprintf("[--path %s] ", T("PATH")), + fmt.Sprintf("[--option %s=%s] ", T("OPTION"), T("VALUE")), + fmt.Sprintf("[--remove-option %s]\n\n", T("OPTION")), + }, + Examples: []string{ + "CF_NAME update-route example.com -o loadbalancing=round-robin", + "CF_NAME update-route example.com -o loadbalancing=least-connection", + "CF_NAME update-route example.com -r loadbalancing", + "CF_NAME update-route example.com --hostname myhost --path foo -o loadbalancing=round-robin", + }, + Flags: fs, + } +} + +func (cmd *UpdateRoute) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) { + if len(fc.Args()) != 1 { + cmd.ui.Failed(T("Incorrect Usage. Requires DOMAIN as an argument\n\n") + commandregistry.Commands.CommandUsage("update-route")) + return nil, fmt.Errorf("incorrect usage: %d arguments of %d required", len(fc.Args()), 1) + } + + domainName := fc.Args()[0] + cmd.domainReq = requirementsFactory.NewDomainRequirement(domainName) + + var reqs []requirements.Requirement + + reqs = append(reqs, []requirements.Requirement{ + requirementsFactory.NewLoginRequirement(), + cmd.domainReq, + }...) + + return reqs, nil +} + +func (cmd *UpdateRoute) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command { + cmd.ui = deps.UI + cmd.config = deps.Config + cmd.routeRepo = deps.RepoLocator.GetRouteRepository() + cmd.domainRepo = deps.RepoLocator.GetDomainRepository() + + return cmd +} + +func (cmd *UpdateRoute) Execute(c flags.FlagContext) error { + domain := c.Args()[0] + rawHostNameFromFlag := c.String("n") + host := strings.ToLower(rawHostNameFromFlag) + path := c.String("path") + domainFields := cmd.domainReq.GetDomain() + port := 0 + + url := (&models.RoutePresenter{ + Host: host, + Domain: domain, + Path: path, + Port: port, + }).URL() + + route, err := cmd.routeRepo.Find(host, domainFields, path, 0) + if err != nil { + cmd.ui.Failed(T("Route with domain '{{.URL}}' does not exist.", + map[string]interface{}{"URL": url})) + return err + } + + if c.IsSet("o") || c.IsSet("r") { + err := cmd.validateAPIVersionForPerRouteOptions() + if err != nil { + return err + } + } + + if c.IsSet("o") { + cmd.setPerRouteOptions(c, route, url) + } + + if c.IsSet("r") { + cmd.removePerRouteOptions(c, route, url) + } + + cmd.ui.Ok() + return nil +} + +func (cmd *UpdateRoute) validateAPIVersionForPerRouteOptions() error { + err := command.MinimumCCAPIVersionCheck(cmd.config.APIVersion(), ccversion.MinVersionPerRouteOpts) + if err != nil { + cmd.ui.Say(T("Your CC API version ({{.APIVersion}}) does not support per route options."+ + "Upgrade to a newer version of the API (minimum version {{.MinSupportedVersion}}). ", map[string]interface{}{ + "APIVersion": cmd.config.APIVersion(), + "MinSupportedVersion": ccversion.MinVersionPerRouteOpts, + })) + } + return err +} + +func (cmd *UpdateRoute) setPerRouteOptions(c flags.FlagContext, route models.Route, url string) { + if c.IsSet("o") { + options := c.StringSlice("o") + for _, option := range options { + key, value, found := strings.Cut(option, "=") + if found { + cmd.ui.Say(T("Setting route option {{.Option}} for {{.URL}} to {{.Value}}...", map[string]interface{}{ + "Option": terminal.EntityNameColor(key), + "Value": terminal.EntityNameColor(value), + "URL": terminal.EntityNameColor(url)})) + if route.Options == nil { + route.Options = make(map[string]string) + } + route.Options[key] = value + } else { + cmd.ui.Say(T("Wrong route option format {{.Option}} for {{.URL}}", map[string]interface{}{ + "Option": terminal.FailureColor(option), + "URL": terminal.EntityNameColor(url)})) + } + } + + } +} + +func (cmd *UpdateRoute) removePerRouteOptions(c flags.FlagContext, route models.Route, url string) { + if c.IsSet("r") { + removeOptions := c.StringSlice("r") + for _, removeOption := range removeOptions { + if _, ok := route.Options[removeOption]; ok { + cmd.ui.Say(T("Removing route option {{.Option}} for {{.URL}}...", map[string]interface{}{ + "Option": terminal.EntityNameColor(removeOption), + "URL": terminal.EntityNameColor(url)})) + + delete(route.Options, removeOption) + } else { + cmd.ui.Say(T("No route option {{.Option}} for {{.URL}}", map[string]interface{}{ + "Option": terminal.EntityNameColor(removeOption), + "URL": terminal.EntityNameColor(url)})) + } + } + } +} diff --git a/cf/commands/route/update_route_test.go b/cf/commands/route/update_route_test.go new file mode 100644 index 00000000000..cdee584129d --- /dev/null +++ b/cf/commands/route/update_route_test.go @@ -0,0 +1,317 @@ +package route_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/cf/commandregistry" + "code.cloudfoundry.org/cli/cf/commands/route" + "code.cloudfoundry.org/cli/cf/configuration/coreconfig" + "code.cloudfoundry.org/cli/cf/flags" + "code.cloudfoundry.org/cli/cf/models" + "code.cloudfoundry.org/cli/cf/requirements" + "code.cloudfoundry.org/cli/cf/requirements/requirementsfakes" + + "code.cloudfoundry.org/cli/cf/api/apifakes" + + testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration" + testterm "code.cloudfoundry.org/cli/cf/util/testhelpers/terminal" + + "strings" + + . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("UpdateRoute", func() { + var ( + ui *testterm.FakeUI + configRepo coreconfig.Repository + routeRepo *apifakes.FakeRouteRepository + + cmd commandregistry.Command + deps commandregistry.Dependency + factory *requirementsfakes.FakeFactory + flagContext flags.FlagContext + + loginRequirement requirements.Requirement + domainRequirement *requirementsfakes.FakeDomainRequirement + + fakeDomain models.DomainFields + ) + + BeforeEach(func() { + ui = &testterm.FakeUI{} + configRepo = testconfig.NewRepositoryWithDefaults() + routeRepo = new(apifakes.FakeRouteRepository) + repoLocator := deps.RepoLocator.SetRouteRepository(routeRepo) + + deps = commandregistry.Dependency{ + UI: ui, + Config: configRepo, + RepoLocator: repoLocator, + } + + cmd = &route.UpdateRoute{} + cmd.SetDependency(deps, false) + + flagContext = flags.NewFlagContext(cmd.MetaData().Flags) + + factory = new(requirementsfakes.FakeFactory) + + loginRequirement = &passingRequirement{Name: "login-requirement"} + factory.NewLoginRequirementReturns(loginRequirement) + + domainRequirement = new(requirementsfakes.FakeDomainRequirement) + factory.NewDomainRequirementReturns(domainRequirement) + + fakeDomain = models.DomainFields{ + GUID: "fake-domain-guid", + Name: "fake-domain-name", + } + domainRequirement.GetDomainReturns(fakeDomain) + }) + + AfterEach(func() { + + }) + + Describe("Help text", func() { + var usage []string + + BeforeEach(func() { + cmd := &route.UpdateRoute{} + up := commandregistry.CLICommandUsagePresenter(cmd) + + usage = strings.Split(up.Usage(), "\n") + }) + + It("contains an example", func() { + Expect(usage).To(ContainElement(" cf update-route example.com -o loadbalancing=round-robin")) + }) + + It("contains the options", func() { + Expect(usage).To(ContainElement(" --hostname, -n Hostname for the HTTP route (required for shared domains)")) + Expect(usage).To(ContainElement(" --path Path for the HTTP route")) + Expect(usage).To(ContainElement(" --option, -o Set the value of a per-route option")) + Expect(usage).To(ContainElement(" --remove-option, -r Remove an option with the given name")) + }) + + It("shows the usage", func() { + Expect(usage).To(ContainElement(" Update an existing HTTP route:")) + Expect(usage).To(ContainElement(" cf update-route DOMAIN [--hostname HOSTNAME] [--path PATH] [--option OPTION=VALUE] [--remove-option OPTION]")) + }) + }) + + Describe("Requirements", func() { + Context("when not provided exactly one arg", func() { + BeforeEach(func() { + }) + + It("fails with usage", func() { + _, err := cmd.Requirements(factory, flagContext) + Expect(err).To(HaveOccurred()) + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Incorrect Usage. Requires DOMAIN as an argument"}, + []string{"NAME"}, + []string{"USAGE"}, + )) + }) + }) + + Context("when provided exactly one arg", func() { + BeforeEach(func() { + flagContext.Parse("domain-name") + }) + + It("returns a LoginRequirement", func() { + actualRequirements, err := cmd.Requirements(factory, flagContext) + Expect(err).NotTo(HaveOccurred()) + Expect(factory.NewLoginRequirementCallCount()).To(Equal(1)) + + Expect(actualRequirements).To(ContainElement(loginRequirement)) + }) + + It("returns a DomainRequirement", func() { + actualRequirements, err := cmd.Requirements(factory, flagContext) + Expect(err).NotTo(HaveOccurred()) + Expect(factory.NewDomainRequirementCallCount()).To(Equal(1)) + + Expect(factory.NewDomainRequirementArgsForCall(0)).To(Equal("domain-name")) + Expect(actualRequirements).To(ContainElement(domainRequirement)) + }) + }) + }) + + Describe("Execute", func() { + var err error + + BeforeEach(func() { + err := flagContext.Parse("domain-name") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + JustBeforeEach(func() { + err = cmd.Execute(flagContext) + }) + + It("tries to find the route", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(routeRepo.FindCallCount()).To(Equal(1)) + hostname, domain, path, port := routeRepo.FindArgsForCall(0) + Expect(hostname).To(Equal("")) + Expect(domain).To(Equal(fakeDomain)) + Expect(path).To(Equal("")) + Expect(port).To(Equal(0)) + }) + + Context("when a hostname and a path are passed", func() { + BeforeEach(func() { + err := flagContext.Parse("domain-name", "--hostname", "the-hostname", "--path", "the-path") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("tries to find the route with the hostname and path", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(routeRepo.FindCallCount()).To(Equal(1)) + hostname, _, path, _ := routeRepo.FindArgsForCall(0) + Expect(hostname).To(Equal("the-hostname")) + Expect(path).To(Equal("the-path")) + }) + }) + + Context("when the route can be found and an option is passed", func() { + BeforeEach(func() { + routeRepo.FindReturns(models.Route{GUID: "route-guid"}, nil) + err := flagContext.Parse("domain-name", "--option", "loadbalancing=round-robin") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("tries to set the given route option", func() { + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Setting route option loadbalancing", "for", "to round-robin"}, + )) + }) + }) + + Context("when the route can be found and an option is passed, which already exists", func() { + BeforeEach(func() { + routeRepo.FindReturns(models.Route{GUID: "route-guid", + Options: map[string]string{"loadbalancing": "round-robin", "a": "b"}}, nil) + err := flagContext.Parse("domain-name", "--option", "loadbalancing=least-connection") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("tries to set the given route option", func() { + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Setting route option loadbalancing", "for", "to least-connection"}, + )) + }) + }) + + Context("when the route can be found a remove option is passed", func() { + BeforeEach(func() { + routeRepo.FindReturns( + models.Route{GUID: "route-guid", + Options: map[string]string{"loadbalancing": "round-robin", "a": "b"}}, + nil, + ) + err := flagContext.Parse("domain-name", "--option", "a=b", "--remove-option", "loadbalancing") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("tries to remove the given route option if it exists and gives an error message", func() { + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Removing route option loadbalancing", "for"}, + )) + }) + + }) + + Context("when the route can be found a remove option for non existent option is passed", func() { + BeforeEach(func() { + routeRepo.FindReturns( + models.Route{GUID: "route-guid", + Options: map[string]string{"a": "b"}}, + nil, + ) + err := flagContext.Parse("domain-name", "--remove-option", "loadbalancing") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("gives an error message that the given route option does not exist", func() { + routeRepo.FindReturns(models.Route{GUID: "route-guid"}, nil) + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"No route option loadbalancing", "for"}, + )) + }) + }) + + Context("when the route can be found and multiple options are passed", func() { + BeforeEach(func() { + routeRepo.FindReturns( + models.Route{GUID: "route-guid", + Options: map[string]string{"a": "b", "c": "d"}}, + nil, + ) + err := flagContext.Parse("domain-name", "--option", "a=b", "--option", "c=d") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("tries to set the given route options", func() { + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Setting route option a for domain-name to b"}, + []string{"Setting route option c for domain-name to d"}, + )) + }) + }) + + Context("when the route can be found and multiple remove options are passed", func() { + BeforeEach(func() { + routeRepo.FindReturns( + models.Route{GUID: "route-guid", + Options: map[string]string{"a": "b", "c": "d"}}, + nil, + ) + err := flagContext.Parse("domain-name", "--remove-option", "a", "--remove-option", "c") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + }) + + It("tries to set the given route options", func() { + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Removing route option a"}, + []string{"Removing route option c"}, + )) + }) + }) + + Context("when the route cannot be found", func() { + BeforeEach(func() { + err := flagContext.Parse("domain-name") + Expect(err).NotTo(HaveOccurred()) + cmd.Requirements(factory, flagContext) + routeRepo.FindReturns(models.Route{}, errors.New("find-by-host-and-domain-err")) + }) + + It("returns an error", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("find-by-host-and-domain-err")) + }) + + It("tells the user a route with the given domain does not exist", func() { + Expect(err).To(HaveOccurred()) + Expect(ui.Outputs()).To(ContainSubstrings( + []string{"Route with domain", "does not exist"}, + )) + }) + }) + }) +}) diff --git a/cf/help/help.go b/cf/help/help.go index bb992977d66..fc8d15815fa 100644 --- a/cf/help/help.go +++ b/cf/help/help.go @@ -238,6 +238,7 @@ func newAppPresenter() appPresenter { { presentCommand("routes"), presentCommand("create-route"), + presentCommand("update-route"), presentCommand("check-route"), presentCommand("map-route"), presentCommand("unmap-route"), diff --git a/cf/models/route.go b/cf/models/route.go index f1dcb1ac759..792c16554ab 100644 --- a/cf/models/route.go +++ b/cf/models/route.go @@ -7,12 +7,12 @@ import ( ) type Route struct { - GUID string - Host string - Domain DomainFields - Path string - Port int - + GUID string + Host string + Domain DomainFields + Path string + Port int + Options map[string]string Space SpaceFields Apps []ApplicationFields ServiceInstance ServiceInstanceFields @@ -20,18 +20,20 @@ type Route struct { func (r Route) URL() string { return (&RoutePresenter{ - Host: r.Host, - Domain: r.Domain.Name, - Path: r.Path, - Port: r.Port, + Host: r.Host, + Domain: r.Domain.Name, + Path: r.Path, + Port: r.Port, + Options: r.Options, }).URL() } type RoutePresenter struct { - Host string - Domain string - Path string - Port int + Host string + Domain string + Path string + Port int + Options map[string]string } func (r *RoutePresenter) URL() string { diff --git a/cf/models/route_summary.go b/cf/models/route_summary.go index 20e7b9d7164..b40a0b7916f 100644 --- a/cf/models/route_summary.go +++ b/cf/models/route_summary.go @@ -1,18 +1,20 @@ package models type RouteSummary struct { - GUID string - Host string - Domain DomainFields - Path string - Port int + GUID string + Host string + Domain DomainFields + Path string + Port int + Options map[string]string } func (r RouteSummary) URL() string { return (&RoutePresenter{ - Host: r.Host, - Domain: r.Domain.Name, - Path: r.Path, - Port: r.Port, + Host: r.Host, + Domain: r.Domain.Name, + Path: r.Path, + Port: r.Port, + Options: r.Options, }).URL() } diff --git a/cf/util/testhelpers/configuration/test_config.go b/cf/util/testhelpers/configuration/test_config.go index 0c7f0341bc6..db9e2d60bcc 100644 --- a/cf/util/testhelpers/configuration/test_config.go +++ b/cf/util/testhelpers/configuration/test_config.go @@ -1,6 +1,7 @@ package configuration import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/cf/configuration/coreconfig" "code.cloudfoundry.org/cli/cf/models" ) @@ -38,5 +39,7 @@ func NewRepositoryWithDefaults() coreconfig.Repository { GUID: "my-org-guid", }) + configRepo.SetAPIVersion(ccversion.MinVersionPerRouteOpts) + return configRepo } diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 9de9243d3fc..5de55da5289 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -66,7 +66,7 @@ var HelpCategoryList = []HelpCategory{ CategoryName: "ROUTES:", CommandList: [][]string{ {"routes", "route"}, - {"create-route", "check-route", "map-route", "unmap-route", "delete-route"}, + {"create-route", "update-route", "check-route", "map-route", "unmap-route", "delete-route"}, {"delete-orphaned-routes"}, {"update-destination"}, {"share-route", "unshare-route"}, diff --git a/integration/helpers/name_generator.go b/integration/helpers/name_generator.go index c6fcfeed525..c35e10ea83e 100644 --- a/integration/helpers/name_generator.go +++ b/integration/helpers/name_generator.go @@ -67,6 +67,12 @@ func NewOrgName() string { return PrefixedRandomName("INTEGRATION-ORG") } +func NewOptions() map[string]string { + return map[string]string{ + "loadbalancing": "round-robin", + } +} + // NewServiceOfferingName provides a random name prefixed with INTEGRATION-SERVICE func NewServiceOfferingName() string { return PrefixedRandomName("INTEGRATION-SERVICE") diff --git a/integration/helpers/route.go b/integration/helpers/route.go index d4cad8196ae..d4598176214 100644 --- a/integration/helpers/route.go +++ b/integration/helpers/route.go @@ -41,47 +41,51 @@ func FindOrCreateTCPRouterGroup(node int) string { // Route represents a route. type Route struct { - Domain string - Host string - Path string - Port int - Space string + Domain string + Host string + Path string + Port int + Space string + Options map[string]string } // NewRoute constructs a route with given space, domain, hostname, and path. -func NewRoute(space string, domain string, hostname string, path string) Route { +func NewRoute(space string, domain string, hostname string, path string, options map[string]string) Route { return Route{ - Space: space, - Domain: domain, - Host: hostname, - Path: path, + Space: space, + Domain: domain, + Host: hostname, + Path: path, + Options: options, } } // NewTCPRoute constructs a TCP route with given space, domain, and port. -func NewTCPRoute(space string, domain string, port int) Route { +func NewTCPRoute(space string, domain string, port int, options map[string]string) Route { return Route{ - Space: space, - Domain: domain, - Port: port, + Space: space, + Domain: domain, + Port: port, + Options: options, } } // Create creates a route using the 'cf create-route' command. func (r Route) Create() { + r.Options = make(map[string]string) if r.Port != 0 { - Eventually(CF("create-route", r.Space, r.Domain, "--port", fmt.Sprint(r.Port))).Should(Exit(0)) + Eventually(CF("create-route", r.Space, r.Domain, "--port", fmt.Sprint(r.Port), "--option", fmt.Sprint(r.Options))).Should(Exit(0)) } else { - Eventually(CF("create-route", r.Space, r.Domain, "--hostname", r.Host, "--path", r.Path)).Should(Exit(0)) + Eventually(CF("create-route", r.Space, r.Domain, "--hostname", r.Host, "--path", r.Path, "--option", fmt.Sprint(r.Options))).Should(Exit(0)) } } // Create creates a route using the 'cf create-route' command. func (r Route) V7Create() { if r.Port != 0 { - Eventually(CF("create-route", r.Domain, "--port", fmt.Sprint(r.Port))).Should(Exit(0)) + Eventually(CF("create-route", r.Domain, "--port", fmt.Sprint(r.Port), "--option", fmt.Sprint(r.Options))).Should(Exit(0)) } else { - Eventually(CF("create-route", r.Domain, "--hostname", r.Host, "--path", r.Path)).Should(Exit(0)) + Eventually(CF("create-route", r.Domain, "--hostname", r.Host, "--path", r.Path, "--option", fmt.Sprint(r.Options))).Should(Exit(0)) } } diff --git a/integration/v7/isolated/map_route_command_test.go b/integration/v7/isolated/map_route_command_test.go index a22b82c0c35..581bc6a1afb 100644 --- a/integration/v7/isolated/map_route_command_test.go +++ b/integration/v7/isolated/map_route_command_test.go @@ -72,12 +72,14 @@ var _ = Describe("map-route command", func() { path string userName string appName string + options map[string]string ) BeforeEach(func() { appName = helpers.NewAppName() hostName = helpers.NewHostName() path = helpers.NewPath() + options = helpers.NewOptions() orgName = helpers.NewOrgName() spaceName = helpers.NewSpaceName() helpers.SetupCF(orgName, spaceName) @@ -96,7 +98,7 @@ var _ = Describe("map-route command", func() { BeforeEach(func() { domainName = helpers.DefaultSharedDomain() - route = helpers.NewRoute(spaceName, domainName, hostName, path) + route = helpers.NewRoute(spaceName, domainName, hostName, path, options) route.V7Create() }) @@ -160,7 +162,7 @@ var _ = Describe("map-route command", func() { routerGroup.Create() domain.CreateWithRouterGroup(routerGroup.Name) - route = helpers.NewTCPRoute(spaceName, domainName, 1082) + route = helpers.NewTCPRoute(spaceName, domainName, 1082, options) }) AfterEach(func() { diff --git a/integration/v7/isolated/unmap_route_command_test.go b/integration/v7/isolated/unmap_route_command_test.go index 7143031a98b..d249b1b4cd9 100644 --- a/integration/v7/isolated/unmap_route_command_test.go +++ b/integration/v7/isolated/unmap_route_command_test.go @@ -71,6 +71,7 @@ var _ = Describe("unmap-route command", func() { tcpRoute helpers.Route port int tcpDomain helpers.Domain + options map[string]string ) BeforeEach(func() { @@ -82,12 +83,13 @@ var _ = Describe("unmap-route command", func() { helpers.SetupCF(orgName, spaceName) userName, _ = helpers.GetCredentials() domainName = helpers.DefaultSharedDomain() + options = helpers.NewOptions() routerGroupName := helpers.FindOrCreateTCPRouterGroup(4) tcpDomain = helpers.NewDomain(orgName, helpers.NewDomainName("TCP-DOMAIN")) tcpDomain.CreateWithRouterGroup(routerGroupName) - route = helpers.NewRoute(spaceName, domainName, hostName, path) + route = helpers.NewRoute(spaceName, domainName, hostName, path, options) route.V7Create() helpers.WithHelloWorldApp(func(dir string) { @@ -118,7 +120,7 @@ var _ = Describe("unmap-route command", func() { When("it's a TCP route", func() { BeforeEach(func() { port = helpers.GetPort() - tcpRoute = helpers.NewTCPRoute(spaceName, tcpDomain.Name, port) + tcpRoute = helpers.NewTCPRoute(spaceName, tcpDomain.Name, port, options) session := helpers.CF("map-route", appName, tcpDomain.Name, "--port", fmt.Sprintf("%d", tcpRoute.Port)) Eventually(session).Should(Exit(0)) }) diff --git a/strings/excluded.json b/strings/excluded.json index 963a2a9d922..c779b2482c4 100644 --- a/strings/excluded.json +++ b/strings/excluded.json @@ -308,6 +308,7 @@ "color", "delete-domain", "create-route", + "update-route", "plugins", "oauth-token", "Service key",