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  }