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  }