package io.pivotal.services.plugin; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.applications.DecomposedRoute; import org.cloudfoundry.operations.applications.DomainSummary; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Comparator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author Gabriel Couto * @author Biju Kunjummen */ public class CfRouteUtil { /** * Returns a list of decomposed routes * * @param cfOperations the operations to check the domain names against * @param routes all aplication's routes * @param routePath the application path to be included in the routes * @return the decomposed routes */ public static Mono<List<DecomposedRoute>> decomposedRoutes(CloudFoundryOperations cfOperations, List<String> routes, String routePath) { final Mono<List<DomainSummary>> domainSummariesMono = fetchDomainSummaries(cfOperations); List<Mono<DecomposedRoute>> decomposedRoutes = routes.stream().map(route -> domainSummariesMono.flatMap(domainSummaries -> decomposeRoute(domainSummaries, route, routePath)) ).collect(Collectors.toList()); return Flux.merge(decomposedRoutes).collectList(); } /** * Get the first route and generate a temp route from it, adding the suffix * after the hostname. * * @param cfOperations the operations to check the domain names against * @param cfProperties the properties of the app * @param suffix the suffix to add in the host * @return the calculated route */ public static Mono<String> getTempRoute(CloudFoundryOperations cfOperations, CfProperties cfProperties, String suffix) { if (cfProperties.routes() == null || cfProperties.routes().isEmpty()) return getTempRoute(cfProperties.host(), cfProperties.domain(), null, cfProperties.path(), suffix); Mono<DecomposedRoute> routeMono = fetchDomainSummaries(cfOperations).flatMap(domainSummaries -> decomposeRoute(domainSummaries, cfProperties.routes().get(0), cfProperties.path())); return routeMono.flatMap(route -> getTempRoute(route.getHost(), route.getDomain(), route.getPort(), route.getPath(), suffix)); } private static Mono<String> getTempRoute(String host, String domain, Integer port, String path, String suffix) { return Mono.just((host != null ? host + "-" + suffix + "." : suffix + ".") + domain + (port != null ? ":" + port : "") + (path != null ? "/" + path : "")); } public static Mono<List<DomainSummary>> fetchDomainSummaries(CloudFoundryOperations cfOperations) { return cfOperations.domains().list() .map(domain -> DomainSummary.builder() .id(domain.getId()) .name(domain.getName()) .type(domain.getType()) .build()) .collectList(); } /** * Copy of org.cloudfoundry.operations.applications.RouteUtil.decomposeRoute */ private static Mono<DecomposedRoute> decomposeRoute(List<DomainSummary> availableDomains, String route, String routePath) { String domain = null; String host = null; String path = null; Integer port = null; String routeWithoutSuffix = route; if (availableDomains.size() == 0) { throw new IllegalArgumentException(String.format("The route %s did not match any existing domains", route)); } List<DomainSummary> sortedDomains = availableDomains.stream() .sorted(Comparator.<DomainSummary>comparingInt(domainSummary -> domainSummary.getName().length()).reversed()) .collect(Collectors.toList()); if (route.contains("/")) { int index = route.indexOf("/"); path = routePath != null ? routePath : route.substring(index); routeWithoutSuffix = route.substring(0, index); } else if (hasPort(route)) { port = getPort(route); routeWithoutSuffix = route.substring(0, route.indexOf(":")); } for (DomainSummary item : sortedDomains) { if (isDomainMatch(routeWithoutSuffix, item.getName())) { domain = item.getName(); if (domain.length() < routeWithoutSuffix.length()) { host = routeWithoutSuffix.substring(0, routeWithoutSuffix.lastIndexOf(domain) - 1); } break; } } if (domain == null) { throw new IllegalArgumentException(String.format("The route %s did not match any existing domains", route)); } if ((host != null || path != null) && port != null) { throw new IllegalArgumentException(String.format("The route %s is invalid: Host/path cannot be set with port", route)); } return Mono.just(DecomposedRoute.builder() .domain(domain) .host(host) .path(path) .port(port) .build()); } private static Integer getPort(String route) { Pattern pattern = Pattern.compile(":\\d+$"); Matcher matcher = pattern.matcher(route); matcher.find(); return Integer.valueOf(route.substring(matcher.start() + 1, matcher.end())); } private static Boolean hasPort(String route) { Pattern pattern = Pattern.compile("^.+?:\\d+$"); Matcher matcher = pattern.matcher(route); return matcher.matches(); } private static boolean isDomainMatch(String route, String domain) { return route.equals(domain) || route.endsWith(domain) && route.charAt(route.length() - domain.length() - 1) == '.'; } }