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