github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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( 203 "%s-%s-%s-%s", 204 actor.sanitize(manifestApp.Name), 205 actor.WordGenerator.RandomAdjective(), 206 actor.WordGenerator.RandomNoun(), 207 actor.WordGenerator.RandomTwoLetters(), 208 ) 209 hostname = strings.Trim(hostname, "-") 210 } 211 212 return v2action.Route{ 213 Host: hostname, 214 Domain: domain, 215 SpaceGUID: spaceGUID, 216 }, warnings, err 217 } 218 219 // GetGeneratedRoute returns a route with the host and the default org domain. 220 // This may be a partial route (ie no GUID) if the route does not exist. 221 func (actor Actor) GetGeneratedRoute(manifestApp manifest.Application, orgGUID string, spaceGUID string, knownRoutes []v2action.Route) (v2action.Route, Warnings, error) { 222 desiredDomain, warnings, err := actor.calculateDomain(manifestApp, orgGUID) 223 if err != nil { 224 return v2action.Route{}, warnings, err 225 } 226 227 desiredHostname, err := actor.calculateHostname(manifestApp, desiredDomain) 228 if err != nil { 229 return v2action.Route{}, warnings, err 230 } 231 232 desiredPath, err := actor.calculatePath(manifestApp, desiredDomain) 233 if err != nil { 234 return v2action.Route{}, warnings, err 235 } 236 237 defaultRoute := v2action.Route{ 238 Domain: desiredDomain, 239 Host: desiredHostname, 240 SpaceGUID: spaceGUID, 241 Path: desiredPath, 242 } 243 244 // when the default desired domain is a TCP domain, always return a 245 // new/random route 246 if desiredDomain.IsTCP() { 247 return defaultRoute, warnings, nil 248 } 249 250 cachedRoute, found := actor.routeInListBySettings(defaultRoute, knownRoutes) 251 if !found { 252 route, routeWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 253 if _, ok := err.(actionerror.RouteNotFoundError); ok { 254 return defaultRoute, append(warnings, routeWarnings...), nil 255 } 256 return route, append(warnings, routeWarnings...), err 257 } 258 return cachedRoute, warnings, nil 259 } 260 261 func (actor Actor) mapRouteToApp(route v2action.Route, appGUID string) (v2action.Warnings, error) { 262 warnings, err := actor.V2Actor.MapRouteToApplication(route.GUID, appGUID) 263 if _, ok := err.(actionerror.RouteInDifferentSpaceError); ok { 264 return warnings, actionerror.RouteInDifferentSpaceError{Route: route.String()} 265 } 266 return warnings, err 267 } 268 269 func (actor Actor) calculateDomain(manifestApp manifest.Application, orgGUID string) (v2action.Domain, Warnings, error) { 270 var ( 271 desiredDomain v2action.Domain 272 warnings Warnings 273 err error 274 ) 275 276 if manifestApp.Domain == "" { 277 desiredDomain, warnings, err = actor.DefaultDomain(orgGUID) 278 if err != nil { 279 log.Errorln("could not find default domains:", err.Error()) 280 return v2action.Domain{}, warnings, err 281 } 282 } else { 283 desiredDomains, getDomainWarnings, getDomainsErr := actor.V2Actor.GetDomainsByNameAndOrganization([]string{manifestApp.Domain}, orgGUID) 284 warnings = append(warnings, getDomainWarnings...) 285 if getDomainsErr != nil { 286 log.Errorf("could not find provided domains '%s': %s\n", manifestApp.Domain, getDomainsErr.Error()) 287 return v2action.Domain{}, warnings, getDomainsErr 288 } 289 if len(desiredDomains) == 0 { 290 log.Errorf("could not find provided domains '%s'\n", manifestApp.Domain) 291 return v2action.Domain{}, warnings, actionerror.DomainNotFoundError{Name: manifestApp.Domain} 292 } 293 // CC does not allow one to have shared/owned domains with the same domain name. so it's ok to take the first one 294 desiredDomain = desiredDomains[0] 295 } 296 297 return desiredDomain, warnings, nil 298 } 299 300 func (actor Actor) calculateHostname(manifestApp manifest.Application, domain v2action.Domain) (string, error) { 301 hostname := manifestApp.Hostname 302 if hostname == "" { 303 hostname = manifestApp.Name 304 } 305 306 sanitizedHostname := actor.sanitize(hostname) 307 308 switch { 309 case manifestApp.Hostname != "" && domain.IsTCP(): 310 return "", actionerror.HostnameWithTCPDomainError{} 311 case manifestApp.NoHostname && domain.IsShared() && domain.IsHTTP(): 312 return "", actionerror.NoHostnameAndSharedDomainError{} 313 case manifestApp.NoHostname: 314 return "", nil 315 case domain.IsHTTP(): 316 return sanitizedHostname, nil 317 default: 318 return "", nil 319 } 320 } 321 322 func (actor Actor) calculateRoute(route string, domainCache map[string]v2action.Domain) ([]string, v2action.Domain, error) { 323 host, domain := actor.splitHost(route) 324 if domain, ok := domainCache[route]; ok { 325 return nil, domain, nil 326 } 327 328 if host == "" { 329 return nil, v2action.Domain{}, actionerror.DomainNotFoundError{Name: route} 330 } 331 332 hosts, foundDomain, err := actor.calculateRoute(domain, domainCache) 333 hosts = append([]string{host}, hosts...) 334 335 return hosts, foundDomain, err 336 } 337 338 func (actor Actor) calculatePath(manifestApp manifest.Application, domain v2action.Domain) (string, error) { 339 if manifestApp.RoutePath != "" && domain.IsTCP() { 340 return "", actionerror.RoutePathWithTCPDomainError{} 341 } 342 343 return manifestApp.RoutePath, nil 344 } 345 346 func (actor Actor) findOrReturnPartialRouteWithSettings(route v2action.Route) (v2action.Route, Warnings, error) { 347 cachedRoute, warnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(route) 348 if _, ok := err.(actionerror.RouteNotFoundError); ok { 349 return route, Warnings(warnings), nil 350 } 351 return cachedRoute, Warnings(warnings), err 352 } 353 354 func (actor Actor) generatePossibleDomains(routes []string) ([]string, error) { 355 var hostnames []string 356 for _, route := range routes { 357 host, _, _, err := actor.parseURL(route) 358 if err != nil { 359 return nil, err 360 } 361 hostnames = append(hostnames, host) 362 } 363 364 possibleDomains := map[string]interface{}{} 365 for _, route := range hostnames { 366 count := strings.Count(route, ".") 367 domains := strings.SplitN(route, ".", count) 368 369 for i := range domains { 370 domain := strings.Join(domains[i:], ".") 371 possibleDomains[domain] = nil 372 } 373 } 374 375 var domains []string 376 for domain := range possibleDomains { 377 domains = append(domains, domain) 378 } 379 380 log.Debugln("domain brakedown:", strings.Join(domains, ",")) 381 return domains, nil 382 } 383 384 func (actor Actor) getDefaultRoute(orgGUID string, spaceGUID string, appName string) (v2action.Route, Warnings, error) { 385 defaultDomain, domainWarnings, err := actor.DefaultDomain(orgGUID) 386 if err != nil { 387 return v2action.Route{}, domainWarnings, err 388 } 389 390 return v2action.Route{ 391 Host: appName, 392 Domain: defaultDomain, 393 SpaceGUID: spaceGUID, 394 }, domainWarnings, nil 395 } 396 397 func (actor Actor) parseURL(route string) (string, types.NullInt, string, error) { 398 if !(actor.startWithProtocol.MatchString(route)) { 399 route = fmt.Sprintf("http://%s", route) 400 } 401 parsedURL, err := url.Parse(route) 402 if err != nil { 403 return "", types.NullInt{}, "", err 404 } 405 406 path := parsedURL.RequestURI() 407 if path == "/" { 408 path = "" 409 } 410 411 var port types.NullInt 412 err = port.ParseStringValue(parsedURL.Port()) 413 return parsedURL.Hostname(), port, path, err 414 } 415 416 func (Actor) routeInListByGUID(route v2action.Route, routes []v2action.Route) bool { 417 for _, r := range routes { 418 if r.GUID == route.GUID { 419 return true 420 } 421 } 422 423 return false 424 } 425 426 func (actor Actor) routeInListByName(route string, routes []v2action.Route) (v2action.Route, bool) { 427 strippedRoute := actor.startWithProtocol.ReplaceAllString(route, "") 428 for _, r := range routes { 429 if r.String() == strippedRoute { 430 return r, true 431 } 432 } 433 434 return v2action.Route{}, false 435 } 436 437 func (Actor) routeInListBySettings(route v2action.Route, routes []v2action.Route) (v2action.Route, bool) { 438 for _, r := range routes { 439 if r.Host == route.Host && r.Path == route.Path && r.Port == route.Port && 440 r.SpaceGUID == route.SpaceGUID && r.Domain.GUID == route.Domain.GUID { 441 return r, true 442 } 443 } 444 445 return v2action.Route{}, false 446 } 447 448 func (Actor) sanitize(name string) string { 449 name = strings.ToLower(name) 450 451 re := regexp.MustCompile("\\s+") 452 name = re.ReplaceAllString(name, "-") 453 454 re = regexp.MustCompile("[^[:alnum:]\\-]") 455 name = re.ReplaceAllString(name, "") 456 457 return strings.TrimLeft(name, "-") 458 } 459 460 func (actor Actor) splitExistingRoutes(routes []string, existingRoutes []v2action.Route) ([]v2action.Route, []string) { 461 var cachedRoutes []v2action.Route 462 for _, route := range existingRoutes { 463 cachedRoutes = append(cachedRoutes, route) 464 } 465 466 var unknownRoutes []string 467 for _, route := range routes { 468 if _, found := actor.routeInListByName(route, existingRoutes); !found { 469 log.WithField("route", route).Debug("unable to find route in cache") 470 unknownRoutes = append(unknownRoutes, route) 471 } 472 } 473 return cachedRoutes, unknownRoutes 474 } 475 476 func (Actor) splitHost(url string) (string, string) { 477 count := strings.Count(url, ".") 478 if count == 1 { 479 return "", url 480 } 481 482 split := strings.SplitN(url, ".", 2) 483 return split[0], split[1] 484 }