github.com/nimakaviani/cli@v6.37.1-0.20180619223813-e734901a73fa+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 defaultRoute, domainWarnings, err := actor.getDefaultRoute(orgGUID, spaceGUID, app.Name) 126 warnings = append(warnings, domainWarnings...) 127 if err != nil { 128 return warnings, err 129 } 130 131 boundRoutes, appRouteWarnings, err := actor.V2Actor.GetApplicationRoutes(app.GUID) 132 warnings = append(warnings, appRouteWarnings...) 133 if err != nil { 134 return warnings, err 135 } 136 137 _, routeAlreadyBound := actor.routeInListBySettings(defaultRoute, boundRoutes) 138 if routeAlreadyBound { 139 return warnings, err 140 } 141 142 spaceRoute, spaceRouteWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 143 warnings = append(warnings, spaceRouteWarnings...) 144 routeAlreadyExists := true 145 if _, ok := err.(actionerror.RouteNotFoundError); ok { 146 routeAlreadyExists = false 147 } else if err != nil { 148 return warnings, err 149 } 150 151 if !routeAlreadyExists { 152 var createRouteWarning v2action.Warnings 153 spaceRoute, createRouteWarning, err = actor.V2Actor.CreateRoute(defaultRoute, false) 154 warnings = append(warnings, createRouteWarning...) 155 if err != nil { 156 return warnings, err 157 } 158 } 159 160 mapWarnings, err := actor.V2Actor.MapRouteToApplication(spaceRoute.GUID, app.GUID) 161 warnings = append(warnings, mapWarnings...) 162 return warnings, err 163 } 164 165 func (actor Actor) CreateRoutes(config ApplicationConfig) (ApplicationConfig, bool, Warnings, error) { 166 log.Info("creating routes") 167 168 var routes []v2action.Route 169 var createdRoutes bool 170 var allWarnings Warnings 171 172 for _, route := range config.DesiredRoutes { 173 if route.GUID == "" { 174 log.WithField("route", route).Debug("creating route") 175 176 createdRoute, warnings, err := actor.V2Actor.CreateRoute(route, route.RandomTCPPort()) 177 allWarnings = append(allWarnings, warnings...) 178 if err != nil { 179 log.Errorln("creating route:", err) 180 return ApplicationConfig{}, true, allWarnings, err 181 } 182 routes = append(routes, createdRoute) 183 184 createdRoutes = true 185 } else { 186 log.WithField("route", route).Debug("already exists, skipping") 187 routes = append(routes, route) 188 } 189 } 190 config.DesiredRoutes = routes 191 192 return config, createdRoutes, allWarnings, nil 193 } 194 195 // GenerateRandomRoute generates a random route with a specified or default domain 196 // If the domain is HTTP, a random hostname is generated 197 // If the domain is TCP, an empty port is used (to signify a random port should be generated) 198 func (actor Actor) GenerateRandomRoute(manifestApp manifest.Application, spaceGUID string, orgGUID string) (v2action.Route, Warnings, error) { 199 domain, warnings, err := actor.calculateDomain(manifestApp, orgGUID) 200 if err != nil { 201 return v2action.Route{}, warnings, err 202 } 203 204 var hostname string 205 if domain.IsHTTP() { 206 hostname = fmt.Sprintf("%s-%s-%s", actor.sanitize(manifestApp.Name), actor.WordGenerator.RandomAdjective(), actor.WordGenerator.RandomNoun()) 207 hostname = strings.Trim(hostname, "-") 208 } 209 210 return v2action.Route{ 211 Host: hostname, 212 Domain: domain, 213 SpaceGUID: spaceGUID, 214 }, warnings, err 215 } 216 217 // GetGeneratedRoute returns a route with the host and the default org domain. 218 // This may be a partial route (ie no GUID) if the route does not exist. 219 func (actor Actor) GetGeneratedRoute(manifestApp manifest.Application, orgGUID string, spaceGUID string, knownRoutes []v2action.Route) (v2action.Route, Warnings, error) { 220 desiredDomain, warnings, err := actor.calculateDomain(manifestApp, orgGUID) 221 if err != nil { 222 return v2action.Route{}, warnings, err 223 } 224 225 desiredHostname, err := actor.calculateHostname(manifestApp, desiredDomain) 226 if err != nil { 227 return v2action.Route{}, warnings, err 228 } 229 230 desiredPath, err := actor.calculatePath(manifestApp, desiredDomain) 231 if err != nil { 232 return v2action.Route{}, warnings, err 233 } 234 235 defaultRoute := v2action.Route{ 236 Domain: desiredDomain, 237 Host: desiredHostname, 238 SpaceGUID: spaceGUID, 239 Path: desiredPath, 240 } 241 242 // when the default desired domain is a TCP domain, always return a 243 // new/random route 244 if desiredDomain.IsTCP() { 245 return defaultRoute, warnings, nil 246 } 247 248 cachedRoute, found := actor.routeInListBySettings(defaultRoute, knownRoutes) 249 if !found { 250 route, routeWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 251 if _, ok := err.(actionerror.RouteNotFoundError); ok { 252 return defaultRoute, append(warnings, routeWarnings...), nil 253 } 254 return route, append(warnings, routeWarnings...), err 255 } 256 return cachedRoute, warnings, nil 257 } 258 259 func (actor Actor) mapRouteToApp(route v2action.Route, appGUID string) (v2action.Warnings, error) { 260 warnings, err := actor.V2Actor.MapRouteToApplication(route.GUID, appGUID) 261 if _, ok := err.(actionerror.RouteInDifferentSpaceError); ok { 262 return warnings, actionerror.RouteInDifferentSpaceError{Route: route.String()} 263 } 264 return warnings, err 265 } 266 267 func (actor Actor) calculateDomain(manifestApp manifest.Application, orgGUID string) (v2action.Domain, Warnings, error) { 268 var ( 269 desiredDomain v2action.Domain 270 warnings Warnings 271 err error 272 ) 273 274 if manifestApp.Domain == "" { 275 desiredDomain, warnings, err = actor.DefaultDomain(orgGUID) 276 if err != nil { 277 log.Errorln("could not find default domains:", err.Error()) 278 return v2action.Domain{}, warnings, err 279 } 280 } else { 281 desiredDomains, getDomainWarnings, getDomainsErr := actor.V2Actor.GetDomainsByNameAndOrganization([]string{manifestApp.Domain}, orgGUID) 282 warnings = append(warnings, getDomainWarnings...) 283 if getDomainsErr != nil { 284 log.Errorln("could not find provided domains '%s':", manifestApp.Domain, getDomainsErr.Error()) 285 return v2action.Domain{}, warnings, getDomainsErr 286 } 287 if len(desiredDomains) == 0 { 288 log.Errorln("could not find provided domains '%s':", manifestApp.Domain) 289 return v2action.Domain{}, warnings, actionerror.DomainNotFoundError{Name: manifestApp.Domain} 290 } 291 // CC does not allow one to have shared/owned domains with the same domain name. so it's ok to take the first one 292 desiredDomain = desiredDomains[0] 293 } 294 295 return desiredDomain, warnings, nil 296 } 297 298 func (actor Actor) calculateHostname(manifestApp manifest.Application, domain v2action.Domain) (string, error) { 299 hostname := manifestApp.Hostname 300 if hostname == "" { 301 hostname = manifestApp.Name 302 } 303 304 sanitizedHostname := actor.sanitize(hostname) 305 306 switch { 307 case manifestApp.Hostname != "" && domain.IsTCP(): 308 return "", actionerror.HostnameWithTCPDomainError{} 309 case manifestApp.NoHostname && domain.IsShared() && domain.IsHTTP(): 310 return "", actionerror.NoHostnameAndSharedDomainError{} 311 case manifestApp.NoHostname: 312 return "", nil 313 case domain.IsHTTP(): 314 return sanitizedHostname, nil 315 default: 316 return "", nil 317 } 318 } 319 320 func (actor Actor) calculateRoute(route string, domainCache map[string]v2action.Domain) ([]string, v2action.Domain, error) { 321 host, domain := actor.splitHost(route) 322 if domain, ok := domainCache[route]; ok { 323 return nil, domain, nil 324 } 325 326 if host == "" { 327 return nil, v2action.Domain{}, actionerror.DomainNotFoundError{Name: route} 328 } 329 330 hosts, foundDomain, err := actor.calculateRoute(domain, domainCache) 331 hosts = append([]string{host}, hosts...) 332 333 return hosts, foundDomain, err 334 } 335 336 func (actor Actor) calculatePath(manifestApp manifest.Application, domain v2action.Domain) (string, error) { 337 if manifestApp.RoutePath != "" && domain.IsTCP() { 338 return "", actionerror.RoutePathWithTCPDomainError{} 339 } else { 340 return manifestApp.RoutePath, nil 341 } 342 } 343 344 func (actor Actor) findOrReturnPartialRouteWithSettings(route v2action.Route) (v2action.Route, Warnings, error) { 345 cachedRoute, warnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(route) 346 if _, ok := err.(actionerror.RouteNotFoundError); ok { 347 return route, Warnings(warnings), nil 348 } 349 return cachedRoute, Warnings(warnings), err 350 } 351 352 func (actor Actor) generatePossibleDomains(routes []string) ([]string, error) { 353 var hostnames []string 354 for _, route := range routes { 355 host, _, _, err := actor.parseURL(route) 356 if err != nil { 357 return nil, err 358 } 359 hostnames = append(hostnames, host) 360 } 361 362 possibleDomains := map[string]interface{}{} 363 for _, route := range hostnames { 364 count := strings.Count(route, ".") 365 domains := strings.SplitN(route, ".", count) 366 367 for i := range domains { 368 domain := strings.Join(domains[i:], ".") 369 possibleDomains[domain] = nil 370 } 371 } 372 373 var domains []string 374 for domain := range possibleDomains { 375 domains = append(domains, domain) 376 } 377 378 log.Debugln("domain brakedown:", strings.Join(domains, ",")) 379 return domains, nil 380 } 381 382 func (actor Actor) getDefaultRoute(orgGUID string, spaceGUID string, appName string) (v2action.Route, Warnings, error) { 383 defaultDomain, domainWarnings, err := actor.DefaultDomain(orgGUID) 384 if err != nil { 385 return v2action.Route{}, domainWarnings, err 386 } 387 388 return v2action.Route{ 389 Host: appName, 390 Domain: defaultDomain, 391 SpaceGUID: spaceGUID, 392 }, domainWarnings, nil 393 } 394 395 func (actor Actor) parseURL(route string) (string, types.NullInt, string, error) { 396 if !(actor.startWithProtocol.MatchString(route)) { 397 route = fmt.Sprintf("http://%s", route) 398 } 399 parsedURL, err := url.Parse(route) 400 if err != nil { 401 return "", types.NullInt{}, "", err 402 } 403 404 path := parsedURL.RequestURI() 405 if path == "/" { 406 path = "" 407 } 408 409 var port types.NullInt 410 err = port.ParseStringValue(parsedURL.Port()) 411 return parsedURL.Hostname(), port, path, err 412 } 413 414 func (Actor) routeInListByGUID(route v2action.Route, routes []v2action.Route) bool { 415 for _, r := range routes { 416 if r.GUID == route.GUID { 417 return true 418 } 419 } 420 421 return false 422 } 423 424 func (actor Actor) routeInListByName(route string, routes []v2action.Route) (v2action.Route, bool) { 425 strippedRoute := actor.startWithProtocol.ReplaceAllString(route, "") 426 for _, r := range routes { 427 if r.String() == strippedRoute { 428 return r, true 429 } 430 } 431 432 return v2action.Route{}, false 433 } 434 435 func (Actor) routeInListBySettings(route v2action.Route, routes []v2action.Route) (v2action.Route, bool) { 436 for _, r := range routes { 437 if r.Host == route.Host && r.Path == route.Path && r.Port == route.Port && 438 r.SpaceGUID == route.SpaceGUID && r.Domain.GUID == route.Domain.GUID { 439 return r, true 440 } 441 } 442 443 return v2action.Route{}, false 444 } 445 446 func (Actor) sanitize(name string) string { 447 name = strings.ToLower(name) 448 449 re := regexp.MustCompile("\\s+") 450 name = re.ReplaceAllString(name, "-") 451 452 re = regexp.MustCompile("[^[:alnum:]\\-]") 453 name = re.ReplaceAllString(name, "") 454 455 return strings.TrimLeft(name, "-") 456 } 457 458 func (actor Actor) splitExistingRoutes(routes []string, existingRoutes []v2action.Route) ([]v2action.Route, []string) { 459 var cachedRoutes []v2action.Route 460 for _, route := range existingRoutes { 461 cachedRoutes = append(cachedRoutes, route) 462 } 463 464 var unknownRoutes []string 465 for _, route := range routes { 466 if _, found := actor.routeInListByName(route, existingRoutes); !found { 467 log.WithField("route", route).Debug("unable to find route in cache") 468 unknownRoutes = append(unknownRoutes, route) 469 } 470 } 471 return cachedRoutes, unknownRoutes 472 } 473 474 func (Actor) splitHost(url string) (string, string) { 475 count := strings.Count(url, ".") 476 if count == 1 { 477 return "", url 478 } 479 480 split := strings.SplitN(url, ".", 2) 481 return split[0], split[1] 482 }