Remove dependency on Spring Cloud Deployer. Remove unused code from the deployer abstraction.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public class DeployerClient {
|
||||
return appDeployer.undeploy(UndeployApplicationRequest.builder()
|
||||
.name(backingApplication.getName())
|
||||
.build())
|
||||
.map(UndeployApplicationResponse::getStatus);
|
||||
.map(UndeployApplicationResponse::getName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())));
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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<String> servicesToBind(AppDeploymentRequest request) {
|
||||
Set<String> 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<Throwable> 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<Throwable> 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 <T> 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
|
||||
*/
|
||||
<T> Function<Mono<T>, Mono<T>> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<DeployApplicationResponse> deploy(DeployApplicationRequest deployApplicationRequest) {
|
||||
AppDeploymentRequest request = createAppDeploymentRequest(deployApplicationRequest);
|
||||
public Mono<DeployApplicationResponse> deploy(DeployApplicationRequest request) {
|
||||
String appName = request.getName();
|
||||
Resource appResource = getAppResource(request);
|
||||
Map<String, String> 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<Void> pushApplication(String deploymentId, AppDeploymentRequest request) {
|
||||
ApplicationManifest manifest = buildAppManifest(deploymentId, request);
|
||||
private Mono<Void> pushApplication(DeployApplicationRequest request,
|
||||
Map<String, String> 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<String, String> 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<Route> routes = routes(request).stream()
|
||||
if (!routes(deploymentProperties).isEmpty()) {
|
||||
Set<Route> 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<GetApplicationStatusResponse> 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<Void> requestPushApplication(PushApplicationManifestRequest request) {
|
||||
return this.operations.applications()
|
||||
.pushManifest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UndeployApplicationResponse> 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<ApplicationDetail> 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<Void> requestDeleteApplication(String name) {
|
||||
return this.operations.applications()
|
||||
.delete(DeleteApplicationRequest.builder()
|
||||
.deleteRoutes(defaultDeploymentProperties.isDeleteRoutes())
|
||||
.name(name)
|
||||
.build());
|
||||
}
|
||||
|
||||
AppStatus.Builder builder = AppStatus.of(deploymentId);
|
||||
private Map<String, String> getEnvironmentVariables(Map<String, String> environment) {
|
||||
Map<String, String> 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<String, String> getApplicationEnvironment(Map<String, String> environment) {
|
||||
Map<String, String> 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<String, String> getApplicationProperties(String deploymentId, AppDeploymentRequest request) {
|
||||
Map<String, String> applicationProperties = getSanitizedApplicationProperties(deploymentId, request);
|
||||
|
||||
if (!useSpringApplicationJson(request)) {
|
||||
if (!useSpringApplicationJson(environment)) {
|
||||
return applicationProperties;
|
||||
}
|
||||
|
||||
@@ -290,127 +254,40 @@ public class CloudFoundryAppDeployer extends AbstractCloudFoundryAppDeployer imp
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> 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<String, String> getEnvironmentVariables(String deploymentId, AppDeploymentRequest request) {
|
||||
Map<String, String> 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<String, String> getSanitizedApplicationProperties(String deploymentId, AppDeploymentRequest request) {
|
||||
Map<String, String> applicationProperties = new HashMap<>(request.getDefinition().getProperties());
|
||||
private Map<String, String> getSanitizedApplicationEnvironment(Map<String, String> environment) {
|
||||
Map<String, String> 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<AppStatus> 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<String, String> 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<String> servicesToBind(List<String> services) {
|
||||
Set<String> allServices = new HashSet<>();
|
||||
allServices.addAll(this.defaultDeploymentProperties.getServices());
|
||||
allServices.addAll(services);
|
||||
return allServices;
|
||||
}
|
||||
|
||||
private String domain(Map<String, String> properties) {
|
||||
return Optional.ofNullable(properties.get(DOMAIN_PROPERTY))
|
||||
.orElse(this.defaultDeploymentProperties.getDomain());
|
||||
}
|
||||
|
||||
private ApplicationHealthCheck healthCheck(Map<String, String> 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<Void> requestDeleteApplication(String id) {
|
||||
return this.operations.applications()
|
||||
.delete(DeleteApplicationRequest.builder()
|
||||
.deleteRoutes(deploymentProperties.isDeleteRoutes())
|
||||
.name(id)
|
||||
.build());
|
||||
}
|
||||
|
||||
private Mono<ApplicationDetail> requestGetApplication(String id) {
|
||||
return this.operations.applications()
|
||||
.get(GetApplicationRequest.builder()
|
||||
.name(id)
|
||||
.build());
|
||||
}
|
||||
|
||||
private Mono<Void> requestPushApplication(PushApplicationManifestRequest request) {
|
||||
return this.operations.applications()
|
||||
.pushManifest(request);
|
||||
}
|
||||
|
||||
private Flux<ApplicationSummary> 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<String> routes(AppDeploymentRequest request) {
|
||||
Set<String> 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<String, String> properties) {
|
||||
return Optional.ofNullable(properties.get(HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY))
|
||||
.orElse(this.defaultDeploymentProperties.getHealthCheckHttpEndpoint());
|
||||
}
|
||||
|
||||
private Integer healthCheckTimeout(Map<String, String> properties) {
|
||||
String timeoutString = properties
|
||||
.getOrDefault(HEALTHCHECK_TIMEOUT_PROPERTY_KEY, this.defaultDeploymentProperties.getHealthCheckTimeout());
|
||||
return Integer.parseInt(timeoutString);
|
||||
}
|
||||
|
||||
private int instances(Map<String, String> properties) {
|
||||
return Optional.ofNullable(properties.get(COUNT_PROPERTY_KEY))
|
||||
.map(Integer::parseInt)
|
||||
.orElse(this.defaultDeploymentProperties.getInstances());
|
||||
}
|
||||
|
||||
private String host(Map<String, String> properties) {
|
||||
return Optional.ofNullable(properties.get(HOST_PROPERTY))
|
||||
.orElse(this.defaultDeploymentProperties.getHost());
|
||||
}
|
||||
|
||||
private String routePath(Map<String, String> 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<String, String> properties) {
|
||||
return properties.get(ROUTE_PROPERTY);
|
||||
}
|
||||
|
||||
private Set<String> routes(Map<String, String> properties) {
|
||||
Set<String> routes = new HashSet<>();
|
||||
routes.addAll(this.defaultDeploymentProperties.getRoutes());
|
||||
routes.addAll(StringUtils.commaDelimitedListToSet(properties.get(ROUTES_PROPERTY)));
|
||||
return routes;
|
||||
}
|
||||
|
||||
private Boolean toggleNoRoute(Map<String, String> 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<String, String> 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<String, String> deploymentProperties = createDeploymentProperties(request);
|
||||
return new AppDeploymentRequest(appDefinition, resource, deploymentProperties);
|
||||
private int diskQuota(Map<String, String> properties) {
|
||||
String withUnit = properties
|
||||
.getOrDefault(DISK_PROPERTY_KEY, this.defaultDeploymentProperties.getDisk());
|
||||
return (int) ByteSizeUtils.parseToMebibytes(withUnit);
|
||||
}
|
||||
|
||||
private Map<String, String> createDeploymentProperties(DeployApplicationRequest request) {
|
||||
Map<String, String> deploymentProperties = new HashMap<>();
|
||||
if (request.getProperties() != null) {
|
||||
deploymentProperties.putAll(request.getProperties());
|
||||
private String buildpack(Map<String, String> properties) {
|
||||
return Optional.ofNullable(properties.get(BUILDPACK_PROPERTY_KEY))
|
||||
.orElse(this.defaultDeploymentProperties.getBuildpack());
|
||||
}
|
||||
|
||||
private String javaOpts(Map<String, String> properties) {
|
||||
return Optional.ofNullable(properties.get(JAVA_OPTS_PROPERTY_KEY))
|
||||
.orElse(this.defaultDeploymentProperties.getJavaOpts());
|
||||
}
|
||||
|
||||
private Predicate<Throwable> 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<String, String> 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<Throwable> 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> 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<String> getServices() {
|
||||
return services;
|
||||
}
|
||||
|
||||
public void setServices(Set<String> 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<String> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
|
||||
public void setRoutes(Set<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,6 @@
|
||||
description = "Spring Cloud App Broker Deployer"
|
||||
|
||||
dependencies {
|
||||
api("org.springframework:spring-core:${springVersion}")
|
||||
api("io.projectreactor:reactor-core:${reactorVersion}")
|
||||
}
|
||||
|
||||
@@ -28,7 +28,4 @@ public interface AppDeployer {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
default Mono<GetApplicationStatusResponse> status(GetApplicationStatusRequest request) {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,8 @@ public class DeployApplicationRequest {
|
||||
|
||||
private List<String> services;
|
||||
|
||||
DeployApplicationRequest(String name, String path, Map<String, String> properties, Map<String, String> environment, List<String> services) {
|
||||
private DeployApplicationRequest(String name, String path, Map<String, String> properties,
|
||||
Map<String, String> environment, List<String> services) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.properties = properties;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <a href="https://en.wikipedia.org/wiki/Mebibyte">Mebibytes</a>,
|
||||
* by default, with optional case-insensitive trailing unit 'm' and 'g' being supported,
|
||||
* for mebi- and giga- respectively.
|
||||
* <p>
|
||||
* 1 MiB = 2^20 bytes = 1024*1024 bytes vs. the decimal based 1MB = 10^6 bytes = 1000*1000 bytes,
|
||||
* <p>
|
||||
* 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 <a href="https://en.wikipedia.org/wiki/Mebibyte">Mebibytes</a>,
|
||||
* by default, with optional case-insensitive trailing unit 'm' and 'g' being supported,
|
||||
* for mebi- and giga- respectively.
|
||||
* <p>
|
||||
* 1 MiB = 2^20 bytes = 1024*1024 bytes vs. the decimal based 1MB = 10^6 bytes = 1000*1000 bytes,
|
||||
* <p>
|
||||
* 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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class UndeployApplicationRequest {
|
||||
|
||||
private String name;
|
||||
|
||||
UndeployApplicationRequest(String name) {
|
||||
private UndeployApplicationRequest(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("(?<amount>\\d+)(?<unit>(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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user