github.com/liamawhite/cli-with-i18n@v6.32.1-0.20171122084555-dede0a5c3448+incompatible/actor/pushaction/route.go (about) 1 package pushaction 2 3 import ( 4 "fmt" 5 "net/url" 6 "strings" 7 8 "github.com/liamawhite/cli-with-i18n/actor/actionerror" 9 "github.com/liamawhite/cli-with-i18n/actor/v2action" 10 "github.com/liamawhite/cli-with-i18n/types" 11 log "github.com/Sirupsen/logrus" 12 ) 13 14 func (actor Actor) BindRoutes(config ApplicationConfig) (ApplicationConfig, bool, Warnings, error) { 15 log.Info("binding routes") 16 17 var boundRoutes bool 18 var allWarnings Warnings 19 20 for _, route := range config.DesiredRoutes { 21 if !actor.routeInListByGUID(route, config.CurrentRoutes) { 22 log.Debugf("binding route: %#v", route) 23 warnings, err := actor.bindRouteToApp(route, config.DesiredApplication.GUID) 24 allWarnings = append(allWarnings, warnings...) 25 if err != nil { 26 log.Errorln("binding route:", err) 27 return ApplicationConfig{}, false, allWarnings, err 28 } 29 boundRoutes = true 30 } else { 31 log.Debugf("route %s already bound to app", route) 32 } 33 } 34 log.Debug("binding routes complete") 35 config.CurrentRoutes = config.DesiredRoutes 36 37 return config, boundRoutes, allWarnings, nil 38 } 39 40 func (actor Actor) CalculateRoutes(routes []string, orgGUID string, spaceGUID string, existingRoutes []v2action.Route) ([]v2action.Route, Warnings, error) { 41 calculatedRoutes, unknownRoutes := actor.spitExistingRoutes(existingRoutes, routes) 42 possibleDomains, err := actor.generatePossibleDomains(unknownRoutes) 43 if err != nil { 44 log.Errorln("domain breakdown:", err) 45 return nil, nil, err 46 } 47 48 var allWarnings Warnings 49 foundDomains, warnings, err := actor.V2Actor.GetDomainsByNameAndOrganization(possibleDomains, orgGUID) 50 allWarnings = append(allWarnings, warnings...) 51 if err != nil { 52 log.Errorln("domain lookup:", err) 53 return nil, allWarnings, err 54 } 55 nameToFoundDomain := map[string]v2action.Domain{} 56 for _, foundDomain := range foundDomains { 57 log.WithField("domain", foundDomain.Name).Debug("found domain") 58 nameToFoundDomain[foundDomain.Name] = foundDomain 59 } 60 61 for _, route := range unknownRoutes { 62 log.WithField("route", route).Debug("generating route") 63 64 root, port, path, parseErr := actor.parseURL(route) 65 if parseErr != nil { 66 log.Errorln("parse route:", parseErr) 67 return nil, allWarnings, parseErr 68 } 69 70 host, domain, domainErr := actor.calculateRoute(root, nameToFoundDomain) 71 if _, ok := domainErr.(v2action.DomainNotFoundError); ok { 72 log.Error("no matching domains") 73 return nil, allWarnings, actionerror.NoMatchingDomainError{Route: route} 74 } else if domainErr != nil { 75 log.Errorln("matching domains:", domainErr) 76 return nil, allWarnings, domainErr 77 } 78 79 // TODO: redo once TCP routing has been completed 80 if port.IsSet && domain.IsHTTP() { 81 return nil, allWarnings, actionerror.InvalidHTTPRouteSettings{Domain: domain.Name} 82 } 83 84 calculatedRoute, routeWarnings, routeErr := actor.findOrReturnPartialRouteWithSettings(v2action.Route{ 85 Host: strings.Join(host, "."), 86 Domain: domain, 87 Path: path, 88 Port: port, 89 SpaceGUID: spaceGUID, 90 }) 91 allWarnings = append(allWarnings, routeWarnings...) 92 if routeErr != nil { 93 log.Errorln("route lookup:", routeErr) 94 return nil, allWarnings, routeErr 95 } 96 97 calculatedRoutes = append(calculatedRoutes, calculatedRoute) 98 } 99 100 return calculatedRoutes, allWarnings, nil 101 } 102 103 func (actor Actor) CreateAndBindApplicationRoutes(orgGUID string, spaceGUID string, app v2action.Application) (Warnings, error) { 104 var warnings Warnings 105 defaultRoute, domainWarnings, err := actor.getDefaultRoute(orgGUID, spaceGUID, app.Name) 106 warnings = append(warnings, domainWarnings...) 107 if err != nil { 108 return warnings, err 109 } 110 111 boundRoutes, appRouteWarnings, err := actor.V2Actor.GetApplicationRoutes(app.GUID) 112 warnings = append(warnings, appRouteWarnings...) 113 if err != nil { 114 return warnings, err 115 } 116 117 _, routeAlreadyBound := actor.routeInListBySettings(defaultRoute, boundRoutes) 118 if routeAlreadyBound { 119 return warnings, err 120 } 121 122 spaceRoute, spaceRouteWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 123 warnings = append(warnings, spaceRouteWarnings...) 124 routeAlreadyExists := true 125 if _, ok := err.(v2action.RouteNotFoundError); ok { 126 routeAlreadyExists = false 127 } else if err != nil { 128 return warnings, err 129 } 130 131 if !routeAlreadyExists { 132 var createRouteWarning v2action.Warnings 133 spaceRoute, createRouteWarning, err = actor.V2Actor.CreateRoute(defaultRoute, false) 134 warnings = append(warnings, createRouteWarning...) 135 if err != nil { 136 return warnings, err 137 } 138 } 139 140 bindWarnings, err := actor.V2Actor.BindRouteToApplication(spaceRoute.GUID, app.GUID) 141 warnings = append(warnings, bindWarnings...) 142 return warnings, err 143 } 144 145 func (actor Actor) CreateRoutes(config ApplicationConfig) (ApplicationConfig, bool, Warnings, error) { 146 log.Info("creating routes") 147 148 var routes []v2action.Route 149 var createdRoutes bool 150 var allWarnings Warnings 151 152 for _, route := range config.DesiredRoutes { 153 if route.GUID == "" { 154 log.WithField("route", route).Debug("creating route") 155 156 createdRoute, warnings, err := actor.V2Actor.CreateRoute(route, false) 157 allWarnings = append(allWarnings, warnings...) 158 if err != nil { 159 log.Errorln("creating route:", err) 160 return ApplicationConfig{}, true, allWarnings, err 161 } 162 routes = append(routes, createdRoute) 163 164 createdRoutes = true 165 } else { 166 log.WithField("route", route).Debug("already exists, skipping") 167 routes = append(routes, route) 168 } 169 } 170 config.DesiredRoutes = routes 171 172 return config, createdRoutes, allWarnings, nil 173 } 174 175 // GetRouteWithDefaultDomain returns a route with the host and the default org 176 // domain. This may be a partial route (ie no GUID) if the route does not 177 // exist. 178 func (actor Actor) GetRouteWithDefaultDomain(host string, orgGUID string, spaceGUID string, knownRoutes []v2action.Route) (v2action.Route, Warnings, error) { 179 defaultDomain, warnings, err := actor.DefaultDomain(orgGUID) 180 if err != nil { 181 log.Errorln("could not find default domains:", err.Error()) 182 return v2action.Route{}, warnings, err 183 } 184 185 defaultRoute := v2action.Route{ 186 Domain: defaultDomain, 187 Host: strings.ToLower(host), 188 SpaceGUID: spaceGUID, 189 } 190 191 cachedRoute, found := actor.routeInListBySettings(defaultRoute, knownRoutes) 192 if !found { 193 route, routeWarnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(defaultRoute) 194 if _, ok := err.(v2action.RouteNotFoundError); ok { 195 return defaultRoute, append(warnings, routeWarnings...), nil 196 } 197 return route, append(warnings, routeWarnings...), err 198 } 199 return cachedRoute, warnings, nil 200 } 201 202 func (actor Actor) bindRouteToApp(route v2action.Route, appGUID string) (v2action.Warnings, error) { 203 warnings, err := actor.V2Actor.BindRouteToApplication(route.GUID, appGUID) 204 if _, ok := err.(v2action.RouteInDifferentSpaceError); ok { 205 return warnings, v2action.RouteInDifferentSpaceError{Route: route.String()} 206 } 207 return warnings, err 208 } 209 210 func (actor Actor) calculateRoute(route string, domainCache map[string]v2action.Domain) ([]string, v2action.Domain, error) { 211 host, domain := actor.splitHost(route) 212 if domain, ok := domainCache[route]; ok { 213 return nil, domain, nil 214 } 215 216 if host == "" { 217 return nil, v2action.Domain{}, v2action.DomainNotFoundError{Name: route} 218 } 219 220 hosts, foundDomain, err := actor.calculateRoute(domain, domainCache) 221 hosts = append([]string{host}, hosts...) 222 223 return hosts, foundDomain, err 224 } 225 226 func (actor Actor) findOrReturnPartialRouteWithSettings(route v2action.Route) (v2action.Route, Warnings, error) { 227 cachedRoute, warnings, err := actor.V2Actor.FindRouteBoundToSpaceWithSettings(route) 228 if _, ok := err.(v2action.RouteNotFoundError); ok { 229 return route, Warnings(warnings), nil 230 } 231 return cachedRoute, Warnings(warnings), err 232 } 233 234 func (actor Actor) generatePossibleDomains(routes []string) ([]string, error) { 235 var hostnames []string 236 for _, route := range routes { 237 host, _, _, err := actor.parseURL(route) 238 if err != nil { 239 return nil, err 240 } 241 hostnames = append(hostnames, host) 242 } 243 244 possibleDomains := map[string]interface{}{} 245 for _, route := range hostnames { 246 count := strings.Count(route, ".") 247 domains := strings.SplitN(route, ".", count) 248 249 for i := range domains { 250 domain := strings.Join(domains[i:], ".") 251 possibleDomains[domain] = nil 252 } 253 } 254 255 var domains []string 256 for domain := range possibleDomains { 257 domains = append(domains, domain) 258 } 259 260 log.Debugln("domain brakedown:", strings.Join(domains, ",")) 261 return domains, nil 262 } 263 264 func (actor Actor) getDefaultRoute(orgGUID string, spaceGUID string, appName string) (v2action.Route, Warnings, error) { 265 defaultDomain, domainWarnings, err := actor.DefaultDomain(orgGUID) 266 if err != nil { 267 return v2action.Route{}, domainWarnings, err 268 } 269 270 return v2action.Route{ 271 Host: appName, 272 Domain: defaultDomain, 273 SpaceGUID: spaceGUID, 274 }, domainWarnings, nil 275 } 276 277 func (Actor) parseURL(route string) (string, types.NullInt, string, error) { 278 if !(strings.HasPrefix(route, "http://") || strings.HasPrefix(route, "https://")) { 279 route = fmt.Sprintf("http://%s", route) 280 } 281 parsedURL, err := url.Parse(route) 282 if err != nil { 283 return "", types.NullInt{}, "", err 284 } 285 286 path := parsedURL.RequestURI() 287 if path == "/" { 288 path = "" 289 } 290 291 var port types.NullInt 292 err = port.ParseStringValue(parsedURL.Port()) 293 return parsedURL.Hostname(), port, path, err 294 } 295 296 func (Actor) routeInListByGUID(route v2action.Route, routes []v2action.Route) bool { 297 for _, r := range routes { 298 if r.GUID == route.GUID { 299 return true 300 } 301 } 302 303 return false 304 } 305 306 func (Actor) routeInListByName(route string, routes []v2action.Route) (v2action.Route, bool) { 307 for _, r := range routes { 308 if r.String() == route { 309 return r, true 310 } 311 } 312 313 return v2action.Route{}, false 314 } 315 func (Actor) routeInListBySettings(route v2action.Route, routes []v2action.Route) (v2action.Route, bool) { 316 for _, r := range routes { 317 if r.Host == route.Host && r.Path == route.Path && r.Port == route.Port && 318 r.SpaceGUID == route.SpaceGUID && r.Domain.GUID == route.Domain.GUID { 319 return r, true 320 } 321 } 322 323 return v2action.Route{}, false 324 } 325 326 func (actor Actor) spitExistingRoutes(existingRoutes []v2action.Route, routes []string) ([]v2action.Route, []string) { 327 var cachedRoutes []v2action.Route 328 for _, route := range existingRoutes { 329 cachedRoutes = append(cachedRoutes, route) 330 } 331 332 var unknownRoutes []string 333 for _, route := range routes { 334 if _, found := actor.routeInListByName(route, existingRoutes); !found { 335 log.WithField("route", route).Debug("unable to find route in cache") 336 unknownRoutes = append(unknownRoutes, route) 337 } 338 } 339 return cachedRoutes, unknownRoutes 340 } 341 342 func (Actor) splitHost(url string) (string, string) { 343 count := strings.Count(url, ".") 344 if count == 1 { 345 return "", url 346 } 347 348 split := strings.SplitN(url, ".", 2) 349 return split[0], split[1] 350 }