diff --git a/spring-cloud-app-broker-acceptance-tests/README.adoc b/spring-cloud-app-broker-acceptance-tests/README.adoc index 0fe0db9..3a2067d 100644 --- a/spring-cloud-app-broker-acceptance-tests/README.adoc +++ b/spring-cloud-app-broker-acceptance-tests/README.adoc @@ -8,4 +8,31 @@ === Running the tests - $ ./gradlew clean check -PacceptanceTests +== Running + +The tests require the following properties to be set: + +* `tests.sampleAppPath` - The path to the sample broker jar (eg. /spring-cloud-app-broker-sample/build/libs/spring-cloud-app-broker-sample.jar). +* `spring.cloud.appbroker.deployer.cloudfoundry.api-host` - The CF API host where the tests are going to run. +* `spring.cloud.appbroker.deployer.cloudfoundry.api-port` - The CF API port where the tests are going to run. +* `spring.cloud.appbroker.deployer.cloudfoundry.username` - The CF API username where the tests are going to run. +* `spring.cloud.appbroker.deployer.cloudfoundry.password` - The CF API password where the tests are going to run. +* `spring.cloud.appbroker.deployer.cloudfoundry.default-org` - The CF organization where the tests are going to run. +* `spring.cloud.appbroker.deployer.cloudfoundry.default-space` - The CF space where the tests are going to run. +* `spring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation` - If SSL validation should be skipped. + +These properties can be set with `-D` system properties on the gradle command line. + +Also, the flag acceptanceTests must be provided as `-PacceptanceTests`. + +[source,bash] +---- +$ ./gradlew test -PacceptanceTests \ + -Dtests.sampleAppPath=/path/to/broker.jar \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.api-host=api.cf.my.cf.io \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.api-port=443 \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.username=admin \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.password=password \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.default-org=test \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.default-space=development \ + -Dspring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation=true diff --git a/spring-cloud-app-broker-acceptance-tests/build.gradle b/spring-cloud-app-broker-acceptance-tests/build.gradle index c39c24f..16d0652 100644 --- a/spring-cloud-app-broker-acceptance-tests/build.gradle +++ b/spring-cloud-app-broker-acceptance-tests/build.gradle @@ -16,19 +16,49 @@ description = "Spring Cloud App Broker Acceptance Tests" -ext { - restAssuredVersion = "3.0.7" -} - dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") testImplementation("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") - testImplementation("io.rest-assured:rest-assured:${restAssuredVersion}") + testImplementation("org.assertj:assertj-core:${assertjVersion}") + + testImplementation("org.springframework.boot:spring-boot-starter-webflux:2.0.3.RELEASE") + testImplementation("org.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE") + testImplementation("org.cloudfoundry:cloudfoundry-client-reactor:3.12.0.RELEASE") + testImplementation("org.cloudfoundry:cloudfoundry-operations:3.12.0.RELEASE") + testImplementation("io.projectreactor:reactor-core:3.1.8.RELEASE") + testImplementation("io.projectreactor.ipc:reactor-netty:0.7.8.RELEASE") } test { + // Only run the tests if acceptanceTests is specified onlyIf { project.hasProperty("acceptanceTests") } -} \ No newline at end of file + // Pass relevant java properties to the test task + systemProperties << System.properties.findAll { it.key.startsWith("tests.") } + systemProperties << System.properties.findAll { it.key.startsWith("spring.") } +} + +// Disable classfile warnings because of cloudfoundry-client warnings +[compileJava, compileTestJava]*.options*.compilerArgs = [ + "-Xlint:serial", + "-Xlint:varargs", + "-Xlint:cast", + "-Xlint:-classfile", + "-Xlint:dep-ann", + "-Xlint:divzero", + "-Xlint:empty", + "-Xlint:finally", + "-Xlint:overrides", + "-Xlint:path", + "-Xlint:-processing", + "-Xlint:static", + "-Xlint:try", + "-Xlint:fallthrough", + "-Xlint:rawtypes", + "-Xlint:deprecation", + "-Xlint:unchecked", + "-Xlint:-options", + "-Werror" +] \ No newline at end of file diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java index 49ec88e..9d0a8ba 100644 --- a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java @@ -16,17 +16,82 @@ package org.springframework.cloud.appbroker.acceptance; -import org.junit.jupiter.api.BeforeEach; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import org.cloudfoundry.operations.applications.ApplicationEnvironments; +import org.cloudfoundry.operations.applications.ApplicationSummary; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryClientConfiguration; +import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryService; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import reactor.util.function.Tuple2; +@SpringBootTest(classes = {CloudFoundryClientConfiguration.class, CloudFoundryService.class}) +@ExtendWith(SpringExtension.class) class CloudFoundryAcceptanceTest { - @BeforeEach - void setUp() { - // get the sample app - // push no start - // set the catalog - // set the target cf deployer config + private static final String SAMPLE_BROKER_APP_NAME = "sample-broker"; + private static final String BROKER_NAME = "sample-broker-name"; + private static final String SERVICE_NAME = "example"; + private static final String PLAN_NAME = "standard"; + private static final String SERVICE_INSTANCE_NAME = "my-service"; - // set the appbroker for each test + @Autowired + private CloudFoundryService cloudFoundryService; + + @Value("${tests.sampleAppPath}") + private String sampleAppPath; + + @AfterEach + void tearDown() { + cleanup(); + } + + void initializeBroker(List> properties) { + cleanup(); + + cloudFoundryService.pushAppNoStart(SAMPLE_BROKER_APP_NAME, getSampleAppPath()); + cloudFoundryService.setBrokerAppEnvironment(properties); + cloudFoundryService.startApplication(SAMPLE_BROKER_APP_NAME); + + String backingAppURL = cloudFoundryService.getApplicationRoute(SAMPLE_BROKER_APP_NAME); + cloudFoundryService.createServiceBroker(BROKER_NAME, backingAppURL); + cloudFoundryService.enableServiceBrokerAccess(SERVICE_NAME); + } + + private void cleanup() { + cloudFoundryService.deleteServiceInstance(SERVICE_INSTANCE_NAME); + cloudFoundryService.deleteServiceBroker(BROKER_NAME); + cloudFoundryService.deleteBackingApp(SAMPLE_BROKER_APP_NAME); + } + + void createServiceInstance() { + cloudFoundryService.createServiceInstance(PLAN_NAME, SERVICE_NAME, SERVICE_INSTANCE_NAME); + } + + void deleteServiceInstance() { + cloudFoundryService.deleteServiceInstance(SERVICE_INSTANCE_NAME); + } + + Optional getApplicationSummaryByName(String appName) { + List applicationsAfterDeletion = cloudFoundryService.getApplications(); + + return applicationsAfterDeletion.stream() + .filter(applicationSummary -> appName.equals(applicationSummary.getName())) + .findFirst(); + } + + ApplicationEnvironments getApplicationEnvironmentByName(String appName) { + return cloudFoundryService.getApplicationEnvironmentByAppName(appName); + } + + private Path getSampleAppPath() { + return Paths.get(sampleAppPath, ""); } } \ No newline at end of file diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceAcceptanceTest.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceAcceptanceTest.java index 4319631..6dfc75e 100644 --- a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceAcceptanceTest.java +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceAcceptanceTest.java @@ -16,19 +16,54 @@ package org.springframework.cloud.appbroker.acceptance; +import java.util.Optional; +import org.cloudfoundry.operations.applications.ApplicationEnvironments; +import org.cloudfoundry.operations.applications.ApplicationSummary; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.newArrayList; +import static reactor.util.function.Tuples.of; class CreateInstanceAcceptanceTest extends CloudFoundryAcceptanceTest { - @Test - void shouldFail() { - Executable executable = () -> { - throw new RuntimeException("message"); - }; - assertThrows(IllegalArgumentException.class, executable); + private static final String BROKER_SAMPLE_APP_CREATE = "broker-sample-app-create"; + + @BeforeEach + void setUp() { + initializeBroker(newArrayList( + of("spring.cloud.appbroker.apps[0].name", BROKER_SAMPLE_APP_CREATE), + of("spring.cloud.appbroker.apps[0].path", "classpath:demo.jar"), + of("spring.cloud.appbroker.apps[0].environment.ENV_VAR_1", "value1"), + of("spring.cloud.appbroker.apps[0].environment.ENV_VAR_2", "value2"), + of("spring.cloud.appbroker.apps[0].properties.spring.cloud.deployer.memory", "2G"), + of("spring.cloud.appbroker.apps[0].properties.spring.cloud.deployer.count", "2") + ) + ); } + + @Test + void shouldPushAppWhenCreateServiceCalled() { + // when a service instance is created + createServiceInstance(); + + // then a backing application is deployed + Optional backingApplication = getApplicationSummaryByName(BROKER_SAMPLE_APP_CREATE); + assertThat(backingApplication).isNotEmpty(); + + // and has the properties + ApplicationSummary applicationSummary = backingApplication.orElseThrow(RuntimeException::new); + assertThat(applicationSummary.getMemoryLimit()).isEqualTo(2048); + assertThat(applicationSummary.getInstances()).isEqualTo(2); + + // and has the environment variables + ApplicationEnvironments applicationEnvironments = getApplicationEnvironmentByName(BROKER_SAMPLE_APP_CREATE); + assertThat(applicationEnvironments.getUserProvided().get("SPRING_APPLICATION_JSON")).asString() + .contains("\"ENV_VAR_1\":\"value1\""); + assertThat(applicationEnvironments.getUserProvided().get("SPRING_APPLICATION_JSON")).asString() + .contains("\"ENV_VAR_2\":\"value2\""); + } + } \ No newline at end of file diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/DeleteInstanceAcceptanceTest.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/DeleteInstanceAcceptanceTest.java new file mode 100644 index 0000000..52f4b0e --- /dev/null +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/DeleteInstanceAcceptanceTest.java @@ -0,0 +1,58 @@ +/* + * 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.acceptance; + +import java.util.Optional; +import org.cloudfoundry.operations.applications.ApplicationSummary; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.newArrayList; +import static reactor.util.function.Tuples.of; + +class DeleteInstanceAcceptanceTest extends CloudFoundryAcceptanceTest { + + private static final String BROKER_SAMPLE_APP_DELETE = "broker-sample-app-delete"; + + @BeforeEach + void setUp() { + initializeBroker(newArrayList( + of("spring.cloud.appbroker.apps[0].name", BROKER_SAMPLE_APP_DELETE), + of("spring.cloud.appbroker.apps[0].path", "classpath:demo.jar") + ) + ); + } + + @Test + void shouldDeleteAppsWhenDeleteServiceCalled() { + // given a service instance is created + createServiceInstance(); + + // and a backing application is deployed + Optional backingApplication = getApplicationSummaryByName(BROKER_SAMPLE_APP_DELETE); + assertThat(backingApplication).isNotEmpty(); + + // when the service instance is deleted + deleteServiceInstance(); + + // then the backing application is deleted + Optional backingApplicationAfterDeletion = getApplicationSummaryByName(BROKER_SAMPLE_APP_DELETE); + assertThat(backingApplicationAfterDeletion).isEmpty(); + } +} \ No newline at end of file diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryClientConfiguration.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryClientConfiguration.java new file mode 100644 index 0000000..3ccca56 --- /dev/null +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryClientConfiguration.java @@ -0,0 +1,97 @@ +/* + * 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.acceptance.fixtures.cf; + +import java.util.Optional; +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.operations.CloudFoundryOperations; +import org.cloudfoundry.operations.DefaultCloudFoundryOperations; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.DefaultConnectionContext; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient; +import org.cloudfoundry.reactor.doppler.ReactorDopplerClient; +import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider; +import org.cloudfoundry.reactor.uaa.ReactorUaaClient; +import org.cloudfoundry.uaa.UaaClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(CloudFoundryProperties.class) +public class CloudFoundryClientConfiguration { + + @Bean + ReactorCloudFoundryClient cloudFoundryClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { + return ReactorCloudFoundryClient.builder() + .connectionContext(connectionContext) + .tokenProvider(tokenProvider) + .build(); + } + + @Bean + CloudFoundryOperations cloudFoundryOperations(CloudFoundryProperties properties, CloudFoundryClient client, + DopplerClient dopplerClient, UaaClient uaaClient) { + return DefaultCloudFoundryOperations.builder() + .cloudFoundryClient(client) + .dopplerClient(dopplerClient) + .uaaClient(uaaClient) + .organization(properties.getDefaultOrg()) + .space(properties.getDefaultSpace()) + .build(); + } + + @Bean + DefaultConnectionContext connectionContext(CloudFoundryProperties properties) { + return DefaultConnectionContext.builder() + .apiHost(properties.getApiHost()) + .port(Optional.ofNullable(properties.getApiPort())) + .skipSslValidation(properties.isSkipSslValidation()) + .secure(properties.isSecure()) + .build(); + } + + @Bean + ReactorDopplerClient dopplerClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { + return ReactorDopplerClient.builder() + .connectionContext(connectionContext) + .tokenProvider(tokenProvider) + .build(); + } + + @Bean + @ConditionalOnProperty({CloudFoundryProperties.PROPERTY_PREFIX + ".username", + CloudFoundryProperties.PROPERTY_PREFIX + ".password"}) + PasswordGrantTokenProvider tokenProvider(CloudFoundryProperties properties) { + return PasswordGrantTokenProvider.builder() + .password(properties.getPassword()) + .username(properties.getUsername()) + .build(); + } + + @Bean + ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { + return ReactorUaaClient.builder() + .connectionContext(connectionContext) + .tokenProvider(tokenProvider) + .build(); + } + +} diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryProperties.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryProperties.java new file mode 100644 index 0000000..807ce11 --- /dev/null +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryProperties.java @@ -0,0 +1,113 @@ +/* + * 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.acceptance.fixtures.cf; + +import java.net.URI; +import org.cloudfoundry.reactor.ProxyConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +import static org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryProperties.PROPERTY_PREFIX; + +@ConfigurationProperties(PROPERTY_PREFIX) +public class CloudFoundryProperties { + + static final String PROPERTY_PREFIX = "spring.cloud.appbroker.deployer.cloudfoundry"; + + private String apiHost; + private Integer apiPort; + private String defaultOrg; + private String defaultSpace; + private String password; + private boolean secure = true; + private boolean skipSslValidation; + private String username; + + public String getApiHost() { + return apiHost; + } + + public void setApiHost(String apiHost) { + this.apiHost = parseApiHost(apiHost); + } + + public Integer getApiPort() { + return apiPort; + } + + public void setApiPort(int apiPort) { + this.apiPort = apiPort; + } + + public String getDefaultOrg() { + return defaultOrg; + } + + public void setDefaultOrg(String defaultOrg) { + this.defaultOrg = defaultOrg; + } + + public String getDefaultSpace() { + return defaultSpace; + } + + public void setDefaultSpace(String defaultSpace) { + this.defaultSpace = defaultSpace; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public ProxyConfiguration getProxyConfiguration() { + return null; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public boolean isSecure() { + return secure; + } + + public void setSecure(boolean secure) { + this.secure = secure; + } + + public boolean isSkipSslValidation() { + return skipSslValidation; + } + + public void setSkipSslValidation(boolean skipSslValidation) { + this.skipSslValidation = skipSslValidation; + } + + private static String parseApiHost(String api) { + final URI uri = URI.create(api); + return uri.getHost() == null ? api : uri.getHost(); + } + +} diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java new file mode 100644 index 0000000..3afbed1 --- /dev/null +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java @@ -0,0 +1,210 @@ +/* + * 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.acceptance.fixtures.cf; + +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import org.cloudfoundry.operations.CloudFoundryOperations; +import org.cloudfoundry.operations.applications.ApplicationEnvironments; +import org.cloudfoundry.operations.applications.ApplicationSummary; +import org.cloudfoundry.operations.applications.DeleteApplicationRequest; +import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest; +import org.cloudfoundry.operations.applications.GetApplicationRequest; +import org.cloudfoundry.operations.applications.PushApplicationRequest; +import org.cloudfoundry.operations.applications.SetEnvironmentVariableApplicationRequest; +import org.cloudfoundry.operations.applications.StartApplicationRequest; +import org.cloudfoundry.operations.serviceadmin.CreateServiceBrokerRequest; +import org.cloudfoundry.operations.serviceadmin.DeleteServiceBrokerRequest; +import org.cloudfoundry.operations.serviceadmin.EnableServiceAccessRequest; +import org.cloudfoundry.operations.services.CreateServiceInstanceRequest; +import org.cloudfoundry.operations.services.DeleteServiceInstanceRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +@Service +public class CloudFoundryService { + + private static final Logger LOGGER = LoggerFactory.getLogger(CloudFoundryService.class); + + @Autowired + private CloudFoundryOperations cloudFoundryOperations; + + @Autowired + private CloudFoundryProperties cloudFoundryProperties; + + public void enableServiceBrokerAccess(String serviceName) { + cloudFoundryOperations + .serviceAdmin() + .enableServiceAccess(EnableServiceAccessRequest.builder().serviceName(serviceName).build()) + .block(); + } + + public void createServiceBroker(String brokerName, String backingAppURL) { + cloudFoundryOperations + .serviceAdmin() + .create(CreateServiceBrokerRequest.builder() + .name(brokerName) + .username("user") + .password("password") + .url(backingAppURL) + .build()) + .block(); + } + + public String getApplicationRoute(String appName) { + return "https://" + cloudFoundryOperations + .applications() + .get(GetApplicationRequest.builder().name(appName).build()) + .block().getUrls().get(0); + } + + public void startApplication(String appName) { + cloudFoundryOperations + .applications() + .start(StartApplicationRequest.builder().name(appName).build()) + .block(); + } + + public void pushAppNoStart(String appName, Path appPath) { + cloudFoundryOperations + .applications() + .push(PushApplicationRequest + .builder() + .noStart(true) + .path(appPath) + .name(appName) + .build()) + .block(); + } + + public void deleteBackingApp(String appName) { + try { + cloudFoundryOperations + .applications() + .delete(DeleteApplicationRequest.builder().name(appName).build()) + .block(); + } catch (Exception e) { + // Ignore + } + } + + public void deleteServiceBroker(String brokerName) { + try { + cloudFoundryOperations + .serviceAdmin() + .delete(DeleteServiceBrokerRequest.builder().name(brokerName).build()) + .block(); + } catch (Exception e) { + // Ignore + } + } + + public void deleteServiceInstance(String serviceInstanceName) { + try { + cloudFoundryOperations + .services() + .deleteInstance(DeleteServiceInstanceRequest.builder().name(serviceInstanceName).build()) + .block(); + } catch (Exception e) { + // Ignore + } + } + + public void createServiceInstance(String planName, String serviceName, String serviceInstanceName) { + cloudFoundryOperations + .services() + .createInstance(CreateServiceInstanceRequest + .builder() + .planName(planName) + .serviceName(serviceName) + .serviceInstanceName(serviceInstanceName) + .build()) + .block(); + } + + private static SetEnvironmentVariableApplicationRequest createEnvRequest(String appName, String key, String value) { + return SetEnvironmentVariableApplicationRequest + .builder() + .name(appName) + .variableName(key) + .variableValue(value) + .build(); + } + + public List getApplications() { + return cloudFoundryOperations.applications().list().collectList().block(); + } + + public ApplicationEnvironments getApplicationEnvironmentByAppName(String appName) { + return cloudFoundryOperations + .applications() + .getEnvironments(GetApplicationEnvironmentsRequest.builder().name(appName).build()) + .block(); + } + + public void setBrokerAppEnvironment(List> properties) { + Flux catalogPublishers = getCatalogPublishers(); + Flux appBrokerCFPublishers = getAppBrokerCFPublishers(); + Flux appBrokerApplicationPublishers = Flux.concat(properties + .stream() + .map(tuple -> setEnvRequest(tuple.getT1(), tuple.getT2())) + .collect(Collectors.toList())); + + Flux.concat(catalogPublishers, appBrokerCFPublishers, appBrokerApplicationPublishers).blockLast(); + } + + private Flux getAppBrokerCFPublishers() { + return Flux.concat( + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.api-host", cloudFoundryProperties.getApiHost()), + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.api-port", String.valueOf(cloudFoundryProperties.getApiPort())), + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.username", cloudFoundryProperties.getUsername()), + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.password", cloudFoundryProperties.getPassword()), + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.default-org", cloudFoundryProperties.getDefaultOrg()), + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.default-space", cloudFoundryProperties.getDefaultSpace()), + setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation", String.valueOf(cloudFoundryProperties.isSkipSslValidation())) + ); + } + + private Flux getCatalogPublishers() { + return Flux.concat( + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].id", "example-service"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].name", "example"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].description", "A simple example"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].bindable", "true"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].tags[0]", "example"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].id", "simple-plan"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].bindable", "true"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].name", "standard"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].description", "A simple plan"), + setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].free", "true") + ); + } + + private Mono setEnvRequest(String key, String value) { + return cloudFoundryOperations + .applications() + .setEnvironmentVariable(createEnvRequest("sample-broker", key, value)) + .doOnSuccess(v -> LOGGER.info("Environment with key {} set", key)); + } + +} diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/resources/application.yml b/spring-cloud-app-broker-acceptance-tests/src/test/resources/application.yml new file mode 100644 index 0000000..3474d8a --- /dev/null +++ b/spring-cloud-app-broker-acceptance-tests/src/test/resources/application.yml @@ -0,0 +1,3 @@ +logging: + level: + cloudfoundry-client: DEBUG \ No newline at end of file