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  }