github.com/cloudfoundry/cli@v7.1.0+incompatible/actor/v2action/route.go (about)

     1  package v2action
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"strings"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/constant"
    12  	"code.cloudfoundry.org/cli/types"
    13  	log "github.com/sirupsen/logrus"
    14  )
    15  
    16  type Routes []Route
    17  
    18  // Summary converts routes into a comma separated string.
    19  func (rs Routes) Summary() string {
    20  	formattedRoutes := []string{}
    21  	for _, route := range rs {
    22  		formattedRoutes = append(formattedRoutes, route.String())
    23  	}
    24  	return strings.Join(formattedRoutes, ", ")
    25  }
    26  
    27  // Route represents a CLI Route.
    28  type Route struct {
    29  	Domain    Domain
    30  	GUID      string
    31  	Host      string
    32  	Path      string
    33  	Port      types.NullInt
    34  	SpaceGUID string
    35  }
    36  
    37  func (r Route) RandomTCPPort() bool {
    38  	return r.Domain.IsTCP() && !r.Port.IsSet
    39  }
    40  
    41  // Validate will return an error if there are invalid HTTP or TCP settings for
    42  // it's given domain.
    43  func (r Route) Validate() error {
    44  	if r.Domain.IsHTTP() {
    45  		if r.Port.IsSet {
    46  			return actionerror.InvalidHTTPRouteSettings{Domain: r.Domain.Name}
    47  		}
    48  		if r.Domain.IsShared() && r.Host == "" {
    49  			return actionerror.NoHostnameAndSharedDomainError{}
    50  		}
    51  	} else { // Is TCP Domain
    52  		if r.Host != "" || r.Path != "" {
    53  			return actionerror.InvalidTCPRouteSettings{Domain: r.Domain.Name}
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  // ValidateWithRandomPort will return an error if a random port is requested
    60  // for an HTTP route, or if the route is TCP, a random port wasn't requested,
    61  // and the route has no port set.
    62  func (r Route) ValidateWithRandomPort(randomPort bool) error {
    63  	if r.Domain.IsHTTP() && randomPort {
    64  		return actionerror.InvalidHTTPRouteSettings{Domain: r.Domain.Name}
    65  	}
    66  
    67  	if r.Domain.IsTCP() && !r.Port.IsSet && !randomPort {
    68  		return actionerror.TCPRouteOptionsNotProvidedError{}
    69  	}
    70  	return r.Validate()
    71  }
    72  
    73  // String formats the route in a human readable format.
    74  func (r Route) String() string {
    75  	routeString := r.Domain.Name
    76  
    77  	if r.Port.IsSet {
    78  		routeString = fmt.Sprintf("%s:%d", routeString, r.Port.Value)
    79  	} else if r.RandomTCPPort() {
    80  		routeString = fmt.Sprintf("%s:????", routeString)
    81  	}
    82  
    83  	if r.Host != "" {
    84  		routeString = fmt.Sprintf("%s.%s", r.Host, routeString)
    85  	}
    86  
    87  	if r.Path != "" {
    88  		routeString = path.Join(routeString, r.Path)
    89  	}
    90  
    91  	return routeString
    92  }
    93  
    94  func (actor Actor) MapRouteToApplication(routeGUID string, appGUID string) (Warnings, error) {
    95  	_, warnings, err := actor.CloudControllerClient.UpdateRouteApplication(routeGUID, appGUID)
    96  	if _, ok := err.(ccerror.InvalidRelationError); ok {
    97  		return Warnings(warnings), actionerror.RouteInDifferentSpaceError{}
    98  	}
    99  	return Warnings(warnings), err
   100  }
   101  
   102  func (actor Actor) UnmapRouteFromApplication(routeGUID string, appGUID string) (Warnings, error) {
   103  	warnings, err := actor.CloudControllerClient.DeleteRouteApplication(routeGUID, appGUID)
   104  	return Warnings(warnings), err
   105  }
   106  
   107  func (actor Actor) CreateRoute(route Route, generatePort bool) (Route, Warnings, error) {
   108  	if route.Path != "" && !strings.HasPrefix(route.Path, "/") {
   109  		route.Path = fmt.Sprintf("/%s", route.Path)
   110  	}
   111  	returnedRoute, warnings, err := actor.CloudControllerClient.CreateRoute(ActorToCCRoute(route), generatePort)
   112  	return CCToActorRoute(returnedRoute, route.Domain), Warnings(warnings), err
   113  }
   114  
   115  func (actor Actor) CreateRouteWithExistenceCheck(orgGUID string, spaceName string, route Route, generatePort bool) (Route, Warnings, error) {
   116  	space, warnings, spaceErr := actor.GetSpaceByOrganizationAndName(orgGUID, spaceName)
   117  	if spaceErr != nil {
   118  		return Route{}, Warnings(warnings), spaceErr
   119  	}
   120  	route.SpaceGUID = space.GUID
   121  
   122  	if route.Domain.GUID == "" {
   123  		domains, orgDomainWarnings, getDomainErr := actor.GetDomainsByNameAndOrganization([]string{route.Domain.Name}, orgGUID)
   124  		warnings = append(warnings, orgDomainWarnings...)
   125  		if getDomainErr != nil {
   126  			return Route{}, warnings, getDomainErr
   127  		} else if len(domains) == 0 {
   128  			return Route{}, warnings, actionerror.DomainNotFoundError{Name: route.Domain.Name}
   129  		}
   130  		route.Domain.GUID = domains[0].GUID
   131  		route.Domain.RouterGroupType = domains[0].RouterGroupType
   132  	}
   133  
   134  	validateErr := route.ValidateWithRandomPort(generatePort)
   135  	if validateErr != nil {
   136  		return Route{}, Warnings(warnings), validateErr
   137  	}
   138  
   139  	if !generatePort {
   140  		foundRoute, spaceRouteWarnings, findErr := actor.FindRouteBoundToSpaceWithSettings(route)
   141  		warnings = append(warnings, spaceRouteWarnings...)
   142  		routeAlreadyExists := true
   143  		if _, ok := findErr.(actionerror.RouteNotFoundError); ok {
   144  			routeAlreadyExists = false
   145  		} else if findErr != nil {
   146  			return Route{}, Warnings(warnings), findErr
   147  		}
   148  
   149  		if routeAlreadyExists {
   150  			return Route{}, Warnings(warnings), actionerror.RouteAlreadyExistsError{Route: foundRoute.String()}
   151  		}
   152  	}
   153  
   154  	createdRoute, createRouteWarnings, createErr := actor.CreateRoute(route, generatePort)
   155  	warnings = append(warnings, createRouteWarnings...)
   156  	if createErr != nil {
   157  		return Route{}, Warnings(warnings), createErr
   158  	}
   159  
   160  	return createdRoute, Warnings(warnings), nil
   161  }
   162  
   163  // GetOrphanedRoutesBySpace returns a list of orphaned routes associated with
   164  // the provided Space GUID.
   165  func (actor Actor) GetOrphanedRoutesBySpace(spaceGUID string) ([]Route, Warnings, error) {
   166  	var (
   167  		orphanedRoutes []Route
   168  		allWarnings    Warnings
   169  	)
   170  
   171  	routes, warnings, err := actor.GetSpaceRoutes(spaceGUID)
   172  	allWarnings = append(allWarnings, warnings...)
   173  	if err != nil {
   174  		return nil, allWarnings, err
   175  	}
   176  
   177  	for _, route := range routes {
   178  		apps, warnings, err := actor.GetRouteApplications(route.GUID)
   179  		allWarnings = append(allWarnings, warnings...)
   180  		if err != nil {
   181  			return nil, allWarnings, err
   182  		}
   183  
   184  		if len(apps) == 0 {
   185  			orphanedRoutes = append(orphanedRoutes, route)
   186  		}
   187  	}
   188  
   189  	if len(orphanedRoutes) == 0 {
   190  		return nil, allWarnings, actionerror.OrphanedRoutesNotFoundError{}
   191  	}
   192  
   193  	return orphanedRoutes, allWarnings, nil
   194  }
   195  
   196  // GetApplicationRoutes returns a list of routes associated with the provided
   197  // Application GUID.
   198  func (actor Actor) GetApplicationRoutes(applicationGUID string) (Routes, Warnings, error) {
   199  	var allWarnings Warnings
   200  	ccv2Routes, warnings, err := actor.CloudControllerClient.GetApplicationRoutes(applicationGUID)
   201  	allWarnings = append(allWarnings, warnings...)
   202  	if err != nil {
   203  		return nil, allWarnings, err
   204  	}
   205  
   206  	routes, domainWarnings, err := actor.applyDomain(ccv2Routes)
   207  
   208  	return routes, append(allWarnings, domainWarnings...), err
   209  }
   210  
   211  // GetSpaceRoutes returns a list of routes associated with the provided Space
   212  // GUID.
   213  func (actor Actor) GetSpaceRoutes(spaceGUID string) ([]Route, Warnings, error) {
   214  	var allWarnings Warnings
   215  	ccv2Routes, warnings, err := actor.CloudControllerClient.GetSpaceRoutes(spaceGUID)
   216  	allWarnings = append(allWarnings, warnings...)
   217  	if err != nil {
   218  		return nil, allWarnings, err
   219  	}
   220  
   221  	routes, domainWarnings, err := actor.applyDomain(ccv2Routes)
   222  
   223  	return routes, append(allWarnings, domainWarnings...), err
   224  }
   225  
   226  // DeleteUnmappedRoutes deletes the unmapped routes associated with the provided Space GUID.
   227  func (actor Actor) DeleteUnmappedRoutes(spaceGUID string) (Warnings, error) {
   228  	warnings, err := actor.CloudControllerClient.DeleteSpaceUnmappedRoutes(spaceGUID)
   229  	return Warnings(warnings), err
   230  }
   231  
   232  // DeleteRoute deletes the Route associated with the provided Route GUID.
   233  func (actor Actor) DeleteRoute(routeGUID string) (Warnings, error) {
   234  	warnings, err := actor.CloudControllerClient.DeleteRoute(routeGUID)
   235  	return Warnings(warnings), err
   236  }
   237  
   238  func (actor Actor) CheckRoute(route Route) (bool, Warnings, error) {
   239  	exists, warnings, err := actor.CloudControllerClient.CheckRoute(ActorToCCRoute(route))
   240  	return exists, Warnings(warnings), err
   241  }
   242  
   243  // FindRouteBoundToSpaceWithSettings finds the route with the given host,
   244  // domain and space. If it is unable to find the route, it will check if it
   245  // exists anywhere in the system. When the route exists in another space,
   246  // RouteInDifferentSpaceError is returned. Use this when you know the space
   247  // GUID.
   248  func (actor Actor) FindRouteBoundToSpaceWithSettings(route Route) (Route, Warnings, error) {
   249  	existingRoute, warnings, err := actor.GetRouteByComponents(route)
   250  	if routeNotFoundErr, ok := err.(actionerror.RouteNotFoundError); ok {
   251  		// This check only works for API versions 2.55 or higher. It will return
   252  		// false for anything below that.
   253  		log.Infof("checking route existence for: %s\n", route)
   254  		exists, checkRouteWarnings, chkErr := actor.CheckRoute(route)
   255  		if chkErr != nil {
   256  			log.Errorln("check route:", err)
   257  			return Route{}, append(Warnings(warnings), checkRouteWarnings...), chkErr
   258  		}
   259  
   260  		// This will happen if the route exists in a space to which the user does
   261  		// not have access.
   262  		if exists {
   263  			log.Errorf("unable to find route %s in current space", route.String())
   264  			return Route{}, append(Warnings(warnings), checkRouteWarnings...), actionerror.RouteInDifferentSpaceError{Route: route.String()}
   265  		}
   266  
   267  		log.Warnf("negative existence check for route %s - returning partial route", route.String())
   268  		log.Debugf("partialRoute: %#v", route)
   269  		return Route{}, append(Warnings(warnings), checkRouteWarnings...), routeNotFoundErr
   270  	} else if err != nil {
   271  		log.Errorln("finding route:", err)
   272  		return Route{}, Warnings(warnings), err
   273  	}
   274  
   275  	if existingRoute.SpaceGUID != route.SpaceGUID {
   276  		log.WithFields(log.Fields{
   277  			"targeted_space_guid": route.SpaceGUID,
   278  			"existing_space_guid": existingRoute.SpaceGUID,
   279  		}).Errorf("route exists in different space the user has access to")
   280  		return Route{}, Warnings(warnings), actionerror.RouteInDifferentSpaceError{Route: route.String()}
   281  	}
   282  
   283  	log.Debugf("found route: %#v", existingRoute)
   284  	return existingRoute, Warnings(warnings), err
   285  }
   286  
   287  // GetRouteByComponents returns the route with the matching host, domain, path,
   288  // and port. Use this when you don't know the space GUID.
   289  // TCP routes require a port to be uniquely identified
   290  // HTTP routes using shared domains require a hostname or path to be uniquely identified
   291  func (actor Actor) GetRouteByComponents(route Route) (Route, Warnings, error) {
   292  	// TODO: validation should probably be done separately (?)
   293  	if route.Domain.IsTCP() && !route.Port.IsSet {
   294  		return Route{}, nil, actionerror.PortNotProvidedForQueryError{}
   295  	}
   296  
   297  	if route.Domain.IsShared() && route.Domain.IsHTTP() && route.Host == "" {
   298  		return Route{}, nil, actionerror.NoHostnameAndSharedDomainError{}
   299  	}
   300  
   301  	queries := []ccv2.Filter{
   302  		{
   303  			Type:     constant.DomainGUIDFilter,
   304  			Operator: constant.EqualOperator,
   305  			Values:   []string{route.Domain.GUID},
   306  		}, {
   307  			Type:     constant.HostFilter,
   308  			Operator: constant.EqualOperator,
   309  			Values:   []string{route.Host},
   310  		}, {
   311  			Type:     constant.PathFilter,
   312  			Operator: constant.EqualOperator,
   313  			Values:   []string{route.Path},
   314  		},
   315  	}
   316  
   317  	if route.Port.IsSet {
   318  		queries = append(queries, ccv2.Filter{
   319  			Type:     constant.PortFilter,
   320  			Operator: constant.EqualOperator,
   321  			Values:   []string{fmt.Sprint(route.Port.Value)},
   322  		})
   323  	}
   324  
   325  	ccv2Routes, warnings, err := actor.CloudControllerClient.GetRoutes(queries...)
   326  	if err != nil {
   327  		return Route{}, Warnings(warnings), err
   328  	}
   329  
   330  	if len(ccv2Routes) == 0 {
   331  		return Route{}, Warnings(warnings), actionerror.RouteNotFoundError{
   332  			Host:       route.Host,
   333  			DomainGUID: route.Domain.GUID,
   334  			Path:       route.Path,
   335  			Port:       route.Port.Value,
   336  		}
   337  	}
   338  
   339  	return CCToActorRoute(ccv2Routes[0], route.Domain), Warnings(warnings), err
   340  }
   341  
   342  func ActorToCCRoute(route Route) ccv2.Route {
   343  	return ccv2.Route{
   344  		DomainGUID: route.Domain.GUID,
   345  		GUID:       route.GUID,
   346  		Host:       route.Host,
   347  		Path:       route.Path,
   348  		Port:       route.Port,
   349  		SpaceGUID:  route.SpaceGUID,
   350  	}
   351  }
   352  
   353  func CCToActorRoute(ccv2Route ccv2.Route, domain Domain) Route {
   354  	return Route{
   355  		Domain:    domain,
   356  		GUID:      ccv2Route.GUID,
   357  		Host:      ccv2Route.Host,
   358  		Path:      ccv2Route.Path,
   359  		Port:      ccv2Route.Port,
   360  		SpaceGUID: ccv2Route.SpaceGUID,
   361  	}
   362  }
   363  
   364  func (actor Actor) applyDomain(ccv2Routes []ccv2.Route) (Routes, Warnings, error) {
   365  	var routes Routes
   366  	var allWarnings Warnings
   367  
   368  	for _, ccv2Route := range ccv2Routes {
   369  		domain, warnings, err := actor.GetDomain(ccv2Route.DomainGUID)
   370  		allWarnings = append(allWarnings, warnings...)
   371  		if err != nil {
   372  			return nil, allWarnings, err
   373  		}
   374  		routes = append(routes, CCToActorRoute(ccv2Route, domain))
   375  	}
   376  
   377  	return routes, allWarnings, nil
   378  }