github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/actor/pushaction/route.go (about) 1 package pushaction 2 3 import ( 4 "fmt" 5 "net/url" 6 "regexp" 7 "strings" 8 9 "code.cloudfoundry.org/cli/actor/actionerror" 10 "code.cloudfoundry.org/cli/actor/v2action" 11 "code.cloudfoundry.org/cli/types" 12 "code.cloudfoundry.org/cli/util/manifest" 13 log "github.com/sirupsen/logrus" 14 ) 15 16 func (actor Actor) MapRoutes(config ApplicationConfig) (ApplicationConfig, bool, Warnings, error) { 17 log.Info("mapping routes") 18 19 var boundRoutes bool 20 var allWarnings Warnings 21 22 for _, route := range config.DesiredRoutes { 23 if !actor.routeInListByGUID(route, config.CurrentRoutes) { 24 log.Debugf("mapping route: %#v", route) 25 warnings, err := actor.mapRouteToApp(route, config.DesiredApplication.GUID) 26 allWarnings = append(allWarnings, warnings...) 27 if err != nil { 28 log.Errorln("mapping route:", err) 29 return ApplicationConfig{}, false, allWarnings, err 30 } 31 boundRoutes = true 32 } else { 33 log.Debugf("route %s already bound to app", route) 34 } 35 } 36 log.Debug("mapping routes complete") 37 config.CurrentRoutes = config.DesiredRoutes 38 39 return config, boundRoutes, allWarnings, nil 40 } 41 42 func (actor Actor) UnmapRoutes(config ApplicationConfig) (ApplicationConfig, Warnings, error) { 43 var warnings Warnings 44 45 appGUID := config.DesiredApplication.GUID 46 for _, route := range config.CurrentRoutes { 47 routeWarnings, err := actor.V2Actor.UnmapRouteFromApplication(route.GUID, appGUID) 48 warnings = append(warnings, routeWarnings...) 49 if err != nil { 50 return config, warnings, err 51 } 52 } 53 config.CurrentRoutes = nil 54 55 return config, warnings, nil 56 } 57 58 func (actor Actor) CalculateRoutes(routes []string, orgGUID string, spaceGUID string, existingRoutes []v2action.Route) ([]v2action.Route, Warnings, error) { 59 calculatedRoutes, unknownRoutes := actor.splitExistingRoutes(routes, existingRoutes) 60 possibleDomains, err := actor.generatePossibleDomains(unknownRoutes) 61 if err != nil { 62 log.Errorln("domain breakdown:", err) 63 return nil, nil, err 64 } 65 66 var allWarnings Warnings 67 foundDomains, warnings, err := actor.V2Actor.GetDomainsByNameAndOrganization(possibleDomains, orgGUID) 68 allWarnings = append(allWarnings, warnings...) 69 if err != nil { 70 log.Errorln("domain lookup:", err) 71 return nil, allWarnings, err 72 } 73 nameToFoundDomain := map[string]v2action.Domain{} 74 for _, foundDomain := range foundDomains { 75 log.WithField("domain", foundDomain.Name).Debug("found domain") 76 nameToFoundDomain[foundDomain.Name] = foundDomain 77 } 78 79 for _, route := range unknownRoutes { 80 log.WithField("route", route).Debug("generating route") 81 82 root, port, path, parseErr := actor.parseURL(route) 83 if parseErr != nil { 84 log.Errorln("parse route:", parseErr) 85 return nil, allWarnings, parseErr 86 } 87 88 host, domain, domainErr := actor.calculateRoute(root, nameToFoundDomain) 89 if _, ok := domainErr.(actionerror.DomainNotFoundError); ok { 90 log.Error("no matching domains") 91 return nil, allWarnings, actionerror.NoMatchingDomainError{Route: route} 92 } else if domainErr != nil { 93 log.Errorln("matching domains:", domainErr) 94 return nil, allWarnings, domainErr 95 } 96 97 potentialRoute := v2action.Route{ 98 Host: strings.Join(host, "."), 99 Domain: domain, 100 Path: path, 101 Port: port, 102 SpaceGUID: spaceGUID, 103 } 104 105 validationErr := potentialRoute.Validate() 106 if validationErr != nil { 107 return nil, allWarnings, validationErr 108 } 109 110 calculatedRoute, routeWarnings, routeErr := actor.findOrReturnPartialRouteWithSettings(potentialRoute) 111 allWarnings = append(allWarnings, routeWarnings...) 112 if routeErr != nil { 113 log.Errorln("route lookup:", routeErr) 114 return nil, allWarnings, routeErr 115 } 116 117 calculatedRoutes = append(calculatedRoutes, calculatedRoute) 118 } 119 120 return calculatedRoutes, allWarnings, nil 121 } 122 123 func (actor Actor) CreateAndMapDefaultApplicationRoute(orgGUID string, spaceGUID string, app v2action.Application) (Warnings, error) { 124 var warnings Warnings 125 126 boundRoutes, appRouteWarnings, err := actor.V2Actor.GetApplicationRoutes(app.GUID) 127 warnings = append(warnings, appRouteWarnings...) 128 if err != nil || len(boundRoutes) > 0 { 129 return warnings, err 130 } 131 132 defaultRoute, domainWarnings, err := actor.getDefaultRoute(orgGUID, spaceGUID, app.Name) 133 warnings = append(warnings, domainWarnings...) 134 if err != nil { 135 return warnings, err 136 } 137 138 spaceRoute, spaceRouteWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 139 warnings = append(warnings, spaceRouteWarnings...) 140 routeAlreadyExists := true 141 if _, ok := err.(actionerror.RouteNotFoundError); ok { 142 routeAlreadyExists = false 143 } else if err != nil { 144 return warnings, err 145 } 146 147 if !routeAlreadyExists { 148 var createRouteWarning v2action.Warnings 149 spaceRoute, createRouteWarning, err = actor.V2Actor.CreateRoute(defaultRoute, false) 150 warnings = append(warnings, createRouteWarning...) 151 if err != nil { 152 return warnings, err 153 } 154 } 155 156 mapWarnings, err := actor.V2Actor.MapRouteToApplication(spaceRoute.GUID, app.GUID) 157 warnings = append(warnings, mapWarnings...) 158 return warnings, err 159 } 160 161 func (actor Actor) CreateRoutes(config ApplicationConfig) (ApplicationConfig, bool, Warnings, error) { 162 log.Info("creating routes") 163 164 var routes []v2action.Route 165 var createdRoutes bool 166 var allWarnings Warnings 167 168 for _, route := range config.DesiredRoutes { 169 if route.GUID == "" { 170 log.WithField("route", route).Debug("creating route") 171 172 createdRoute, warnings, err := actor.V2Actor.CreateRoute(route, route.RandomTCPPort()) 173 allWarnings = append(allWarnings, warnings...) 174 if err != nil { 175 log.Errorln("creating route:", err) 176 return ApplicationConfig{}, true, allWarnings, err 177 } 178 routes = append(routes, createdRoute) 179 180 createdRoutes = true 181 } else { 182 log.WithField("route", route).Debug("already exists, skipping") 183 routes = append(routes, route) 184 } 185 } 186 config.DesiredRoutes = routes 187 188 return config, createdRoutes, allWarnings, nil 189 } 190 191 // GenerateRandomRoute generates a random route with a specified or default domain 192 // If the domain is HTTP, a random hostname is generated 193 // If the domain is TCP, an empty port is used (to signify a random port should be generated) 194 func (actor Actor) GenerateRandomRoute(manifestApp manifest.Application, spaceGUID string, orgGUID string) (v2action.Route, Warnings, error) { 195 domain, warnings, err := actor.calculateDomain(manifestApp, orgGUID) 196 if err != nil { 197 return v2action.Route{}, warnings, err 198 } 199 200 var hostname string 201 if domain.IsHTTP() { 202 hostname = fmt.Sprintf("%s-%s-%s", actor.sanitize(manifestApp.Name), actor.WordGenerator.RandomAdjective(), actor.WordGenerator.RandomNoun()) 203 hostname = strings.Trim(hostname, "-") 204 } 205 206 return v2action.Route{ 207 Host: hostname, 208 Domain: domain, 209 SpaceGUID: spaceGUID, 210 }, warnings, err 211 } 212 213 // GetGeneratedRoute returns a route with the host and the default org domain. 214 // This may be a partial route (ie no GUID) if the route does not exist. 215 func (actor Actor) GetGeneratedRoute(manifestApp manifest.Application, orgGUID string, spaceGUID string, knownRoutes []v2action.Route) (v2action.Route, Warnings, error) { 216 desiredDomain, warnings, err := actor.calculateDomain(manifestApp, orgGUID) 217 if err != nil { 218 return v2action.Route{}, warnings, err 219 } 220 221 desiredHostname, err := actor.calculateHostname(manifestApp, desiredDomain) 222 if err != nil { 223 return v2action.Route{}, warnings, err 224 } 225 226 desiredPath, err := actor.calculatePath(manifestApp, desiredDomain) 227 if err != nil { 228 return v2action.Route{}, warnings, err 229 } 230 231 defaultRoute := v2action.Route{ 232 Domain: desiredDomain, 233 Host: desiredHostname, 234 SpaceGUID: spaceGUID, 235 Path: desiredPath, 236 } 237 238 // when the default desired domain is a TCP domain, always return a 239 // new/random route 240 if desiredDomain.IsTCP() { 241 return defaultRoute, warnings, nil 242 } 243 244 cachedRoute, found := actor.routeInListBySettings(defaultRoute, knownRoutes) 245 if !found { 246 route, routeWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 247 if _, ok := err.(actionerror.RouteNotFoundError); ok { 248 return defaultRoute, append(warnings, routeWarnings...), nil 249 } 250 return route, append(warnings, routeWarnings...), err 251 } 252 return cachedRoute, warnings, nil 253 } 254 255 func (actor Actor) mapRouteToApp(route v2action.Route, appGUID string) (v2action.Warnings, error) { 256 warnings, err := actor.V2Actor.MapRouteToApplication(route.GUID, appGUID) 257 if _, ok := err.(actionerror.RouteInDifferentSpaceError); ok { 258 return warnings, actionerror.RouteInDifferentSpaceError{Route: route.String()} 259 } 260 return warnings, err 261 } 262 263 func (actor Actor) calculateDomain(manifestApp manifest.Application, orgGUID string) (v2action.Domain, Warnings, error) { 264 var ( 265 desiredDomain v2action.Domain 266 warnings Warnings 267 err error 268 ) 269 270 if manifestApp.Domain == "" { 271 desiredDomain, warnings, err = actor.DefaultDomain(orgGUID) 272 if err != nil { 273 log.Errorln("could not find default domains:", err.Error()) 274 return v2action.Domain{}, warnings, err 275 } 276 } else { 277 desiredDomains, getDomainWarnings, getDomainsErr := actor.V2Actor.GetDomainsByNameAndOrganization([]string{manifestApp.Domain}, orgGUID) 278 warnings = append(warnings, getDomainWarnings...) 279 if getDomainsErr != nil { 280 log.Errorf("could not find provided domains '%s': %s\n", manifestApp.Domain, getDomainsErr.Error()) 281 return v2action.Domain{}, warnings, getDomainsErr 282 } 283 if len(desiredDomains) == 0 { 284 log.Errorf("could not find provided domains '%s'\n", manifestApp.Domain) 285 return v2action.Domain{}, warnings, actionerror.DomainNotFoundError{Name: manifestApp.Domain} 286 } 287 // CC does not allow one to have shared/owned domains with the same domain name. so it's ok to take the first one 288 desiredDomain = desiredDomains[0] 289 } 290 291 return desiredDomain, warnings, nil 292 } 293 294 func (actor Actor) calculateHostname(manifestApp manifest.Application, domain v2action.Domain) (string, error) { 295 hostname := manifestApp.Hostname 296 if hostname == "" { 297 hostname = manifestApp.Name 298 } 299 300 sanitizedHostname := actor.sanitize(hostname) 301 302 switch { 303 case manifestApp.Hostname != "" && domain.IsTCP(): 304 return "", actionerror.HostnameWithTCPDomainError{} 305 case manifestApp.NoHostname && domain.IsShared() && domain.IsHTTP(): 306 return "", actionerror.NoHostnameAndSharedDomainError{} 307 case manifestApp.NoHostname: 308 return "", nil 309 case domain.IsHTTP(): 310 return sanitizedHostname, nil 311 default: 312 return "", nil 313 } 314 } 315 316 func (actor Actor) calculateRoute(route string, domainCache map[string]v2action.Domain) ([]string, v2action.Domain, error) { 317 host, domain := actor.splitHost(route) 318 if domain, ok := domainCache[route]; ok { 319 return nil, domain, nil 320 } 321 322 if host == "" { 323 return nil, v2action.Domain{}, actionerror.DomainNotFoundError{Name: route} 324 } 325 326 hosts, foundDomain, err := actor.calculateRoute(domain, domainCache) 327 hosts = append([]string{host}, hosts...) 328 329 return hosts, foundDomain, err 330 } 331 332 func (actor Actor) calculatePath(manifestApp manifest.Application, domain v2action.Domain) (string, error) { 333 if manifestApp.RoutePath != "" && domain.IsTCP() { 334 return "", actionerror.RoutePathWithTCPDomainError{} 335 } else { 336 return manifestApp.RoutePath, nil 337 } 338 } 339 340 func (actor Actor) findOrReturnPartialRouteWithSettings(route v2action.Route) (v2action.Route, Warnings, error) { 341 cachedRoute, warnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(route) 342 if _, ok := err.(actionerror.RouteNotFoundError); ok { 343 return route, Warnings(warnings), nil 344 } 345 return cachedRoute, Warnings(warnings), err 346 } 347 348 func (actor Actor) generatePossibleDomains(routes []string) ([]string, error) { 349 var hostnames []string 350 for _, route := range routes { 351 host, _, _, err := actor.parseURL(route) 352 if err != nil { 353 return nil, err 354 } 355 hostnames = append(hostnames, host) 356 } 357 358 possibleDomains := map[string]interface{}{} 359 for _, route := range hostnames { 360 count := strings.Count(route, ".") 361 domains := strings.SplitN(route, ".", count) 362 363 for i := range domains { 364 domain := strings.Join(domains[i:], ".") 365 possibleDomains[domain] = nil 366 } 367 } 368 369 var domains []string 370 for domain := range possibleDomains { 371 domains = append(domains, domain) 372 } 373 374 log.Debugln("domain brakedown:", strings.Join(domains, ",")) 375 return domains, nil 376 } 377 378 func (actor Actor) getDefaultRoute(orgGUID string, spaceGUID string, appName string) (v2action.Route, Warnings, error) { 379 defaultDomain, domainWarnings, err := actor.DefaultDomain(orgGUID) 380 if err != nil { 381 return v2action.Route{}, domainWarnings, err 382 } 383 384 return v2action.Route{ 385 Host: appName, 386 Domain: defaultDomain, 387 SpaceGUID: spaceGUID, 388 }, domainWarnings, nil 389 } 390 391 func (actor Actor) parseURL(route string) (string, types.NullInt, string, error) { 392 if !(actor.startWithProtocol.MatchString(route)) { 393 route = fmt.Sprintf("http://%s", route) 394 } 395 parsedURL, err := url.Parse(route) 396 if err != nil { 397 return "", types.NullInt{}, "", err 398 } 399 400 path := parsedURL.RequestURI() 401 if path == "/" { 402 path = "" 403 } 404 405 var port types.NullInt 406 err = port.ParseStringValue(parsedURL.Port()) 407 return parsedURL.Hostname(), port, path, err 408 } 409 410 func (Actor) routeInListByGUID(route v2action.Route, routes []v2action.Route) bool { 411 for _, r := range routes { 412 if r.GUID == route.GUID { 413 return true 414 } 415 } 416 417 return false 418 } 419 420 func (actor Actor) routeInListByName(route string, routes []v2action.Route) (v2action.Route, bool) { 421 strippedRoute := actor.startWithProtocol.ReplaceAllString(route, "") 422 for _, r := range routes { 423 if r.String() == strippedRoute { 424 return r, true 425 } 426 } 427 428 return v2action.Route{}, false 429 } 430 431 func (Actor) routeInListBySettings(route v2action.Route, routes []v2action.Route) (v2action.Route, bool) { 432 for _, r := range routes { 433 if r.Host == route.Host && r.Path == route.Path && r.Port == route.Port && 434 r.SpaceGUID == route.SpaceGUID && r.Domain.GUID == route.Domain.GUID { 435 return r, true 436 } 437 } 438 439 return v2action.Route{}, false 440 } 441 442 func (Actor) sanitize(name string) string { 443 name = strings.ToLower(name) 444 445 re := regexp.MustCompile("\\s+") 446 name = re.ReplaceAllString(name, "-") 447 448 re = regexp.MustCompile("[^[:alnum:]\\-]") 449 name = re.ReplaceAllString(name, "") 450 451 return strings.TrimLeft(name, "-") 452 } 453 454 func (actor Actor) splitExistingRoutes(routes []string, existingRoutes []v2action.Route) ([]v2action.Route, []string) { 455 var cachedRoutes []v2action.Route 456 for _, route := range existingRoutes { 457 cachedRoutes = append(cachedRoutes, route) 458 } 459 460 var unknownRoutes []string 461 for _, route := range routes { 462 if _, found := actor.routeInListByName(route, existingRoutes); !found { 463 log.WithField("route", route).Debug("unable to find route in cache") 464 unknownRoutes = append(unknownRoutes, route) 465 } 466 } 467 return cachedRoutes, unknownRoutes 468 } 469 470 func (Actor) splitHost(url string) (string, string) { 471 count := strings.Count(url, ".") 472 if count == 1 { 473 return "", url 474 } 475 476 split := strings.SplitN(url, ".", 2) 477 return split[0], split[1] 478 }