github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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 // DeleteRoute deletes the Route associated with the provided Route GUID. 225 func (actor Actor) DeleteRoute(routeGUID string) (Warnings, error) { 226 warnings, err := actor.CloudControllerClient.DeleteRoute(routeGUID) 227 return Warnings(warnings), err 228 } 229 230 func (actor Actor) CheckRoute(route Route) (bool, Warnings, error) { 231 exists, warnings, err := actor.CloudControllerClient.CheckRoute(ActorToCCRoute(route)) 232 return exists, Warnings(warnings), err 233 } 234 235 // FindRouteBoundToSpaceWithSettings finds the route with the given host, 236 // domain and space. If it is unable to find the route, it will check if it 237 // exists anywhere in the system. When the route exists in another space, 238 // RouteInDifferentSpaceError is returned. Use this when you know the space 239 // GUID. 240 func (actor Actor) FindRouteBoundToSpaceWithSettings(route Route) (Route, Warnings, error) { 241 existingRoute, warnings, err := actor.GetRouteByComponents(route) 242 if routeNotFoundErr, ok := err.(actionerror.RouteNotFoundError); ok { 243 // This check only works for API versions 2.55 or higher. It will return 244 // false for anything below that. 245 log.Infoln("checking route existence for: %s", route) 246 exists, checkRouteWarnings, chkErr := actor.CheckRoute(route) 247 if chkErr != nil { 248 log.Errorln("check route:", err) 249 return Route{}, append(Warnings(warnings), checkRouteWarnings...), chkErr 250 } 251 252 // This will happen if the route exists in a space to which the user does 253 // not have access. 254 if exists { 255 log.Errorf("unable to find route %s in current space", route.String()) 256 return Route{}, append(Warnings(warnings), checkRouteWarnings...), actionerror.RouteInDifferentSpaceError{Route: route.String()} 257 } 258 259 log.Warnf("negative existence check for route %s - returning partial route", route.String()) 260 log.Debugf("partialRoute: %#v", route) 261 return Route{}, append(Warnings(warnings), checkRouteWarnings...), routeNotFoundErr 262 } else if err != nil { 263 log.Errorln("finding route:", err) 264 return Route{}, Warnings(warnings), err 265 } 266 267 if existingRoute.SpaceGUID != route.SpaceGUID { 268 log.WithFields(log.Fields{ 269 "targeted_space_guid": route.SpaceGUID, 270 "existing_space_guid": existingRoute.SpaceGUID, 271 }).Errorf("route exists in different space the user has access to") 272 return Route{}, Warnings(warnings), actionerror.RouteInDifferentSpaceError{Route: route.String()} 273 } 274 275 log.Debugf("found route: %#v", existingRoute) 276 return existingRoute, Warnings(warnings), err 277 } 278 279 // GetRouteByComponents returns the route with the matching host, domain, path, 280 // and port. Use this when you don't know the space GUID. 281 // TCP routes require a port to be uniquely identified 282 // HTTP routes using shared domains require a hostname or path to be uniquely identified 283 func (actor Actor) GetRouteByComponents(route Route) (Route, Warnings, error) { 284 // TODO: validation should probably be done separately (?) 285 if route.Domain.IsTCP() && !route.Port.IsSet { 286 return Route{}, nil, actionerror.PortNotProvidedForQueryError{} 287 } 288 289 if route.Domain.IsShared() && route.Domain.IsHTTP() && route.Host == "" { 290 return Route{}, nil, actionerror.NoHostnameAndSharedDomainError{} 291 } 292 293 queries := []ccv2.Filter{ 294 { 295 Type: constant.DomainGUIDFilter, 296 Operator: constant.EqualOperator, 297 Values: []string{route.Domain.GUID}, 298 }, { 299 Type: constant.HostFilter, 300 Operator: constant.EqualOperator, 301 Values: []string{route.Host}, 302 }, { 303 Type: constant.PathFilter, 304 Operator: constant.EqualOperator, 305 Values: []string{route.Path}, 306 }, 307 } 308 309 if route.Port.IsSet { 310 queries = append(queries, ccv2.Filter{ 311 Type: constant.PortFilter, 312 Operator: constant.EqualOperator, 313 Values: []string{fmt.Sprint(route.Port.Value)}, 314 }) 315 } 316 317 ccv2Routes, warnings, err := actor.CloudControllerClient.GetRoutes(queries...) 318 if err != nil { 319 return Route{}, Warnings(warnings), err 320 } 321 322 if len(ccv2Routes) == 0 { 323 return Route{}, Warnings(warnings), actionerror.RouteNotFoundError{ 324 Host: route.Host, 325 DomainGUID: route.Domain.GUID, 326 Path: route.Path, 327 Port: route.Port.Value, 328 } 329 } 330 331 return CCToActorRoute(ccv2Routes[0], route.Domain), Warnings(warnings), err 332 } 333 334 func ActorToCCRoute(route Route) ccv2.Route { 335 return ccv2.Route{ 336 DomainGUID: route.Domain.GUID, 337 GUID: route.GUID, 338 Host: route.Host, 339 Path: route.Path, 340 Port: route.Port, 341 SpaceGUID: route.SpaceGUID, 342 } 343 } 344 345 func CCToActorRoute(ccv2Route ccv2.Route, domain Domain) Route { 346 return Route{ 347 Domain: domain, 348 GUID: ccv2Route.GUID, 349 Host: ccv2Route.Host, 350 Path: ccv2Route.Path, 351 Port: ccv2Route.Port, 352 SpaceGUID: ccv2Route.SpaceGUID, 353 } 354 } 355 356 func (actor Actor) applyDomain(ccv2Routes []ccv2.Route) (Routes, Warnings, error) { 357 var routes Routes 358 var allWarnings Warnings 359 360 for _, ccv2Route := range ccv2Routes { 361 domain, warnings, err := actor.GetDomain(ccv2Route.DomainGUID) 362 allWarnings = append(allWarnings, warnings...) 363 if err != nil { 364 return nil, allWarnings, err 365 } 366 routes = append(routes, CCToActorRoute(ccv2Route, domain)) 367 } 368 369 return routes, allWarnings, nil 370 }