github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/actor/v2action/route.go (about) 1 package v2action 2 3 import ( 4 "fmt" 5 "path" 6 "strings" 7 8 "code.cloudfoundry.org/cli/actor/actionerror" 9 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/constant" 12 "code.cloudfoundry.org/cli/types" 13 log "github.com/sirupsen/logrus" 14 ) 15 16 type Routes []Route 17 18 // Summary converts routes into a comma separated string. 19 func (rs Routes) Summary() string { 20 formattedRoutes := []string{} 21 for _, route := range rs { 22 formattedRoutes = append(formattedRoutes, route.String()) 23 } 24 return strings.Join(formattedRoutes, ", ") 25 } 26 27 // Route represents a CLI Route. 28 type Route struct { 29 Domain Domain 30 GUID string 31 Host string 32 Path string 33 Port types.NullInt 34 SpaceGUID string 35 } 36 37 func (r Route) RandomTCPPort() bool { 38 return r.Domain.IsTCP() && !r.Port.IsSet 39 } 40 41 // Validate will return an error if there are invalid HTTP or TCP settings for 42 // it's given domain. 43 func (r Route) Validate() error { 44 if r.Domain.IsHTTP() { 45 if r.Port.IsSet { 46 return actionerror.InvalidHTTPRouteSettings{Domain: r.Domain.Name} 47 } 48 if r.Domain.IsShared() && r.Host == "" { 49 return actionerror.NoHostnameAndSharedDomainError{} 50 } 51 } else { // Is TCP Domain 52 if r.Host != "" || r.Path != "" { 53 return actionerror.InvalidTCPRouteSettings{Domain: r.Domain.Name} 54 } 55 } 56 return nil 57 } 58 59 // TODO: rename to ValidateWithPortOptions 60 func (r Route) ValidateWithRandomPort(randomPort bool) error { 61 if r.Domain.IsHTTP() && randomPort { 62 return actionerror.InvalidHTTPRouteSettings{Domain: r.Domain.Name} 63 } 64 65 if r.Domain.IsTCP() && !r.Port.IsSet && !randomPort { 66 return actionerror.TCPRouteOptionsNotProvidedError{} 67 } 68 return r.Validate() 69 } 70 71 // String formats the route in a human readable format. 72 func (r Route) String() string { 73 routeString := r.Domain.Name 74 75 if r.Port.IsSet { 76 routeString = fmt.Sprintf("%s:%d", routeString, r.Port.Value) 77 } else if r.RandomTCPPort() { 78 routeString = fmt.Sprintf("%s:????", routeString) 79 } 80 81 if r.Host != "" { 82 routeString = fmt.Sprintf("%s.%s", r.Host, routeString) 83 } 84 85 if r.Path != "" { 86 routeString = path.Join(routeString, r.Path) 87 } 88 89 return routeString 90 } 91 92 func (actor Actor) MapRouteToApplication(routeGUID string, appGUID string) (Warnings, error) { 93 _, warnings, err := actor.CloudControllerClient.UpdateRouteApplication(routeGUID, appGUID) 94 if _, ok := err.(ccerror.InvalidRelationError); ok { 95 return Warnings(warnings), actionerror.RouteInDifferentSpaceError{} 96 } 97 return Warnings(warnings), err 98 } 99 100 func (actor Actor) UnmapRouteFromApplication(routeGUID string, appGUID string) (Warnings, error) { 101 warnings, err := actor.CloudControllerClient.DeleteRouteApplication(routeGUID, appGUID) 102 return Warnings(warnings), err 103 } 104 105 func (actor Actor) CreateRoute(route Route, generatePort bool) (Route, Warnings, error) { 106 if route.Path != "" && !strings.HasPrefix(route.Path, "/") { 107 route.Path = fmt.Sprintf("/%s", route.Path) 108 } 109 returnedRoute, warnings, err := actor.CloudControllerClient.CreateRoute(ActorToCCRoute(route), generatePort) 110 return CCToActorRoute(returnedRoute, route.Domain), Warnings(warnings), err 111 } 112 113 func (actor Actor) CreateRouteWithExistenceCheck(orgGUID string, spaceName string, route Route, generatePort bool) (Route, Warnings, error) { 114 space, warnings, spaceErr := actor.GetSpaceByOrganizationAndName(orgGUID, spaceName) 115 if spaceErr != nil { 116 return Route{}, Warnings(warnings), spaceErr 117 } 118 route.SpaceGUID = space.GUID 119 120 if route.Domain.GUID == "" { 121 domains, orgDomainWarnings, getDomainErr := actor.GetDomainsByNameAndOrganization([]string{route.Domain.Name}, orgGUID) 122 warnings = append(warnings, orgDomainWarnings...) 123 if getDomainErr != nil { 124 return Route{}, warnings, getDomainErr 125 } else if len(domains) == 0 { 126 return Route{}, warnings, actionerror.DomainNotFoundError{Name: route.Domain.Name} 127 } 128 route.Domain.GUID = domains[0].GUID 129 route.Domain.RouterGroupType = domains[0].RouterGroupType 130 } 131 132 validateErr := route.ValidateWithRandomPort(generatePort) 133 if validateErr != nil { 134 return Route{}, Warnings(warnings), validateErr 135 } 136 137 if !generatePort { 138 foundRoute, spaceRouteWarnings, findErr := actor.FindRouteBoundToSpaceWithSettings(route) 139 warnings = append(warnings, spaceRouteWarnings...) 140 routeAlreadyExists := true 141 if _, ok := findErr.(actionerror.RouteNotFoundError); ok { 142 routeAlreadyExists = false 143 } else if findErr != nil { 144 return Route{}, Warnings(warnings), findErr 145 } 146 147 if routeAlreadyExists { 148 return Route{}, Warnings(warnings), actionerror.RouteAlreadyExistsError{Route: foundRoute.String()} 149 } 150 } 151 152 createdRoute, createRouteWarnings, createErr := actor.CreateRoute(route, generatePort) 153 warnings = append(warnings, createRouteWarnings...) 154 if createErr != nil { 155 return Route{}, Warnings(warnings), createErr 156 } 157 158 return createdRoute, Warnings(warnings), nil 159 } 160 161 // GetOrphanedRoutesBySpace returns a list of orphaned routes associated with 162 // the provided Space GUID. 163 func (actor Actor) GetOrphanedRoutesBySpace(spaceGUID string) ([]Route, Warnings, error) { 164 var ( 165 orphanedRoutes []Route 166 allWarnings Warnings 167 ) 168 169 routes, warnings, err := actor.GetSpaceRoutes(spaceGUID) 170 allWarnings = append(allWarnings, warnings...) 171 if err != nil { 172 return nil, allWarnings, err 173 } 174 175 for _, route := range routes { 176 apps, warnings, err := actor.GetRouteApplications(route.GUID) 177 allWarnings = append(allWarnings, warnings...) 178 if err != nil { 179 return nil, allWarnings, err 180 } 181 182 if len(apps) == 0 { 183 orphanedRoutes = append(orphanedRoutes, route) 184 } 185 } 186 187 if len(orphanedRoutes) == 0 { 188 return nil, allWarnings, actionerror.OrphanedRoutesNotFoundError{} 189 } 190 191 return orphanedRoutes, allWarnings, nil 192 } 193 194 // GetApplicationRoutes returns a list of routes associated with the provided 195 // Application GUID. 196 func (actor Actor) GetApplicationRoutes(applicationGUID string) (Routes, Warnings, error) { 197 var allWarnings Warnings 198 ccv2Routes, warnings, err := actor.CloudControllerClient.GetApplicationRoutes(applicationGUID) 199 allWarnings = append(allWarnings, warnings...) 200 if err != nil { 201 return nil, allWarnings, err 202 } 203 204 routes, domainWarnings, err := actor.applyDomain(ccv2Routes) 205 206 return routes, append(allWarnings, domainWarnings...), err 207 } 208 209 // GetSpaceRoutes returns a list of routes associated with the provided Space 210 // GUID. 211 func (actor Actor) GetSpaceRoutes(spaceGUID string) ([]Route, Warnings, error) { 212 var allWarnings Warnings 213 ccv2Routes, warnings, err := actor.CloudControllerClient.GetSpaceRoutes(spaceGUID) 214 allWarnings = append(allWarnings, warnings...) 215 if err != nil { 216 return nil, allWarnings, err 217 } 218 219 routes, domainWarnings, err := actor.applyDomain(ccv2Routes) 220 221 return routes, append(allWarnings, domainWarnings...), err 222 } 223 224 // DeleteUnmappedRoutes deletes the unmapped routes associated with the provided Space GUID. 225 func (actor Actor) DeleteUnmappedRoutes(spaceGUID string) (Warnings, error) { 226 warnings, err := actor.CloudControllerClient.DeleteSpaceUnmappedRoutes(spaceGUID) 227 return Warnings(warnings), err 228 } 229 230 // DeleteRoute deletes the Route associated with the provided Route GUID. 231 func (actor Actor) DeleteRoute(routeGUID string) (Warnings, error) { 232 warnings, err := actor.CloudControllerClient.DeleteRoute(routeGUID) 233 return Warnings(warnings), err 234 } 235 236 func (actor Actor) CheckRoute(route Route) (bool, Warnings, error) { 237 exists, warnings, err := actor.CloudControllerClient.CheckRoute(ActorToCCRoute(route)) 238 return exists, Warnings(warnings), err 239 } 240 241 // FindRouteBoundToSpaceWithSettings finds the route with the given host, 242 // domain and space. If it is unable to find the route, it will check if it 243 // exists anywhere in the system. When the route exists in another space, 244 // RouteInDifferentSpaceError is returned. Use this when you know the space 245 // GUID. 246 func (actor Actor) FindRouteBoundToSpaceWithSettings(route Route) (Route, Warnings, error) { 247 existingRoute, warnings, err := actor.GetRouteByComponents(route) 248 if routeNotFoundErr, ok := err.(actionerror.RouteNotFoundError); ok { 249 // This check only works for API versions 2.55 or higher. It will return 250 // false for anything below that. 251 log.Infof("checking route existence for: %s\n", route) 252 exists, checkRouteWarnings, chkErr := actor.CheckRoute(route) 253 if chkErr != nil { 254 log.Errorln("check route:", err) 255 return Route{}, append(Warnings(warnings), checkRouteWarnings...), chkErr 256 } 257 258 // This will happen if the route exists in a space to which the user does 259 // not have access. 260 if exists { 261 log.Errorf("unable to find route %s in current space", route.String()) 262 return Route{}, append(Warnings(warnings), checkRouteWarnings...), actionerror.RouteInDifferentSpaceError{Route: route.String()} 263 } 264 265 log.Warnf("negative existence check for route %s - returning partial route", route.String()) 266 log.Debugf("partialRoute: %#v", route) 267 return Route{}, append(Warnings(warnings), checkRouteWarnings...), routeNotFoundErr 268 } else if err != nil { 269 log.Errorln("finding route:", err) 270 return Route{}, Warnings(warnings), err 271 } 272 273 if existingRoute.SpaceGUID != route.SpaceGUID { 274 log.WithFields(log.Fields{ 275 "targeted_space_guid": route.SpaceGUID, 276 "existing_space_guid": existingRoute.SpaceGUID, 277 }).Errorf("route exists in different space the user has access to") 278 return Route{}, Warnings(warnings), actionerror.RouteInDifferentSpaceError{Route: route.String()} 279 } 280 281 log.Debugf("found route: %#v", existingRoute) 282 return existingRoute, Warnings(warnings), err 283 } 284 285 // GetRouteByComponents returns the route with the matching host, domain, path, 286 // and port. Use this when you don't know the space GUID. 287 // TCP routes require a port to be uniquely identified 288 // HTTP routes using shared domains require a hostname or path to be uniquely identified 289 func (actor Actor) GetRouteByComponents(route Route) (Route, Warnings, error) { 290 // TODO: validation should probably be done separately (?) 291 if route.Domain.IsTCP() && !route.Port.IsSet { 292 return Route{}, nil, actionerror.PortNotProvidedForQueryError{} 293 } 294 295 if route.Domain.IsShared() && route.Domain.IsHTTP() && route.Host == "" { 296 return Route{}, nil, actionerror.NoHostnameAndSharedDomainError{} 297 } 298 299 queries := []ccv2.Filter{ 300 { 301 Type: constant.DomainGUIDFilter, 302 Operator: constant.EqualOperator, 303 Values: []string{route.Domain.GUID}, 304 }, { 305 Type: constant.HostFilter, 306 Operator: constant.EqualOperator, 307 Values: []string{route.Host}, 308 }, { 309 Type: constant.PathFilter, 310 Operator: constant.EqualOperator, 311 Values: []string{route.Path}, 312 }, 313 } 314 315 if route.Port.IsSet { 316 queries = append(queries, ccv2.Filter{ 317 Type: constant.PortFilter, 318 Operator: constant.EqualOperator, 319 Values: []string{fmt.Sprint(route.Port.Value)}, 320 }) 321 } 322 323 ccv2Routes, warnings, err := actor.CloudControllerClient.GetRoutes(queries...) 324 if err != nil { 325 return Route{}, Warnings(warnings), err 326 } 327 328 if len(ccv2Routes) == 0 { 329 return Route{}, Warnings(warnings), actionerror.RouteNotFoundError{ 330 Host: route.Host, 331 DomainGUID: route.Domain.GUID, 332 Path: route.Path, 333 Port: route.Port.Value, 334 } 335 } 336 337 return CCToActorRoute(ccv2Routes[0], route.Domain), Warnings(warnings), err 338 } 339 340 func ActorToCCRoute(route Route) ccv2.Route { 341 return ccv2.Route{ 342 DomainGUID: route.Domain.GUID, 343 GUID: route.GUID, 344 Host: route.Host, 345 Path: route.Path, 346 Port: route.Port, 347 SpaceGUID: route.SpaceGUID, 348 } 349 } 350 351 func CCToActorRoute(ccv2Route ccv2.Route, domain Domain) Route { 352 return Route{ 353 Domain: domain, 354 GUID: ccv2Route.GUID, 355 Host: ccv2Route.Host, 356 Path: ccv2Route.Path, 357 Port: ccv2Route.Port, 358 SpaceGUID: ccv2Route.SpaceGUID, 359 } 360 } 361 362 func (actor Actor) applyDomain(ccv2Routes []ccv2.Route) (Routes, Warnings, error) { 363 var routes Routes 364 var allWarnings Warnings 365 366 for _, ccv2Route := range ccv2Routes { 367 domain, warnings, err := actor.GetDomain(ccv2Route.DomainGUID) 368 allWarnings = append(allWarnings, warnings...) 369 if err != nil { 370 return nil, allWarnings, err 371 } 372 routes = append(routes, CCToActorRoute(ccv2Route, domain)) 373 } 374 375 return routes, allWarnings, nil 376 }