diff --git a/build.gradle b/build.gradle index 183a396..090c51d 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,7 @@ configure(allprojects) { ext { openServiceBrokerVersion = "3.0.0.BUILD-SNAPSHOT" + springVersion = "5.0.7.RELEASE" reactorVersion = "3.1.8.RELEASE" junitJupiterVersion = "5.2.0" assertjVersion = "3.10.0" diff --git a/spring-cloud-app-broker-autoconfigure/build.gradle b/spring-cloud-app-broker-autoconfigure/build.gradle index 6c2f84a..21205aa 100644 --- a/spring-cloud-app-broker-autoconfigure/build.gradle +++ b/spring-cloud-app-broker-autoconfigure/build.gradle @@ -25,6 +25,7 @@ dependencyManagement { dependencies { api project(":spring-cloud-app-broker-core") api project(":spring-cloud-app-broker-deployer-cloudfoundry") + api("org.springframework.boot:spring-boot-autoconfigure") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor") diff --git a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppDeployerAutoConfiguration.java b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppDeployerAutoConfiguration.java index 8c71388..1680efa 100644 --- a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppDeployerAutoConfiguration.java +++ b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppDeployerAutoConfiguration.java @@ -22,9 +22,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.cloud.appbroker.deployer.AppDeployer; import org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryAppDeployer; -import org.springframework.cloud.appbroker.deployer.cloudfoundry.NoOpAppNameGenerator; -import org.springframework.cloud.deployer.spi.cloudfoundry.AppNameGenerator; -import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties; +import org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; @@ -35,16 +33,10 @@ public class AppDeployerAutoConfiguration { @Bean @ConditionalOnBean(CloudFoundryOperations.class) - public AppDeployer cloudFoundryReactiveAppDeployer(AppNameGenerator noOpApplicationNameGenerator, - CloudFoundryOperations cloudFoundryOperations, - ResourceLoader resourceLoader) { + public AppDeployer cloudFoundryAppDeployer(CloudFoundryOperations cloudFoundryOperations, + ResourceLoader resourceLoader) { CloudFoundryDeploymentProperties cloudFoundryDeploymentProperties = new CloudFoundryDeploymentProperties(); - return new CloudFoundryAppDeployer(noOpApplicationNameGenerator, cloudFoundryDeploymentProperties, - cloudFoundryOperations, null, resourceLoader); - } - - @Bean - public AppNameGenerator noOpApplicationNameGenerator() { - return new NoOpAppNameGenerator(); + return new CloudFoundryAppDeployer(cloudFoundryDeploymentProperties, + cloudFoundryOperations, resourceLoader); } } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java index 572b3ac..585302c 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java @@ -48,7 +48,7 @@ public class DeployerClient { return appDeployer.undeploy(UndeployApplicationRequest.builder() .name(backingApplication.getName()) .build()) - .map(UndeployApplicationResponse::getStatus); + .map(UndeployApplicationResponse::getName); } } diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java index 2e91660..bb8e7a5 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java @@ -152,7 +152,7 @@ class DeployerClientTest { // given when(appDeployer.undeploy(any())) .thenReturn(Mono.just(UndeployApplicationResponse.builder() - .status("undeployed") + .name(APP_NAME) .build())); BackingApplication application = BackingApplication.builder() @@ -163,7 +163,7 @@ class DeployerClientTest { // when StepVerifier.create(deployerClient.undeploy(application)) // then - .expectNext("undeployed") + .expectNext(APP_NAME) .verifyComplete(); verify(appDeployer).undeploy(argThat(request -> APP_NAME.equals(request.getName()))); diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle b/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle index e908866..b6273f4 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle +++ b/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle @@ -18,17 +18,15 @@ description = "Spring Cloud App Broker Deployer Cloud Foundry" ext { reactorNettyVersion = '0.7.5.RELEASE' - -// webflux defaults; must be overridden for org.cloudfoundry:cloudfoundry-client-reactor:3.9.0.RELEASE -// reactorVersion = '3.1.7.RELEASE' -// reactorNettyVersion = '0.7.7.RELEASE' - - deployerCFVersion = '1.4.0.RELEASE' + cfJavaClientVersion = "3.9.0.RELEASE" } dependencies { api project(":spring-cloud-app-broker-deployer") - api("org.springframework.cloud:spring-cloud-deployer-cloudfoundry:${deployerCFVersion}") + api("org.cloudfoundry:cloudfoundry-operations:${cfJavaClientVersion}") + api("org.cloudfoundry:cloudfoundry-client-reactor:${cfJavaClientVersion}") + api("org.cloudfoundry:cloudfoundry-util:${cfJavaClientVersion}") + // fix the Boot version to prevent 1.5.x and 2.0.x on the classpath implementation("org.springframework.boot:spring-boot-starter-validation:${springBootVersion}") diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/AbstractCloudFoundryAppDeployer.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/AbstractCloudFoundryAppDeployer.java deleted file mode 100644 index f981307..0000000 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/AbstractCloudFoundryAppDeployer.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2016-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.appbroker.deployer.cloudfoundry; - -import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.UnknownCloudFoundryException; -import org.cloudfoundry.util.DelayUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.Exceptions; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.deployer.spi.app.AppDeployer; -import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties; -import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; -import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo; -import org.springframework.cloud.deployer.spi.util.ByteSizeUtils; -import org.springframework.http.HttpStatus; -import org.springframework.util.StringUtils; - -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.JAVA_OPTS_PROPERTY_KEY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY; - -class AbstractCloudFoundryAppDeployer { - - private final RuntimeEnvironmentInfo runtimeEnvironmentInfo; - - final CloudFoundryDeploymentProperties deploymentProperties; - - private final Logger logger = LoggerFactory.getLogger(AbstractCloudFoundryAppDeployer.class); - - AbstractCloudFoundryAppDeployer(CloudFoundryDeploymentProperties deploymentProperties, RuntimeEnvironmentInfo runtimeEnvironmentInfo) { - this.deploymentProperties = deploymentProperties; - this.runtimeEnvironmentInfo = runtimeEnvironmentInfo; - } - - int memory(AppDeploymentRequest request) { - String withUnit = request.getDeploymentProperties() - .getOrDefault(org.springframework.cloud.deployer.spi.app.AppDeployer.MEMORY_PROPERTY_KEY, this.deploymentProperties.getMemory()); - return (int) ByteSizeUtils.parseToMebibytes(withUnit); - } - - Set servicesToBind(AppDeploymentRequest request) { - Set services = new HashSet<>(); - services.addAll(this.deploymentProperties.getServices()); - services.addAll(StringUtils.commaDelimitedListToSet(request.getDeploymentProperties().get(SERVICES_PROPERTY_KEY))); - return services; - } - - int diskQuota(AppDeploymentRequest request) { - String withUnit = request.getDeploymentProperties() - .getOrDefault(AppDeployer.DISK_PROPERTY_KEY, this.deploymentProperties.getDisk()); - return (int) ByteSizeUtils.parseToMebibytes(withUnit); - } - - String buildpack(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(BUILDPACK_PROPERTY_KEY)) - .orElse(this.deploymentProperties.getBuildpack()); - } - - String javaOpts(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(JAVA_OPTS_PROPERTY_KEY)) - .orElse(this.deploymentProperties.getJavaOpts()); - } - - Predicate isNotFoundError() { - return t -> t instanceof AbstractCloudFoundryException && ((AbstractCloudFoundryException) t).getStatusCode() == HttpStatus.NOT_FOUND.value(); - } - - /** - * Return a Docker image identifier if the application Resource is for a Docker image, or {@literal null} otherwise. - * - * @see #getApplication(AppDeploymentRequest) - */ - String getDockerImage(AppDeploymentRequest request) { - try { - String uri = request.getResource().getURI().toString(); - if (uri.startsWith("docker:")) { - return uri.substring("docker:".length()); - } else { - return null; - } - } catch (IOException e) { - throw Exceptions.propagate(e); - } - } - - /** - * Return a Path to the application Resource or {@literal null} if the request is for a Docker image. - * - * @see #getDockerImage(AppDeploymentRequest) - */ - Path getApplication(AppDeploymentRequest request) { - try { - if (!request.getResource().getURI().toString().startsWith("docker:")) { - return request.getResource().getFile().toPath(); - } else { - return null; - } - } catch (IOException e) { - throw Exceptions.propagate(e); - } - } - - /** - * Return a function usable in {@literal doOnError} constructs that will unwrap unrecognized Cloud Foundry Exceptions - * and log the text payload. - */ - protected Consumer logError(String msg) { - return e -> { - if (e instanceof UnknownCloudFoundryException) { - logger.error(msg + "\nUnknownCloudFoundryException encountered, whose payload follows:\n" + ((UnknownCloudFoundryException)e).getPayload(), e); - } else { - logger.error(msg, e); - } - }; - } - - - /** - * To be used in order to retry the status operation for an application or task. - * @param id The application id or the task id - * @param The type of status object being queried for, usually AppStatus or TaskStatus - * @return The function that executes the retry logic around for determining App or Task Status - */ - Function, Mono> statusRetry(String id) { - long statusTimeout = this.deploymentProperties.getStatusTimeout(); - long requestTimeout = Math.round(statusTimeout * 0.40); // wait 500ms with default status timeout of 2000ms - long initialRetryDelay = Math.round(statusTimeout * 0.10); // wait 200ms with status timeout of 2000ms - - if (requestTimeout < 500L) { - logger.info("Computed statusRetry Request timeout = {} ms is below 500ms minimum value. Setting to 500ms", requestTimeout); - requestTimeout = 500L; - } - final long requestTimeoutToUse = requestTimeout; - return m -> m.timeout(Duration.ofMillis(requestTimeoutToUse)) - .doOnError(e -> logger.warn(String.format("Error getting status for %s within %sms, Retrying operation.", id, requestTimeoutToUse))) - .retryWhen(DelayUtils.exponentialBackOffError( - Duration.ofMillis(initialRetryDelay), //initial retry delay - Duration.ofMillis(statusTimeout / 2), // max retry delay - Duration.ofMillis(statusTimeout)) // max total retry time - .andThen(retries -> Flux.from(retries).doOnComplete(() -> - logger.info("Successfully retried getStatus operation status [{}] for {}", id)))) - .doOnError(e -> logger.error(String.format("Retry operation on getStatus failed for %s. Max retry time %sms", id, statusTimeout))); - } - - - public RuntimeEnvironmentInfo environmentInfo() { - return runtimeEnvironmentInfo; - } -} \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java index 3b53a48..663eda7 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java @@ -16,87 +16,84 @@ package org.springframework.cloud.appbroker.deployer.cloudfoundry; +import java.io.IOException; +import java.nio.file.Path; import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.cloudfoundry.AbstractCloudFoundryException; +import org.cloudfoundry.UnknownCloudFoundryException; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.ApplicationManifest; -import org.cloudfoundry.operations.applications.ApplicationSummary; import org.cloudfoundry.operations.applications.DeleteApplicationRequest; import org.cloudfoundry.operations.applications.Docker; import org.cloudfoundry.operations.applications.GetApplicationRequest; -import org.cloudfoundry.operations.applications.InstanceDetail; import org.cloudfoundry.operations.applications.PushApplicationManifestRequest; import org.cloudfoundry.operations.applications.Route; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; -import reactor.core.publisher.Flux; +import org.springframework.cloud.appbroker.deployer.util.ByteSizeUtils; +import org.springframework.http.HttpStatus; +import reactor.core.Exceptions; import reactor.core.publisher.Mono; import org.springframework.cloud.appbroker.deployer.AppDeployer; import org.springframework.cloud.appbroker.deployer.DeployApplicationRequest; import org.springframework.cloud.appbroker.deployer.DeployApplicationResponse; -import org.springframework.cloud.appbroker.deployer.GetApplicationStatusRequest; -import org.springframework.cloud.appbroker.deployer.GetApplicationStatusResponse; import org.springframework.cloud.appbroker.deployer.UndeployApplicationRequest; import org.springframework.cloud.appbroker.deployer.UndeployApplicationResponse; -import org.springframework.cloud.deployer.spi.app.AppStatus; -import org.springframework.cloud.deployer.spi.app.DeploymentState; -import org.springframework.cloud.deployer.spi.cloudfoundry.AppNameGenerator; -import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryAppInstanceStatus; -import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties; -import org.springframework.cloud.deployer.spi.core.AppDefinition; -import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; -import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.StringUtils; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.DOMAIN_PROPERTY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_TIMEOUT_PROPERTY_KEY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.HOST_PROPERTY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.ROUTES_PROPERTY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.ROUTE_PROPERTY; -import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.USE_SPRING_APPLICATION_JSON_KEY; +import static org.springframework.cloud.appbroker.deployer.DeploymentProperties.COUNT_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.DeploymentProperties.DISK_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.DeploymentProperties.GROUP_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.DeploymentProperties.MEMORY_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.DOMAIN_PROPERTY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_TIMEOUT_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.HOST_PROPERTY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.JAVA_OPTS_PROPERTY_KEY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.ROUTES_PROPERTY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.ROUTE_PROPERTY; +import static org.springframework.cloud.appbroker.deployer.cloudfoundry.CloudFoundryDeploymentProperties.USE_SPRING_APPLICATION_JSON_KEY; -public class CloudFoundryAppDeployer extends AbstractCloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware { +public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware { private static final Logger logger = LoggerFactory.getLogger(CloudFoundryAppDeployer.class); - private static final String SPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_SERVICES_KEY = "spring.cloud.deployer.cloudfoundry.services"; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final AppNameGenerator applicationNameGenerator; + private final CloudFoundryDeploymentProperties defaultDeploymentProperties; private final CloudFoundryOperations operations; private ResourceLoader resourceLoader; - public CloudFoundryAppDeployer(AppNameGenerator applicationNameGenerator, - CloudFoundryDeploymentProperties deploymentProperties, + public CloudFoundryAppDeployer(CloudFoundryDeploymentProperties deploymentProperties, CloudFoundryOperations operations, - RuntimeEnvironmentInfo runtimeEnvironmentInfo, ResourceLoader resourceLoader) { - super(deploymentProperties, runtimeEnvironmentInfo); + this.defaultDeploymentProperties = deploymentProperties; this.operations = operations; - this.applicationNameGenerator = applicationNameGenerator; this.resourceLoader = resourceLoader; } @@ -106,180 +103,147 @@ public class CloudFoundryAppDeployer extends AbstractCloudFoundryAppDeployer imp } @Override - public Mono deploy(DeployApplicationRequest deployApplicationRequest) { - AppDeploymentRequest request = createAppDeploymentRequest(deployApplicationRequest); + public Mono deploy(DeployApplicationRequest request) { + String appName = request.getName(); + Resource appResource = getAppResource(request); + Map deploymentProperties = request.getProperties(); - logger.trace("Entered deploy: Deploying AppDeploymentRequest: AppDefinition = {}, Resource = {}, Deployment Properties = {}", - request.getDefinition(), request.getResource(), request.getDeploymentProperties()); - String deploymentId = deploymentId(request); + logger.trace("Deploying application: request={}, resource={}", + appName, appResource); - logger.trace("deploy: Pushing application"); - return pushApplication(deploymentId, request) - .timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout())) - .doOnSuccess(item -> logger.info("Successfully deployed {}", deploymentId)) + return pushApplication(request, deploymentProperties, appResource) + .timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout())) + .doOnSuccess(item -> logger.info("Successfully deployed {}", appName)) .doOnError(error -> { if (isNotFoundError().test(error)) { logger.warn("Unable to deploy application. It may have been destroyed before start completed: " + error.getMessage()); } else { - logError(String.format("Failed to deploy %s", deploymentId)).accept(error); + logError(String.format("Failed to deploy %s", appName)).accept(error); } }) .thenReturn(DeployApplicationResponse.builder() - .name(deploymentId) + .name(appName) .build()); } - private Mono pushApplication(String deploymentId, AppDeploymentRequest request) { - ApplicationManifest manifest = buildAppManifest(deploymentId, request); + private Mono pushApplication(DeployApplicationRequest request, + Map deploymentProperties, + Resource appResource) { + ApplicationManifest manifest = buildAppManifest(request, deploymentProperties, appResource); logger.debug("Pushing manifest" + manifest.toString()); return requestPushApplication( PushApplicationManifestRequest.builder() .manifest(manifest) - .stagingTimeout(this.deploymentProperties.getStagingTimeout()) - .startupTimeout(this.deploymentProperties.getStartupTimeout()) + .stagingTimeout(this.defaultDeploymentProperties.getStagingTimeout()) + .startupTimeout(this.defaultDeploymentProperties.getStartupTimeout()) .build()) - .doOnSuccess(v -> logger.info("Done uploading bits for {}", deploymentId)) - .doOnError(e -> logger.error(String.format("Error creating app %s. Exception Message %s", deploymentId, e.getMessage()))); + .doOnSuccess(v -> logger.info("Done uploading bits for {}", request.getName())) + .doOnError(e -> logger.error(String.format("Error creating app %s. Exception Message %s", + request.getName(), e.getMessage()))); } - private ApplicationManifest buildAppManifest(String deploymentId, AppDeploymentRequest request) { + private ApplicationManifest buildAppManifest(DeployApplicationRequest request, + Map deploymentProperties, + Resource appResource) { ApplicationManifest.Builder manifest = ApplicationManifest.builder() - .path(getApplication(request)) // Only one of the two is non-null - .disk(diskQuota(request)) - .environmentVariables(getEnvironmentVariables(deploymentId, request)) - .healthCheckType(healthCheck(request)) - .healthCheckHttpEndpoint(healthCheckEndpoint(request)) - .timeout(healthCheckTimeout(request)) - .instances(instances(request)) - .memory(memory(request)) - .name(deploymentId) - .noRoute(toggleNoRoute(request)) - .services(servicesToBind(request)); + .name(request.getName()) + .path(getApplication(appResource)) + .environmentVariables(getEnvironmentVariables(request.getEnvironment())) + .services(servicesToBind(request.getServices())) + .disk(diskQuota(deploymentProperties)) + .healthCheckType(healthCheck(deploymentProperties)) + .healthCheckHttpEndpoint(healthCheckEndpoint(deploymentProperties)) + .timeout(healthCheckTimeout(deploymentProperties)) + .instances(instances(deploymentProperties)) + .memory(memory(deploymentProperties)) + .noRoute(toggleNoRoute(deploymentProperties)); - Optional.ofNullable(host(request)).ifPresent(manifest::host); - Optional.ofNullable(domain(request)).ifPresent(manifest::domain); - Optional.ofNullable(routePath(request)).ifPresent(manifest::routePath); + Optional.ofNullable(host(deploymentProperties)).ifPresent(manifest::host); + Optional.ofNullable(domain(deploymentProperties)).ifPresent(manifest::domain); + Optional.ofNullable(routePath(deploymentProperties)).ifPresent(manifest::routePath); - if (route(request) != null) { - manifest.route(Route.builder().route(route(request)).build()); + if (route(deploymentProperties) != null) { + manifest.route(Route.builder().route(route(deploymentProperties)).build()); } - if (!routes(request).isEmpty()) { - Set routes = routes(request).stream() + if (!routes(deploymentProperties).isEmpty()) { + Set routes = routes(deploymentProperties).stream() .map(r -> Route.builder().route(r).build()) .collect(Collectors.toSet()); manifest.routes(routes); } - if (getDockerImage(request) != null) { - manifest.docker(Docker.builder().image(getDockerImage(request)).build()); + if (getDockerImage(appResource) != null) { + manifest.docker(Docker.builder().image(getDockerImage(appResource)).build()); } else { - manifest.buildpack(buildpack(request)); + manifest.buildpack(buildpack(deploymentProperties)); } return manifest.build(); } - - private DeploymentState mapShallowAppState(ApplicationSummary applicationSummary) { - if (applicationSummary.getRunningInstances().equals(applicationSummary.getInstances())) { - return DeploymentState.deployed; - } - else if (applicationSummary.getInstances() > 0) { - return DeploymentState.partial; - } else { - return DeploymentState.undeployed; - } - } - - @Override - public Mono status(GetApplicationStatusRequest request) { - return getStatus(request.getName()) - .doOnSuccess(v -> logger.info("Successfully computed status [{}] for {}", v, request.getName())) - .doOnError(logError(String.format("Failed to compute status for %s", request.getName()))) - .timeout(Duration.ofMillis(this.deploymentProperties.getStatusTimeout())) - .onErrorResume(t -> { - logger.error("Caught exception while querying for status of {}", request.getName(), t); - return Mono.just(createErrorAppStatus(request.getName())); - }) - .flatMap(status -> Mono.just(GetApplicationStatusResponse.builder() - .deploymentId(status.getDeploymentId()) - .status(status.toString()) - .build())); + private Mono requestPushApplication(PushApplicationManifestRequest request) { + return this.operations.applications() + .pushManifest(request); } @Override public Mono undeploy(UndeployApplicationRequest request) { - return getStatus(request.getName()) - .doOnNext(status -> assertApplicationExists(request.getName(), status)) - .timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout())) - .then(requestDeleteApplication(request.getName()) - .timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout())) - .doOnSuccess(v -> logger.info("Successfully undeployed app {}", request.getName())) - .doOnError(logError(String.format("Failed to undeploy app %s", request.getName())))) - .then(getStatus(request.getName())) - //TODO: do we wait for deletion here? maybe we don't need status. - .flatMap(status -> Mono.just(UndeployApplicationResponse.builder() - .status(status.getState().toString()) - .build())); + String appName = request.getName(); + + logger.trace("Undeploying application: request={}", request); + + return requestGetApplication(appName) + .timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout())) + .then(requestDeleteApplication(appName) + .timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout())) + .doOnSuccess(v -> logger.info("Successfully undeployed app {}", appName)) + .doOnError(logError(String.format("Failed to undeploy app %s", appName))) + .then(Mono.just(UndeployApplicationResponse.builder() + .name(appName) + .build()))); } - private void assertApplicationExists(String deploymentId, AppStatus status) { - DeploymentState state = status.getState(); - if (state == DeploymentState.unknown) { - throw new IllegalStateException(String.format("App %s is not in a deployed state", deploymentId)); - } + private Mono requestGetApplication(String name) { + return this.operations.applications() + .get(GetApplicationRequest.builder() + .name(name) + .build()); } - private AppStatus createAppStatus(ApplicationDetail applicationDetail, String deploymentId) { - logger.trace("Gathering instances for " + applicationDetail); - logger.trace("InstanceDetails: " + applicationDetail.getInstanceDetails()); + private Mono requestDeleteApplication(String name) { + return this.operations.applications() + .delete(DeleteApplicationRequest.builder() + .deleteRoutes(defaultDeploymentProperties.isDeleteRoutes()) + .name(name) + .build()); + } - AppStatus.Builder builder = AppStatus.of(deploymentId); + private Map getEnvironmentVariables(Map environment) { + Map envVariables = new HashMap<>(getApplicationEnvironment(environment)); - int i = 0; - for (InstanceDetail instanceDetail : applicationDetail.getInstanceDetails()) { - builder.with(new CloudFoundryAppInstanceStatus(applicationDetail, instanceDetail, i++)); - } - for (; i < applicationDetail.getInstances(); i++) { - builder.with(new CloudFoundryAppInstanceStatus(applicationDetail, null, i)); + String javaOpts = javaOpts(environment); + if (StringUtils.hasText(javaOpts)) { + envVariables.put("JAVA_OPTS", javaOpts(environment)); } - return builder.build(); + String group = environment.get(GROUP_PROPERTY_KEY); + if (StringUtils.hasText(group)) { + envVariables.put("SPRING_CLOUD_APPLICATION_GROUP", group); + } + + envVariables.put("SPRING_CLOUD_APPLICATION_GUID", "${vcap.application.name}:${vcap.application.instance_index}"); + envVariables.put("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}"); + + return envVariables; } - private AppStatus createEmptyAppStatus(String deploymentId) { - return AppStatus.of(deploymentId) - .build(); - } + private Map getApplicationEnvironment(Map environment) { + Map applicationProperties = getSanitizedApplicationEnvironment(environment); - private AppStatus createErrorAppStatus(String deploymentId) { - return AppStatus.of(deploymentId) - .generalState(DeploymentState.error) - .build(); - } - - private String deploymentId(AppDeploymentRequest request) { - String prefix = Optional.ofNullable(request.getDeploymentProperties().get(org.springframework.cloud.deployer.spi.app.AppDeployer.GROUP_PROPERTY_KEY)) - .map(group -> String.format("%s-", group)) - .orElse(""); - - String appName = String.format("%s%s", prefix, request.getDefinition().getName()); - - return this.applicationNameGenerator.generateAppName(appName); - } - - private String domain(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(DOMAIN_PROPERTY)) - .orElse(this.deploymentProperties.getDomain()); - } - - private Map getApplicationProperties(String deploymentId, AppDeploymentRequest request) { - Map applicationProperties = getSanitizedApplicationProperties(deploymentId, request); - - if (!useSpringApplicationJson(request)) { + if (!useSpringApplicationJson(environment)) { return applicationProperties; } @@ -290,127 +254,40 @@ public class CloudFoundryAppDeployer extends AbstractCloudFoundryAppDeployer imp } } - private Map getCommandLineArguments(AppDeploymentRequest request) { - if (request.getCommandlineArguments().isEmpty()) { - return Collections.emptyMap(); - } - - String argumentsAsString = request.getCommandlineArguments().stream() - .collect(Collectors.joining(" ")); - String yaml = new Yaml().dump(Collections.singletonMap("arguments", argumentsAsString)); - - return Collections.singletonMap("JBP_CONFIG_JAVA_MAIN", yaml); - } - - private Map getEnvironmentVariables(String deploymentId, AppDeploymentRequest request) { - Map envVariables = new HashMap<>(); - envVariables.putAll(getApplicationProperties(deploymentId, request)); - envVariables.putAll(getCommandLineArguments(request)); - String javaOpts = javaOpts(request); - if (StringUtils.hasText(javaOpts)) { - envVariables.put("JAVA_OPTS", javaOpts(request)); - } - String group = request.getDeploymentProperties().get(org.springframework.cloud.deployer.spi.app.AppDeployer.GROUP_PROPERTY_KEY); - if (StringUtils.hasText(group)) { - envVariables.put("SPRING_CLOUD_APPLICATION_GROUP", group); - } - envVariables.put("SPRING_CLOUD_APPLICATION_GUID", "${vcap.application.name}:${vcap.application.instance_index}"); - envVariables.put("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}"); - return envVariables; - } - - private Map getSanitizedApplicationProperties(String deploymentId, AppDeploymentRequest request) { - Map applicationProperties = new HashMap<>(request.getDefinition().getProperties()); + private Map getSanitizedApplicationEnvironment(Map environment) { + Map applicationProperties = new HashMap<>(environment); // Remove server.port as CF assigns a port for us, and we don't want to override that Optional.ofNullable(applicationProperties.remove("server.port")) - .ifPresent(port -> logger.warn("Ignoring 'server.port={}' for app {}, as Cloud Foundry will assign a local dynamic port. Route to the app will use port 80.", port, deploymentId)); + .ifPresent(port -> logger.warn("Ignoring 'server.port={}', " + + "as Cloud Foundry will assign a local dynamic port. " + + "Route to the app will use port 80.", port)); return applicationProperties; } - private Mono getStatus(String deploymentId) { - return requestGetApplication(deploymentId) - .map(applicationDetail -> createAppStatus(applicationDetail, deploymentId)) - .onErrorResume(IllegalArgumentException.class, t -> { - logger.debug("Application for {} does not exist.", deploymentId); - return Mono.just(createEmptyAppStatus(deploymentId)); - }) - .transform(statusRetry(deploymentId)) - .onErrorReturn(createErrorAppStatus(deploymentId)); + private boolean useSpringApplicationJson(Map environment) { + return Optional.ofNullable(environment.get(USE_SPRING_APPLICATION_JSON_KEY)) + .map(Boolean::valueOf) + .orElse(this.defaultDeploymentProperties.isUseSpringApplicationJson()); } - private ApplicationHealthCheck healthCheck(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(HEALTHCHECK_PROPERTY_KEY)) + private Set servicesToBind(List services) { + Set allServices = new HashSet<>(); + allServices.addAll(this.defaultDeploymentProperties.getServices()); + allServices.addAll(services); + return allServices; + } + + private String domain(Map properties) { + return Optional.ofNullable(properties.get(DOMAIN_PROPERTY)) + .orElse(this.defaultDeploymentProperties.getDomain()); + } + + private ApplicationHealthCheck healthCheck(Map properties) { + return Optional.ofNullable(properties.get(HEALTHCHECK_PROPERTY_KEY)) .map(this::toApplicationHealthCheck) - .orElse(this.deploymentProperties.getHealthCheck()); - } - - private String healthCheckEndpoint(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY)) - .orElse(this.deploymentProperties.getHealthCheckHttpEndpoint()); - } - - private Integer healthCheckTimeout(AppDeploymentRequest request) { - String timeoutString = request.getDeploymentProperties() - .getOrDefault(HEALTHCHECK_TIMEOUT_PROPERTY_KEY, this.deploymentProperties.getHealthCheckTimeout()); - return Integer.parseInt(timeoutString); - } - - private String host(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(HOST_PROPERTY)) - .orElse(this.deploymentProperties.getHost()); - } - - private int instances(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(org.springframework.cloud.deployer.spi.app.AppDeployer.COUNT_PROPERTY_KEY)) - .map(Integer::parseInt) - .orElse(this.deploymentProperties.getInstances()); - } - - - private Mono requestDeleteApplication(String id) { - return this.operations.applications() - .delete(DeleteApplicationRequest.builder() - .deleteRoutes(deploymentProperties.isDeleteRoutes()) - .name(id) - .build()); - } - - private Mono requestGetApplication(String id) { - return this.operations.applications() - .get(GetApplicationRequest.builder() - .name(id) - .build()); - } - - private Mono requestPushApplication(PushApplicationManifestRequest request) { - return this.operations.applications() - .pushManifest(request); - } - - private Flux requestSummary() { - return this.operations.applications().list(); - } - - private String routePath(AppDeploymentRequest request) { - String routePath = request.getDeploymentProperties().get(ROUTE_PATH_PROPERTY); - if (StringUtils.hasText(routePath) && !routePath.startsWith("/")) { - throw new IllegalArgumentException( - "Cloud Foundry routes must start with \"/\". Route passed = [" + routePath + "]."); - } - return routePath; - } - - private String route(AppDeploymentRequest request) { - return request.getDeploymentProperties().get(ROUTE_PROPERTY); - } - - private Set routes(AppDeploymentRequest request) { - Set routes = new HashSet<>(); - routes.addAll(this.deploymentProperties.getRoutes()); - routes.addAll(StringUtils.commaDelimitedListToSet(request.getDeploymentProperties().get(ROUTES_PROPERTY))); - return routes; + .orElse(this.defaultDeploymentProperties.getHealthCheck()); } private ApplicationHealthCheck toApplicationHealthCheck(String raw) { @@ -418,41 +295,136 @@ public class CloudFoundryAppDeployer extends AbstractCloudFoundryAppDeployer imp return ApplicationHealthCheck.from(raw); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("Unsupported health-check value '%s'. Available values are %s", raw, - StringUtils.arrayToCommaDelimitedString(ApplicationHealthCheck.values())), e); + StringUtils.arrayToCommaDelimitedString(ApplicationHealthCheck.values())), e); } } - private Boolean toggleNoRoute(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(NO_ROUTE_PROPERTY)) + private String healthCheckEndpoint(Map properties) { + return Optional.ofNullable(properties.get(HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY)) + .orElse(this.defaultDeploymentProperties.getHealthCheckHttpEndpoint()); + } + + private Integer healthCheckTimeout(Map properties) { + String timeoutString = properties + .getOrDefault(HEALTHCHECK_TIMEOUT_PROPERTY_KEY, this.defaultDeploymentProperties.getHealthCheckTimeout()); + return Integer.parseInt(timeoutString); + } + + private int instances(Map properties) { + return Optional.ofNullable(properties.get(COUNT_PROPERTY_KEY)) + .map(Integer::parseInt) + .orElse(this.defaultDeploymentProperties.getInstances()); + } + + private String host(Map properties) { + return Optional.ofNullable(properties.get(HOST_PROPERTY)) + .orElse(this.defaultDeploymentProperties.getHost()); + } + + private String routePath(Map properties) { + String routePath = properties.get(ROUTE_PATH_PROPERTY); + if (StringUtils.hasText(routePath) && !routePath.startsWith("/")) { + throw new IllegalArgumentException( + "Cloud Foundry routes must start with \"/\". Route passed = [" + routePath + "]."); + } + return routePath; + } + + private String route(Map properties) { + return properties.get(ROUTE_PROPERTY); + } + + private Set routes(Map properties) { + Set routes = new HashSet<>(); + routes.addAll(this.defaultDeploymentProperties.getRoutes()); + routes.addAll(StringUtils.commaDelimitedListToSet(properties.get(ROUTES_PROPERTY))); + return routes; + } + + private Boolean toggleNoRoute(Map properties) { + return Optional.ofNullable(properties.get(NO_ROUTE_PROPERTY)) .map(Boolean::valueOf) .orElse(null); } - private boolean useSpringApplicationJson(AppDeploymentRequest request) { - return Optional.ofNullable(request.getDeploymentProperties().get(USE_SPRING_APPLICATION_JSON_KEY)) - .map(Boolean::valueOf) - .orElse(this.deploymentProperties.isUseSpringApplicationJson()); + private int memory(Map properties) { + String withUnit = properties + .getOrDefault(MEMORY_PROPERTY_KEY, this.defaultDeploymentProperties.getMemory()); + return (int) ByteSizeUtils.parseToMebibytes(withUnit); } - private AppDeploymentRequest createAppDeploymentRequest(DeployApplicationRequest request) { - AppDefinition appDefinition = new AppDefinition(request.getName(), request.getEnvironment()); - Resource resource = resourceLoader.getResource(request.getPath()); - Map deploymentProperties = createDeploymentProperties(request); - return new AppDeploymentRequest(appDefinition, resource, deploymentProperties); + private int diskQuota(Map properties) { + String withUnit = properties + .getOrDefault(DISK_PROPERTY_KEY, this.defaultDeploymentProperties.getDisk()); + return (int) ByteSizeUtils.parseToMebibytes(withUnit); } - private Map createDeploymentProperties(DeployApplicationRequest request) { - Map deploymentProperties = new HashMap<>(); - if (request.getProperties() != null) { - deploymentProperties.putAll(request.getProperties()); + private String buildpack(Map properties) { + return Optional.ofNullable(properties.get(BUILDPACK_PROPERTY_KEY)) + .orElse(this.defaultDeploymentProperties.getBuildpack()); + } + + private String javaOpts(Map properties) { + return Optional.ofNullable(properties.get(JAVA_OPTS_PROPERTY_KEY)) + .orElse(this.defaultDeploymentProperties.getJavaOpts()); + } + + private Predicate isNotFoundError() { + return t -> t instanceof AbstractCloudFoundryException && ((AbstractCloudFoundryException) t).getStatusCode() == HttpStatus.NOT_FOUND.value(); + } + + /** + * Return a Docker image identifier if the application Resource is for a Docker image, or {@literal null} otherwise. + * + * @see #getApplication(Resource) + */ + private String getDockerImage(Resource resource) { + try { + String uri = resource.getURI().toString(); + if (uri.startsWith("docker:")) { + return uri.substring("docker:".length()); + } else { + return null; + } + } catch (IOException e) { + throw Exceptions.propagate(e); } - - if (request.getServices() != null) { - Map services = Collections.singletonMap(SPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_SERVICES_KEY, - StringUtils.collectionToCommaDelimitedString(request.getServices())); - deploymentProperties.putAll(services); - } - return deploymentProperties; } + private Resource getAppResource(DeployApplicationRequest request) { + return resourceLoader.getResource(request.getPath()); + } + + /** + * Return a Path to the application Resource or {@literal null} if the request is for a Docker image. + * + * @see #getDockerImage(Resource) + * @param resource the resource representing the app bits + */ + private Path getApplication(Resource resource) { + try { + if (!resource.getURI().toString().startsWith("docker:")) { + return resource.getFile().toPath(); + } else { + return null; + } + } catch (IOException e) { + throw Exceptions.propagate(e); + } + } + + /** + * Return a function usable in {@literal doOnError} constructs that will unwrap unrecognized Cloud Foundry Exceptions + * and log the text payload. + */ + private Consumer logError(String msg) { + return e -> { + if (e instanceof UnknownCloudFoundryException) { + logger.error(msg + "\nUnknownCloudFoundryException encountered, whose payload follows:\n" + + ((UnknownCloudFoundryException)e).getPayload(), e); + } else { + logger.error(msg, e); + } + }; + } } \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryDeploymentProperties.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryDeploymentProperties.java new file mode 100644 index 0000000..6d6a50a --- /dev/null +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryDeploymentProperties.java @@ -0,0 +1,320 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.deployer.cloudfoundry; + +import org.cloudfoundry.operations.applications.ApplicationHealthCheck; +import org.springframework.beans.factory.annotation.Value; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; + +/** + * Holds configuration properties for specifying what resources and services an app deployed to a Cloud Foundry runtime + * will get. + * + * @author Eric Bottard + * @author Greg Turnquist + * @author Ilayaperumal Gopinathan + */ +public class CloudFoundryDeploymentProperties { + public static final String CLOUDFOUNDRY_PROPERTIES = "spring.cloud.deployer.cloudfoundry"; + + public static final String HEALTHCHECK_PROPERTY_KEY = CLOUDFOUNDRY_PROPERTIES + ".health-check"; + + public static final String HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY = CLOUDFOUNDRY_PROPERTIES + ".health-check-http-endpoint"; + + public static final String HEALTHCHECK_TIMEOUT_PROPERTY_KEY = CLOUDFOUNDRY_PROPERTIES + ".health-check-timeout"; + + public static final String ROUTE_PATH_PROPERTY = CLOUDFOUNDRY_PROPERTIES + ".route-path"; + + public static final String ROUTE_PROPERTY = CLOUDFOUNDRY_PROPERTIES + ".route"; + + public static final String ROUTES_PROPERTY = CLOUDFOUNDRY_PROPERTIES + ".routes"; + + public static final String NO_ROUTE_PROPERTY = CLOUDFOUNDRY_PROPERTIES + ".no-route"; + + public static final String HOST_PROPERTY = CLOUDFOUNDRY_PROPERTIES + ".host"; + + public static final String DOMAIN_PROPERTY = CLOUDFOUNDRY_PROPERTIES + ".domain"; + + public static final String BUILDPACK_PROPERTY_KEY = CLOUDFOUNDRY_PROPERTIES + ".buildpack"; + + public static final String JAVA_OPTS_PROPERTY_KEY = CLOUDFOUNDRY_PROPERTIES + ".javaOpts"; + + public static final String USE_SPRING_APPLICATION_JSON_KEY = CLOUDFOUNDRY_PROPERTIES + ".use-spring-application-json"; + + /** + * The names of services to bind to all applications deployed as a module. + * This should typically contain a service capable of playing the role of a binding transport. + */ + private Set services = new HashSet<>(); + + /** + * The host name to use as part of the route. Defaults to hostname derived by Cloud Foundry. + */ + private String host = null; + + /** + * The domain to use when mapping routes for applications. + */ + private String domain; + + /** + * The routes that the application should be bound to. + * Mutually exclusive with host and domain. + */ + private Set routes = new HashSet<>(); + + /** + * The buildpack to use for deploying the application. + */ + private String buildpack = "https://github.com/cloudfoundry/java-buildpack.git#v4.7.1"; + + /** + * The amount of memory to allocate, if not overridden per-app. Default unit is mebibytes, 'M' and 'G" suffixes supported. + */ + private String memory = "1024m"; + + /** + * The amount of disk space to allocate, if not overridden per-app. Default unit is mebibytes, 'M' and 'G" suffixes supported. + */ + private String disk = "1024m"; + + /** + * The type of health check to perform on deployed application, if not overridden per-app. Defaults to PORT + */ + private ApplicationHealthCheck healthCheck = ApplicationHealthCheck.PORT; + + /** + * The path that the http health check will use, defaults to @{code /health} + */ + private String healthCheckHttpEndpoint = "/health"; + + /** + * The timeout value for health checks in seconds. Defaults to 120 seconds. + */ + private String healthCheckTimeout = "120"; + + /** + * The number of instances to run. + */ + private int instances = 1; + + /** + * Flag to enable prefixing the app name with a random prefix. + */ + private boolean enableRandomAppNamePrefix = true; + + /** + * Timeout for blocking API calls, in seconds. + */ + private long apiTimeout = 360L; + + /** + * Timeout for name API operations in milliseconds + */ + private long statusTimeout = 5_000L; + + /** + * Flag to indicate whether application properties are fed into SPRING_APPLICATION_JSON or ENVIRONMENT VARIABLES. + */ + private boolean useSpringApplicationJson = true; + + /** + * If set, override the timeout allocated for staging the app by the client. + */ + private Duration stagingTimeout = Duration.ofMinutes(15L); + + /** + * If set, override the timeout allocated for starting the app by the client. + */ + private Duration startupTimeout = Duration.ofMinutes(5L); + + /** + * String to use as prefix for name of deployed app. Defaults to spring.application.name. + */ + @Value("${spring.application.name:}") + private String appNamePrefix; + + /** + * Whether to also delete routes when un-deploying an application. + */ + private boolean deleteRoutes = true; + + private String javaOpts; + + public Set getServices() { + return services; + } + + public void setServices(Set services) { + this.services = services; + } + + public String getBuildpack() { + return buildpack; + } + + public void setBuildpack(String buildpack) { + this.buildpack = buildpack; + } + + public String getMemory() { + return memory; + } + + public void setMemory(String memory) { + this.memory = memory; + } + + public String getDisk() { + return disk; + } + + public void setDisk(String disk) { + this.disk = disk; + } + + public int getInstances() { + return instances; + } + + public void setInstances(int instances) { + this.instances = instances; + } + + public boolean isEnableRandomAppNamePrefix() { + return enableRandomAppNamePrefix; + } + + public void setEnableRandomAppNamePrefix(boolean enableRandomAppNamePrefix) { + this.enableRandomAppNamePrefix = enableRandomAppNamePrefix; + } + + public String getAppNamePrefix() { + return appNamePrefix; + } + + public void setAppNamePrefix(String appNamePrefix) { + this.appNamePrefix = appNamePrefix; + } + + public long getApiTimeout() { + return apiTimeout; + } + + public void setApiTimeout(long apiTimeout) { + this.apiTimeout = apiTimeout; + } + + public boolean isUseSpringApplicationJson() { + return useSpringApplicationJson; + } + + public void setUseSpringApplicationJson(boolean useSpringApplicationJson) { + this.useSpringApplicationJson = useSpringApplicationJson; + } + + public ApplicationHealthCheck getHealthCheck() { + return healthCheck; + } + + public void setHealthCheck(ApplicationHealthCheck healthCheck) { + this.healthCheck = healthCheck; + } + + public String getHealthCheckHttpEndpoint() { + return healthCheckHttpEndpoint; + } + + public void setHealthCheckHttpEndpoint(String healthCheckHttpEndpoint) { + this.healthCheckHttpEndpoint = healthCheckHttpEndpoint; + } + + public String getHealthCheckTimeout() { + return healthCheckTimeout; + } + + public void setHealthCheckTimeout(String healthCheckTimeout) { + this.healthCheckTimeout = healthCheckTimeout; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Set getRoutes() { + return routes; + } + + public void setRoutes(Set routes) { + this.routes = routes; + } + + public Duration getStagingTimeout() { + return stagingTimeout; + } + + public void setStagingTimeout(Duration stagingTimeout) { + this.stagingTimeout = stagingTimeout; + } + + public Duration getStartupTimeout() { + return startupTimeout; + } + + public void setStartupTimeout(Duration startupTimeout) { + this.startupTimeout = startupTimeout; + } + + public long getStatusTimeout() { + return statusTimeout; + } + + public void setStatusTimeout(long statusTimeout) { + this.statusTimeout = statusTimeout; + } + + public boolean isDeleteRoutes() { + return deleteRoutes; + } + + public void setDeleteRoutes(boolean deleteRoutes) { + this.deleteRoutes = deleteRoutes; + } + + public String getJavaOpts() { + return javaOpts; + } + + public void setJavaOpts(String javaOpts) { + this.javaOpts = javaOpts; + } +} diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/NoOpAppNameGenerator.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/NoOpAppNameGenerator.java deleted file mode 100644 index 4445d9d..0000000 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/NoOpAppNameGenerator.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2016-2018. the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.appbroker.deployer.cloudfoundry; - -import org.springframework.cloud.deployer.spi.cloudfoundry.AppNameGenerator; - -public class NoOpAppNameGenerator implements AppNameGenerator { - - @Override - public String generateAppName(String appName) { - return appName; - } - -} diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java index 89dbbc7..607245e 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java @@ -33,7 +33,6 @@ import reactor.test.StepVerifier; import org.springframework.cloud.appbroker.deployer.AppDeployer; import org.springframework.cloud.appbroker.deployer.DeployApplicationRequest; -import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.ResourceLoader; @@ -64,7 +63,6 @@ class CloudFoundryAppDeployerTest { @BeforeEach void setUp() { - NoOpAppNameGenerator applicationNameGenerator = new NoOpAppNameGenerator(); CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties(); deploymentProperties.setDisk(APP_MEMORY_STRING); @@ -72,8 +70,8 @@ class CloudFoundryAppDeployerTest { when(cloudFoundryOperations.applications()).thenReturn(applications); when(resourceLoader.getResource(APP_PATH)).thenReturn(new FileSystemResource(APP_PATH)); - appDeployer = new CloudFoundryAppDeployer(applicationNameGenerator, - deploymentProperties, cloudFoundryOperations, null, resourceLoader); + appDeployer = new CloudFoundryAppDeployer( + deploymentProperties, cloudFoundryOperations, resourceLoader); } @Test diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/NoOpAppNameGeneratorTest.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/NoOpAppNameGeneratorTest.java deleted file mode 100644 index ea331d7..0000000 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/NoOpAppNameGeneratorTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2016-2018. the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.appbroker.deployer.cloudfoundry; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.deployer.spi.cloudfoundry.AppNameGenerator; - -import static org.assertj.core.api.Assertions.assertThat; - -class NoOpAppNameGeneratorTest { - - private AppNameGenerator appNameGenerator = new NoOpAppNameGenerator(); - - @Test - void shouldNoOp() { - assertThat(appNameGenerator.generateAppName("app-name")).isEqualTo("app-name"); - } -} \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer/build.gradle b/spring-cloud-app-broker-deployer/build.gradle index f4da66a..4166419 100644 --- a/spring-cloud-app-broker-deployer/build.gradle +++ b/spring-cloud-app-broker-deployer/build.gradle @@ -17,5 +17,6 @@ description = "Spring Cloud App Broker Deployer" dependencies { + api("org.springframework:spring-core:${springVersion}") api("io.projectreactor:reactor-core:${reactorVersion}") } diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java index 2303379..b6c6c80 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java @@ -28,7 +28,4 @@ public interface AppDeployer { return Mono.empty(); } - default Mono status(GetApplicationStatusRequest request) { - return Mono.empty(); - } } \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationRequest.java index 65d852b..c0978b1 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationRequest.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationRequest.java @@ -33,7 +33,8 @@ public class DeployApplicationRequest { private List services; - DeployApplicationRequest(String name, String path, Map properties, Map environment, List services) { + private DeployApplicationRequest(String name, String path, Map properties, + Map environment, List services) { this.name = name; this.path = path; this.properties = properties; diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationResponse.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationResponse.java index 48aa8e5..ca05aa0 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationResponse.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeployApplicationResponse.java @@ -20,7 +20,7 @@ public class DeployApplicationResponse { private String name; - DeployApplicationResponse(String name) { + private DeployApplicationResponse(String name) { this.name = name; } @@ -37,7 +37,6 @@ public class DeployApplicationResponse { private String name; DeployApplicationResponseBuilder() { - } public DeployApplicationResponseBuilder name(String name) { diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeploymentProperties.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeploymentProperties.java new file mode 100644 index 0000000..0547ee0 --- /dev/null +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeploymentProperties.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.deployer; + +import org.springframework.cloud.appbroker.deployer.util.ByteSizeUtils; + +public interface DeploymentProperties { + + /** + * Common prefix used for deployment properties. + */ + String PREFIX = "spring.cloud.deployer."; + + /** + * The deployment property for the count (number of app instances). + * If not provided, a deployer should assume 1 instance. + */ + String COUNT_PROPERTY_KEY = PREFIX + "count"; + + /** + * The deployment property for the group to which an app belongs. + * If not provided, a deployer should assume no group. + */ + String GROUP_PROPERTY_KEY = PREFIX + "group"; + + /** + * The deployment property that indicates if each app instance should have an index value + * within a sequence from 0 to N-1, where N is the value of the {@value #COUNT_PROPERTY_KEY} + * property. If not provided, a deployer should assume app instance indexing is not necessary. + */ + String INDEXED_PROPERTY_KEY = PREFIX + "indexed"; + + /** + * The property to be set at each instance level to specify the sequence number + * amongst 0 to N-1, where N is the value of the {@value #COUNT_PROPERTY_KEY} property. + * Specified as CAPITAL_WITH_UNDERSCORES as this is typically passed as an environment + * variable, but when targeting a Spring app, other variations may apply. + * + * @see #INDEXED_PROPERTY_KEY + */ + String INSTANCE_INDEX_PROPERTY_KEY = "INSTANCE_INDEX"; + + /** + * The deployment property for the memory setting for the container that will run the app. + * The memory is specified in Mebibytes, + * by default, with optional case-insensitive trailing unit 'm' and 'g' being supported, + * for mebi- and giga- respectively. + *

+ * 1 MiB = 2^20 bytes = 1024*1024 bytes vs. the decimal based 1MB = 10^6 bytes = 1000*1000 bytes, + *

+ * Implementations are expected to translate this value to the target platform as faithfully as possible. + * + * @see ByteSizeUtils + */ + String MEMORY_PROPERTY_KEY = PREFIX + "memory"; + + /** + * The deployment property for the disk setting for the container that will run the app. + * The memory is specified in Mebibytes, + * by default, with optional case-insensitive trailing unit 'm' and 'g' being supported, + * for mebi- and giga- respectively. + *

+ * 1 MiB = 2^20 bytes = 1024*1024 bytes vs. the decimal based 1MB = 10^6 bytes = 1000*1000 bytes, + *

+ * Implementations are expected to translate this value to the target platform as faithfully as possible. + * + * @see ByteSizeUtils + */ + String DISK_PROPERTY_KEY = PREFIX + "disk"; + + /** + * The deployment property for the cpu setting for the container that will run the app. + * The cpu is specified as whole multiples or decimal fractions of virtual cores. Some platforms will not + * support setting cpu and will ignore this setting. Other platforms may require whole numbers and might + * round up. Exactly how this property affects the deployments will vary between implementations. + */ + String CPU_PROPERTY_KEY = PREFIX + "cpu"; +} diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/GetApplicationStatusRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/GetApplicationStatusRequest.java deleted file mode 100644 index 1d296f4..0000000 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/GetApplicationStatusRequest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.appbroker.deployer; - -public class GetApplicationStatusRequest { - - private String name; - - GetApplicationStatusRequest(String name) { - this.name = name; - } - - public static GetApplicationStatusRequestBuilder builder() { - return new GetApplicationStatusRequestBuilder(); - } - - public String getName() { - return name; - } - - public static class GetApplicationStatusRequestBuilder { - - private String name; - - GetApplicationStatusRequestBuilder() { - - } - - public GetApplicationStatusRequestBuilder name(String name) { - this.name = name; - return this; - } - - public GetApplicationStatusRequest build() { - return new GetApplicationStatusRequest(name); - } - } - -} diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/GetApplicationStatusResponse.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/GetApplicationStatusResponse.java deleted file mode 100644 index 7d3dceb..0000000 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/GetApplicationStatusResponse.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.appbroker.deployer; - -public class GetApplicationStatusResponse { - - private String deploymentId; - - private String status; - - GetApplicationStatusResponse(String deploymentId, String status) { - this.deploymentId = deploymentId; - this.status = status; - } - - public static GetApplicationStatusResponseBuilder builder() { - return new GetApplicationStatusResponseBuilder(); - } - - public String getDeploymentId() { - return deploymentId; - } - - public String getStatus() { - return status; - } - - public static class GetApplicationStatusResponseBuilder { - - private String deploymentId; - - private String status; - - GetApplicationStatusResponseBuilder() { - - } - - public GetApplicationStatusResponseBuilder deploymentId(String deploymentId) { - this.deploymentId = deploymentId; - return this; - } - - public GetApplicationStatusResponseBuilder status(String status) { - this.status = status; - return this; - } - - public GetApplicationStatusResponse build() { - return new GetApplicationStatusResponse(deploymentId, status); - } - - } - -} diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationRequest.java index 3e05db9..adae305 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationRequest.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationRequest.java @@ -20,7 +20,7 @@ public class UndeployApplicationRequest { private String name; - UndeployApplicationRequest(String name) { + private UndeployApplicationRequest(String name) { this.name = name; } diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationResponse.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationResponse.java index 9179ade..743f47e 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationResponse.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UndeployApplicationResponse.java @@ -18,35 +18,34 @@ package org.springframework.cloud.appbroker.deployer; public class UndeployApplicationResponse { - private String status; + private String name; - UndeployApplicationResponse(String status) { - this.status = status; + private UndeployApplicationResponse(String name) { + this.name = name; } public static UndeployApplicationResponseBuilder builder() { return new UndeployApplicationResponseBuilder(); } - public String getStatus() { - return this.status; + public String getName() { + return this.name; } public static class UndeployApplicationResponseBuilder { - private String status; + private String name; UndeployApplicationResponseBuilder() { - } - public UndeployApplicationResponseBuilder status(String status) { - this.status = status; + public UndeployApplicationResponseBuilder name(String name) { + this.name = name; return this; } public UndeployApplicationResponse build() { - return new UndeployApplicationResponse(status); + return new UndeployApplicationResponse(name); } } } diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/util/ByteSizeUtils.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/util/ByteSizeUtils.java new file mode 100644 index 0000000..0448eb0 --- /dev/null +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/util/ByteSizeUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.deployer.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class for dealing with parseable byte sizes, such as memory and disk limits. + * + * @author Eric Bottard + */ +public class ByteSizeUtils { + + private ByteSizeUtils() { + + } + + private static final Pattern SIZE_PATTERN = Pattern.compile("(?\\d+)(?(m|g)?)", Pattern.CASE_INSENSITIVE); + + /** + * Return the number of mebibytes (1024*1024) denoted by the given text, where an optional case-insensitive unit of + * 'm' or 'g' can be used to mean mebi- or gebi- bytes, respectively. Lack of unit assumes mebibytes. + */ + public static long parseToMebibytes(String text) { + Matcher matcher = SIZE_PATTERN.matcher(text); + if (!matcher.matches()) { + throw new IllegalArgumentException(String.format("Could not parse '%s' as a byte size." + + " Expected a number with optional 'm' or 'g' suffix", text)); + } + long size = Long.parseLong(matcher.group("amount")); + if (matcher.group("unit").equalsIgnoreCase("g")) { + size *= 1024L; + } + return size; + } +} diff --git a/spring-cloud-app-broker-sample/src/main/java/org/springframework/cloud/appbroker/sample/AppBrokerSampleApplication.java b/spring-cloud-app-broker-sample/src/main/java/org/springframework/cloud/appbroker/sample/AppBrokerSampleApplication.java index 8d5ce0f..0f57652 100644 --- a/spring-cloud-app-broker-sample/src/main/java/org/springframework/cloud/appbroker/sample/AppBrokerSampleApplication.java +++ b/spring-cloud-app-broker-sample/src/main/java/org/springframework/cloud/appbroker/sample/AppBrokerSampleApplication.java @@ -18,9 +18,8 @@ package org.springframework.cloud.appbroker.sample; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration; -@SpringBootApplication(exclude = CloudFoundryDeployerAutoConfiguration.class) +@SpringBootApplication public class AppBrokerSampleApplication { public static void main(String[] args) {