github.com/cloudfoundry/cli@v7.1.0+incompatible/cf/actors/routes.go (about)

     1  package actors
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"code.cloudfoundry.org/cli/cf/api"
     9  	"code.cloudfoundry.org/cli/cf/errors"
    10  	. "code.cloudfoundry.org/cli/cf/i18n"
    11  	"code.cloudfoundry.org/cli/cf/models"
    12  	"code.cloudfoundry.org/cli/cf/terminal"
    13  	"code.cloudfoundry.org/cli/util/randomword"
    14  )
    15  
    16  //go:generate counterfeiter . RouteActor
    17  
    18  const tcp = "tcp"
    19  
    20  type RouteActor interface {
    21  	CreateRandomTCPRoute(domain models.DomainFields) (models.Route, error)
    22  	FindOrCreateRoute(hostname string, domain models.DomainFields, path string, port int, useRandomPort bool) (models.Route, error)
    23  	BindRoute(app models.Application, route models.Route) error
    24  	UnbindAll(app models.Application) error
    25  	FindDomain(routeName string) (string, models.DomainFields, error)
    26  	FindPath(routeName string) (string, string)
    27  	FindPort(routeName string) (string, int, error)
    28  	FindAndBindRoute(routeName string, app models.Application, appParamsFromContext models.AppParams) error
    29  }
    30  
    31  type routeActor struct {
    32  	ui         terminal.UI
    33  	routeRepo  api.RouteRepository
    34  	domainRepo api.DomainRepository
    35  }
    36  
    37  func NewRouteActor(ui terminal.UI, routeRepo api.RouteRepository, domainRepo api.DomainRepository) routeActor {
    38  	return routeActor{
    39  		ui:         ui,
    40  		routeRepo:  routeRepo,
    41  		domainRepo: domainRepo,
    42  	}
    43  }
    44  
    45  func (routeActor routeActor) CreateRandomTCPRoute(domain models.DomainFields) (models.Route, error) {
    46  	routeActor.ui.Say(T("Creating random route for {{.Domain}}", map[string]interface{}{
    47  		"Domain": terminal.EntityNameColor(domain.Name),
    48  	}) + "...")
    49  
    50  	route, err := routeActor.routeRepo.Create("", domain, "", 0, true)
    51  	if err != nil {
    52  		return models.Route{}, err
    53  	}
    54  
    55  	return route, nil
    56  }
    57  
    58  func (routeActor routeActor) FindOrCreateRoute(hostname string, domain models.DomainFields, path string, port int, useRandomPort bool) (models.Route, error) {
    59  	var route models.Route
    60  	var err error
    61  	//if tcp route use random port should skip route lookup
    62  	if useRandomPort && domain.RouterGroupType == tcp {
    63  		err = new(errors.ModelNotFoundError)
    64  	} else {
    65  		route, err = routeActor.routeRepo.Find(hostname, domain, path, port)
    66  	}
    67  
    68  	switch err.(type) {
    69  	case nil:
    70  		routeActor.ui.Say(
    71  			T("Using route {{.RouteURL}}",
    72  				map[string]interface{}{
    73  					"RouteURL": terminal.EntityNameColor(route.URL()),
    74  				}),
    75  		)
    76  	case *errors.ModelNotFoundError:
    77  		if useRandomPort && domain.RouterGroupType == tcp {
    78  			route, err = routeActor.CreateRandomTCPRoute(domain)
    79  		} else {
    80  			routeActor.ui.Say(
    81  				T("Creating route {{.Hostname}}...",
    82  					map[string]interface{}{
    83  						"Hostname": terminal.EntityNameColor(domain.URLForHostAndPath(hostname, path, port)),
    84  					}),
    85  			)
    86  
    87  			route, err = routeActor.routeRepo.Create(hostname, domain, path, port, false)
    88  		}
    89  
    90  		routeActor.ui.Ok()
    91  		routeActor.ui.Say("")
    92  	}
    93  
    94  	return route, err
    95  }
    96  
    97  func (routeActor routeActor) BindRoute(app models.Application, route models.Route) error {
    98  	if !app.HasRoute(route) {
    99  		routeActor.ui.Say(T(
   100  			"Binding {{.URL}} to {{.AppName}}...",
   101  			map[string]interface{}{
   102  				"URL":     terminal.EntityNameColor(route.URL()),
   103  				"AppName": terminal.EntityNameColor(app.Name),
   104  			}),
   105  		)
   106  
   107  		err := routeActor.routeRepo.Bind(route.GUID, app.GUID)
   108  		switch err := err.(type) {
   109  		case nil:
   110  			routeActor.ui.Ok()
   111  			routeActor.ui.Say("")
   112  			return nil
   113  		case errors.HTTPError:
   114  			if err.ErrorCode() == errors.InvalidRelation {
   115  				return errors.New(T(
   116  					"The route {{.URL}} is already in use.\nTIP: Change the hostname with -n HOSTNAME or use --random-route to generate a new route and then push again.",
   117  					map[string]interface{}{
   118  						"URL": route.URL(),
   119  					}),
   120  				)
   121  			}
   122  		}
   123  		return err
   124  	}
   125  	return nil
   126  }
   127  
   128  func (routeActor routeActor) UnbindAll(app models.Application) error {
   129  	for _, route := range app.Routes {
   130  		routeActor.ui.Say(T(
   131  			"Removing route {{.URL}}...",
   132  			map[string]interface{}{
   133  				"URL": terminal.EntityNameColor(route.URL()),
   134  			}),
   135  		)
   136  		err := routeActor.routeRepo.Unbind(route.GUID, app.GUID)
   137  		if err != nil {
   138  			return err
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  func (routeActor routeActor) FindDomain(routeName string) (string, models.DomainFields, error) {
   145  	host, domain, continueSearch, err := parseRoute(routeName, routeActor.domainRepo.FindPrivateByName)
   146  	if continueSearch {
   147  		host, domain, _, err = parseRoute(routeName, routeActor.domainRepo.FindSharedByName)
   148  	}
   149  	return host, domain, err
   150  }
   151  
   152  func (routeActor routeActor) FindPath(routeName string) (string, string) {
   153  	routeSlice := strings.Split(routeName, "/")
   154  	return routeSlice[0], strings.Join(routeSlice[1:], "/")
   155  }
   156  
   157  func (routeActor routeActor) FindPort(routeName string) (string, int, error) {
   158  	var err error
   159  	routeSlice := strings.Split(routeName, ":")
   160  	port := 0
   161  	if len(routeSlice) == 2 {
   162  		port, err = strconv.Atoi(routeSlice[1])
   163  		if err != nil {
   164  			return "", 0, errors.New(T("Invalid port for route {{.RouteName}}",
   165  				map[string]interface{}{
   166  					"RouteName": routeName,
   167  				},
   168  			))
   169  		}
   170  	}
   171  	return routeSlice[0], port, nil
   172  }
   173  
   174  func (routeActor routeActor) replaceDomain(routeWithoutPathAndPort string, domain string) (string, error) {
   175  	_, flagDomain, err := routeActor.FindDomain(domain)
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  
   180  	switch {
   181  	case flagDomain.Shared && flagDomain.RouterGroupType == "": // Shared HTTP
   182  		host := strings.Split(routeWithoutPathAndPort, ".")[0]
   183  		routeWithoutPathAndPort = fmt.Sprintf("%s.%s", host, flagDomain.Name)
   184  	default:
   185  		routeWithoutPathAndPort = flagDomain.Name
   186  	}
   187  
   188  	return routeWithoutPathAndPort, nil
   189  }
   190  
   191  func (routeActor routeActor) FindAndBindRoute(routeName string, app models.Application, appParamsFromContext models.AppParams) error {
   192  	routeWithoutPath, path := routeActor.FindPath(routeName)
   193  
   194  	routeWithoutPathAndPort, port, err := routeActor.FindPort(routeWithoutPath)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	if len(appParamsFromContext.Domains) == 1 {
   200  		routeWithoutPathAndPort, err = routeActor.replaceDomain(routeWithoutPathAndPort, appParamsFromContext.Domains[0])
   201  		if err != nil {
   202  			return err
   203  		}
   204  	}
   205  
   206  	hostname, domain, err := routeActor.FindDomain(routeWithoutPathAndPort)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	if appParamsFromContext.RoutePath != nil && *appParamsFromContext.RoutePath != "" && domain.RouterGroupType != tcp {
   212  		path = *appParamsFromContext.RoutePath
   213  	}
   214  
   215  	if appParamsFromContext.UseRandomRoute && domain.RouterGroupType != tcp {
   216  		hostname = randomword.NewGenerator().Babble()
   217  	}
   218  
   219  	replaceHostname(domain.RouterGroupType, appParamsFromContext.Hosts, &hostname)
   220  
   221  	err = validateRoute(domain.Name, domain.RouterGroupType, port, path)
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	route, err := routeActor.FindOrCreateRoute(hostname, domain, path, port, appParamsFromContext.UseRandomRoute)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	return routeActor.BindRoute(app, route)
   232  }
   233  
   234  func validateRoute(routeName string, domainType string, port int, path string) error {
   235  	if domainType == tcp && path != "" {
   236  		return fmt.Errorf(T("Path not allowed in TCP route {{.RouteName}}",
   237  			map[string]interface{}{
   238  				"RouteName": routeName,
   239  			},
   240  		))
   241  	}
   242  
   243  	if domainType == "" && port != 0 {
   244  		return fmt.Errorf(T("Port not allowed in HTTP route {{.RouteName}}",
   245  			map[string]interface{}{
   246  				"RouteName": routeName,
   247  			},
   248  		))
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  func replaceHostname(domainType string, hosts []string, hostname *string) {
   255  	if domainType == "" && len(hosts) > 0 && hosts[0] != "" {
   256  		*hostname = hosts[0]
   257  	}
   258  }
   259  
   260  func validateFoundDomain(domain models.DomainFields, err error) (bool, error) {
   261  	switch err.(type) {
   262  	case *errors.ModelNotFoundError:
   263  		return false, nil
   264  	case nil:
   265  		return true, nil
   266  	default:
   267  		return false, err
   268  	}
   269  }
   270  
   271  func parseRoute(routeName string, findFunc func(domainName string) (models.DomainFields, error)) (string, models.DomainFields, bool, error) {
   272  	domain, err := findFunc(routeName)
   273  	found, err := validateFoundDomain(domain, err)
   274  	if err != nil {
   275  		return "", models.DomainFields{}, false, err
   276  	}
   277  	if found {
   278  		return "", domain, false, nil
   279  	}
   280  
   281  	routeParts := strings.Split(routeName, ".")
   282  	domain, err = findFunc(strings.Join(routeParts[1:], "."))
   283  	found, err = validateFoundDomain(domain, err)
   284  	if err != nil {
   285  		return "", models.DomainFields{}, false, err
   286  	}
   287  	if found {
   288  		return routeParts[0], domain, false, nil
   289  	}
   290  
   291  	return "", models.DomainFields{}, true, fmt.Errorf(T(
   292  		"The route {{.RouteName}} did not match any existing domains.",
   293  		map[string]interface{}{
   294  			"RouteName": routeName,
   295  		},
   296  	))
   297  }