github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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 }