diff --git a/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc b/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc index 0734ef0..ad7798e 100644 --- a/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc +++ b/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc @@ -212,6 +212,54 @@ If you use the `SpacePerServiceInstance` target, App Broker will deploy backing If you use the `ServiceInstanceGuidSuffix` target, App Broker will deploy backing applications using a unique name and hostname that incorporates the service instance GUID provided by the platform at service instance create time. For Cloud Foundry, the target location will be the org named by `spring.cloud.appbroker.deployer.cloudfoundry.default-org`, the space named by `spring.cloud.appbroker.deployer.cloudfoundry.default-space`, and an application name as `[APP-NAME]-[SI-GUID]`, where `[APP-NAME]` is the `name` listed for the application under `spring.cloud.appbroker.services.apps` and `[SI-GUID]` is the service instance GUID. The application will also use a hostname incorporating the service instance GUID as a suffix, as `[APP-NAME]-[SI-GUID]`. +====== Creating a custom Target + +If you want to create a custom Target, app broker provides a flexible way to add new targets by just creating a new `Bean` that extends from `TargetFactory` and implementing the `create` method: + +[source,java] +---- +public class CustomSpaceTarget extends TargetFactory { + + public CustomSpaceTarget() { + super(Config.class); + } + + @Override + public Target create(Config config) { + return this::apply; + } + + private ArtifactDetails apply(Map properties, String name, String serviceInstanceId) { + String space = "my-custom-space"; + properties.put(DeploymentProperties.TARGET_PROPERTY_KEY, space); + + return ArtifactDetails.builder() + .name(name) + .properties(properties) + .build(); + } + + public static class Config { + } + +} +---- + +Once configured, we can specify in our service the new custom Target: + +[source, yml, indent=0] +---- +spring: + cloud: + appbroker: + services: + - service-name: example + plan-name: standard + target: + name: CustomSpaceTarget +---- + + ===== Service Instance Parameters When a user provides parameters while creating or updating a service instance, App Broker can transform these parameters into details of the backing app deployment using parameters transformers. You can configure parameters transformers using properties under `parameters-transformers`, as in the following example: diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org/springframework/cloud/appbroker/integration/CreateInstanceWithCustomTargetComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org/springframework/cloud/appbroker/integration/CreateInstanceWithCustomTargetComponentTest.java new file mode 100644 index 0000000..2ed87bc --- /dev/null +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org/springframework/cloud/appbroker/integration/CreateInstanceWithCustomTargetComponentTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.integration; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.appbroker.deployer.DeploymentProperties; +import org.springframework.cloud.appbroker.extensions.targets.ArtifactDetails; +import org.springframework.cloud.appbroker.extensions.targets.Target; +import org.springframework.cloud.appbroker.extensions.targets.TargetFactory; +import org.springframework.cloud.appbroker.integration.fixtures.CloudControllerStubFixture; +import org.springframework.cloud.appbroker.integration.fixtures.OpenServiceBrokerApiFixture; +import org.springframework.cloud.servicebroker.model.instance.OperationState; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithSpacePerServiceInstanceTargetComponentTest.APP_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithSpacePerServiceInstanceTargetComponentTest.BACKING_SERVICE_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithSpacePerServiceInstanceTargetComponentTest.BACKING_SI_NAME; + +@TestPropertySource(properties = { + "spring.cloud.appbroker.services[0].service-name=example", + "spring.cloud.appbroker.services[0].plan-name=standard", + "spring.cloud.appbroker.services[0].apps[0].path=classpath:demo.jar", + "spring.cloud.appbroker.services[0].apps[0].name=" + APP_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + BACKING_SI_NAME, + + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_SERVICE_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard", + + "spring.cloud.appbroker.services[0].target.name=CustomSpaceTarget" +}) +@ContextConfiguration(classes = CreateInstanceWithCustomTargetComponentTest.CustomConfig.class) +class CreateInstanceWithCustomTargetComponentTest extends WiremockComponentTest { + + protected static final String APP_NAME = "app-new-services-target"; + + protected static final String BACKING_SI_NAME = "my-db-service"; + + protected static final String BACKING_SERVICE_NAME = "db-service"; + + protected static final String BACKING_PLAN_NAME = "standard"; + + @Autowired + private OpenServiceBrokerApiFixture brokerFixture; + + @Autowired + private CloudControllerStubFixture cloudControllerFixture; + + @Test + void pushAppWithServicesInSpace() { + String serviceInstanceId = "instance-id"; + String customSpace = "my-space"; + + cloudControllerFixture.stubCreateSpace(customSpace); + cloudControllerFixture.stubAssociatePermissions(customSpace); + cloudControllerFixture.stubPushApp(APP_NAME); + + // given services are available in the marketplace + cloudControllerFixture.stubServiceExists(BACKING_SERVICE_NAME, BACKING_PLAN_NAME); + + // will create and bind the service instance + cloudControllerFixture.stubCreateServiceInstance(BACKING_SI_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); + + // when a service instance is created + given(brokerFixture.serviceInstanceRequest()) + .when() + .put(brokerFixture.createServiceInstanceUrl(), serviceInstanceId) + .then() + .statusCode(HttpStatus.ACCEPTED.value()); + + // when the "last_operation" API is polled + given(brokerFixture.serviceInstanceRequest()) + .when() + .get(brokerFixture.getLastInstanceOperationUrl(), serviceInstanceId) + .then() + .statusCode(HttpStatus.OK.value()) + .body("state", is(equalTo(OperationState.IN_PROGRESS.toString()))); + + String state = brokerFixture.waitForAsyncOperationComplete(serviceInstanceId); + assertThat(state).isEqualTo(OperationState.SUCCEEDED.toString()); + } + + @Configuration + protected static class CustomConfig { + + @Bean + public CustomSpaceService customSpaceService() { + return new CustomSpaceService(); + } + + @Bean + public CustomSpaceTarget customSpaceTarget(CustomSpaceService customSpaceService) { + return new CustomSpaceTarget(customSpaceService); + } + + @SuppressWarnings({"PMD.UnusedFormalParameter"}) + static final class CustomSpaceTarget extends TargetFactory { + + private final CustomSpaceService customSpaceService; + + public CustomSpaceTarget(CustomSpaceService customSpaceService) { + super(Config.class); + this.customSpaceService = customSpaceService; + } + + @Override + public Target create(Config config) { + return this::apply; + } + + private ArtifactDetails apply(Map properties, String name, String serviceInstanceId) { + String space = customSpaceService.retrieveSpaceName(); + properties.put(DeploymentProperties.TARGET_PROPERTY_KEY, space); + + return ArtifactDetails.builder() + .name(name) + .properties(properties) + .build(); + } + + public static class Config { + } + + } + + static class CustomSpaceService { + + String retrieveSpaceName() { + return "my-space"; + } + + } + + } + +}