Adding the ability to customize the way creation parameters are mapped to SPRING_APPLICATION_JSON
Some trade-offs: The `ParametersTransformer` interface takes all the backing apps because otherwise we wouldn't allow users to config properties for each one of them. I went for the simple solution of adding the `Bean` instead of going all the way through strategy matched via config. Closes #22
This commit is contained in:
@@ -7,3 +7,11 @@ image:https://codecov.io/gh/spring-cloud-incubator/spring-cloud-app-broker/branc
|
||||
|
||||
Spring Cloud App Broker is a project for implementing service broker functionality within a Spring Boot application.
|
||||
|
||||
Spring Cloud App Broker allows you to implement different strategies for different steps.
|
||||
|
||||
== Implementing a custom ParametersTransformer
|
||||
|
||||
By default, the parameters created via `-c '{"key", "value"}'` will be added to `SPRING_APPLICATION_JSON`.
|
||||
In the case, the same properties were provided via `spring.cloud.appbroker.apps[0].environment.key=value`. The one provided via `-c` will take precedence.
|
||||
|
||||
The default implementation can be overwritten by just creating a `Bean` that implements the `ParametersTransformer` interface as in `CreateInstanceWithCustomCreationParametersComponentTest`
|
||||
|
||||
@@ -18,16 +18,19 @@ package org.springframework.cloud.appbroker.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingApplications;
|
||||
import org.springframework.cloud.appbroker.deployer.DeployerClient;
|
||||
import org.springframework.cloud.appbroker.deployer.ReactiveAppDeployer;
|
||||
import org.springframework.cloud.appbroker.service.WorkflowServiceInstanceService;
|
||||
import org.springframework.cloud.appbroker.state.InMemoryServiceInstanceStateRepository;
|
||||
import org.springframework.cloud.appbroker.state.ServiceInstanceStateRepository;
|
||||
import org.springframework.cloud.appbroker.service.WorkflowServiceInstanceService;
|
||||
import org.springframework.cloud.appbroker.workflow.instance.CreateServiceInstanceWorkflow;
|
||||
import org.springframework.cloud.appbroker.workflow.instance.DefaultParametersTransformer;
|
||||
import org.springframework.cloud.appbroker.workflow.instance.DeleteServiceInstanceWorkflow;
|
||||
import org.springframework.cloud.appbroker.workflow.instance.ParametersTransformer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -59,10 +62,17 @@ public class AppBrokerAutoConfiguration {
|
||||
return new InMemoryServiceInstanceStateRepository();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ParametersTransformer.class)
|
||||
public ParametersTransformer parametersTransformer() {
|
||||
return new DefaultParametersTransformer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CreateServiceInstanceWorkflow createServiceInstanceWorkflow(BackingApplications backingApplications,
|
||||
BackingAppDeploymentService backingAppDeploymentService) {
|
||||
return new CreateServiceInstanceWorkflow(backingApplications, backingAppDeploymentService);
|
||||
BackingAppDeploymentService backingAppDeploymentService,
|
||||
ParametersTransformer parametersTransformer) {
|
||||
return new CreateServiceInstanceWorkflow(backingApplications, backingAppDeploymentService, parametersTransformer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -75,6 +85,6 @@ public class AppBrokerAutoConfiguration {
|
||||
public WorkflowServiceInstanceService serviceInstanceService(ServiceInstanceStateRepository stateRepository,
|
||||
CreateServiceInstanceWorkflow createWorkflow,
|
||||
DeleteServiceInstanceWorkflow deleteWorkflow) {
|
||||
return new WorkflowServiceInstanceService(stateRepository,createWorkflow, deleteWorkflow);
|
||||
return new WorkflowServiceInstanceService(stateRepository, createWorkflow, deleteWorkflow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.appbroker.workflow.instance;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingApplications;
|
||||
@@ -29,25 +28,18 @@ public class CreateServiceInstanceWorkflow {
|
||||
|
||||
private BackingApplications backingApps;
|
||||
private BackingAppDeploymentService deploymentService;
|
||||
private ParametersTransformer parametersTransformer;
|
||||
|
||||
public CreateServiceInstanceWorkflow(BackingApplications backingApps,
|
||||
BackingAppDeploymentService deploymentService) {
|
||||
BackingAppDeploymentService deploymentService,
|
||||
ParametersTransformer parametersTransformer) {
|
||||
this.backingApps = backingApps;
|
||||
this.deploymentService = deploymentService;
|
||||
this.parametersTransformer = parametersTransformer;
|
||||
}
|
||||
|
||||
public Mono<String> create(Map<String, Object> parameters) {
|
||||
backingApps.forEach(backingApplication -> {
|
||||
final Map<String, String> environment = new HashMap<>();
|
||||
final Map<String, String> backingAppEnvironment = backingApplication.getEnvironment();
|
||||
if (backingAppEnvironment != null) {
|
||||
environment.putAll(backingAppEnvironment);
|
||||
}
|
||||
if (parameters != null) {
|
||||
parameters.forEach((key, value) -> environment.put(key, value.toString()));
|
||||
}
|
||||
backingApplication.setEnvironment(environment);
|
||||
});
|
||||
parametersTransformer.transform(backingApps, parameters);
|
||||
|
||||
return deploymentService.deploy(backingApps)
|
||||
.doOnRequest(l -> log.info("Deploying applications {}", backingApps))
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.workflow.instance;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingApplications;
|
||||
|
||||
public class DefaultParametersTransformer implements ParametersTransformer {
|
||||
|
||||
public void transform(BackingApplications backingApps, Map<String, Object> parameters) {
|
||||
backingApps.forEach(backingApplication -> {
|
||||
final Map<String, String> environment = new HashMap<>();
|
||||
final Map<String, String> backingAppEnvironment = backingApplication.getEnvironment();
|
||||
if (backingAppEnvironment != null) {
|
||||
environment.putAll(backingAppEnvironment);
|
||||
}
|
||||
if (parameters != null) {
|
||||
parameters.forEach((key, value) -> environment.put(key, value.toString()));
|
||||
}
|
||||
backingApplication.setEnvironment(environment);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.workflow.instance;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingApplications;
|
||||
|
||||
public interface ParametersTransformer {
|
||||
|
||||
void transform(BackingApplications backingApps, Map<String, Object> parameters);
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.appbroker.workflow.instance;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -40,6 +41,12 @@ class CreateServiceInstanceWorkflowTest {
|
||||
|
||||
@Mock
|
||||
private BackingAppDeploymentService backingAppDeploymentService;
|
||||
private ParametersTransformer parametersTransformer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
parametersTransformer = new DefaultParametersTransformer();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateServiceInstance() {
|
||||
@@ -47,8 +54,11 @@ class CreateServiceInstanceWorkflowTest {
|
||||
.willReturn(Mono.just("deployment-id-app1"));
|
||||
|
||||
// given that properties contains app details
|
||||
parametersTransformer = new DefaultParametersTransformer();
|
||||
CreateServiceInstanceWorkflow createServiceInstanceWorkflow =
|
||||
new CreateServiceInstanceWorkflow(createBackingApplications(), backingAppDeploymentService);
|
||||
new CreateServiceInstanceWorkflow(createBackingApplications(),
|
||||
backingAppDeploymentService,
|
||||
parametersTransformer);
|
||||
|
||||
// when
|
||||
createServiceInstanceWorkflow.create(Collections.emptyMap());
|
||||
@@ -71,7 +81,9 @@ class CreateServiceInstanceWorkflowTest {
|
||||
// given that properties contains app details and environment variables
|
||||
Map<String, String> environment = singletonMap("ENV_VAR_1", "value from environment");
|
||||
CreateServiceInstanceWorkflow createServiceInstanceWorkflow =
|
||||
new CreateServiceInstanceWorkflow(createBackingApplicationWithEnvironment(environment), backingAppDeploymentService);
|
||||
new CreateServiceInstanceWorkflow(createBackingApplicationWithEnvironment(environment),
|
||||
backingAppDeploymentService,
|
||||
parametersTransformer);
|
||||
|
||||
// when
|
||||
createServiceInstanceWorkflow.create(Collections.emptyMap());
|
||||
@@ -93,7 +105,9 @@ class CreateServiceInstanceWorkflowTest {
|
||||
|
||||
// given that properties contains app details
|
||||
CreateServiceInstanceWorkflow createServiceInstanceWorkflow =
|
||||
new CreateServiceInstanceWorkflow(createBackingApplications(), backingAppDeploymentService);
|
||||
new CreateServiceInstanceWorkflow(createBackingApplications(),
|
||||
backingAppDeploymentService,
|
||||
parametersTransformer);
|
||||
|
||||
// when create is called with parameters
|
||||
Map<String, Object> parameters = singletonMap("ENV_VAR_1", "value from parameters");
|
||||
@@ -117,7 +131,9 @@ class CreateServiceInstanceWorkflowTest {
|
||||
// given that properties contains app details and environment variables
|
||||
Map<String, String> environment = singletonMap("ENV_VAR_1", "value from environment");
|
||||
CreateServiceInstanceWorkflow createServiceInstanceWorkflow =
|
||||
new CreateServiceInstanceWorkflow(createBackingApplicationWithEnvironment(environment), backingAppDeploymentService);
|
||||
new CreateServiceInstanceWorkflow(createBackingApplicationWithEnvironment(environment),
|
||||
backingAppDeploymentService,
|
||||
parametersTransformer);
|
||||
|
||||
// when create is called with parameters
|
||||
Map<String, Object> parameters = singletonMap("ENV_VAR_1", "value from parameters");
|
||||
@@ -151,7 +167,9 @@ class CreateServiceInstanceWorkflowTest {
|
||||
.build();
|
||||
|
||||
CreateServiceInstanceWorkflow createServiceInstanceWorkflow =
|
||||
new CreateServiceInstanceWorkflow(backingApplications, backingAppDeploymentService);
|
||||
new CreateServiceInstanceWorkflow(backingApplications,
|
||||
backingAppDeploymentService,
|
||||
parametersTransformer);
|
||||
|
||||
// when create is called with parameters
|
||||
Map<String, Object> parameters = singletonMap("ENV_VAR_1", "value from parameters");
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.sample;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.appbroker.deployer.BackingApplications;
|
||||
import org.springframework.cloud.appbroker.sample.fixtures.CloudControllerStubFixture;
|
||||
import org.springframework.cloud.appbroker.sample.fixtures.OpenServiceBrokerApiFixture;
|
||||
import org.springframework.cloud.appbroker.workflow.instance.ParametersTransformer;
|
||||
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 com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath;
|
||||
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.sample.CreateInstanceWithCustomCreationParametersComponentTest.APP_NAME;
|
||||
|
||||
@TestPropertySource(properties = {
|
||||
"spring.cloud.appbroker.apps[0].path=classpath:demo.jar",
|
||||
"spring.cloud.appbroker.apps[0].name=" + APP_NAME
|
||||
})
|
||||
@ContextConfiguration(classes = CreateInstanceWithCustomCreationParametersComponentTest.CustomConfig.class)
|
||||
class CreateInstanceWithCustomCreationParametersComponentTest extends WiremockComponentTest {
|
||||
static final String APP_NAME = "app-with-request-create-params";
|
||||
|
||||
@Autowired
|
||||
private OpenServiceBrokerApiFixture brokerFixture;
|
||||
|
||||
@Autowired
|
||||
private CloudControllerStubFixture cloudControllerFixture;
|
||||
|
||||
@Test
|
||||
void shouldPushAppWithEnvironmentWhenCreateServiceEndpointCalledWithCreationParameters() {
|
||||
cloudControllerFixture.stubAppDoesNotExist(APP_NAME);
|
||||
cloudControllerFixture.stubPushApp(APP_NAME,
|
||||
matchingJsonPath("$.environment_json[?(@.SPRING_APPLICATION_JSON =~ /.*otherNestedKey.*:.*otherKey.*:.*keyValue.*/)]"),
|
||||
matchingJsonPath("$.environment_json[?(@.SPRING_APPLICATION_JSON =~ /.*otherNestedKey.*:.*otherLabel.*:.*labelValue.*/)]"));
|
||||
|
||||
// given a set of parameters
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("firstKey", "{\"label\":\"labelValue\",\"secondKey\":\"keyValue\"}");
|
||||
|
||||
// when a service instance is created
|
||||
given(brokerFixture.serviceInstanceRequest(params))
|
||||
.when()
|
||||
.put(brokerFixture.createServiceInstanceUrl(), "instance-id")
|
||||
.then()
|
||||
.statusCode(HttpStatus.ACCEPTED.value());
|
||||
|
||||
// when the "last_operation" API is polled
|
||||
given(brokerFixture.serviceInstanceRequest())
|
||||
.when()
|
||||
.get(brokerFixture.getLastInstanceOperationUrl(), "instance-id")
|
||||
.then()
|
||||
.statusCode(HttpStatus.OK.value())
|
||||
.body("state", is(equalTo(OperationState.IN_PROGRESS.toString())));
|
||||
|
||||
String state = brokerFixture.waitForAsyncOperationComplete("instance-id");
|
||||
assertThat(state).isEqualTo(OperationState.SUCCEEDED.toString());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomConfig {
|
||||
@Bean
|
||||
public ParametersTransformer parametersTransformer() {
|
||||
return new CustomParametersTransformer();
|
||||
}
|
||||
|
||||
public class CustomParametersTransformer implements ParametersTransformer {
|
||||
@Override
|
||||
public void transform(BackingApplications backingApps, Map<String, Object> parameters) {
|
||||
backingApps.forEach(backingApplication -> backingApplication.setEnvironment(createEnvironmentMap(parameters)));
|
||||
}
|
||||
|
||||
private Map<String, String> createEnvironmentMap(Map<String, Object> parameters) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
ObjectNode customOutputEnvironmentParameters = objectMapper.createObjectNode();
|
||||
try {
|
||||
CustomInputParameters customInputParameters = objectMapper.readValue(parameters.get("firstKey").toString(), CustomInputParameters.class);
|
||||
customOutputEnvironmentParameters.put("otherKey", customInputParameters.getSecondKey());
|
||||
customOutputEnvironmentParameters.put("otherLabel", customInputParameters.getLabel());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Collections.singletonMap("otherNestedKey", customOutputEnvironmentParameters.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomInputParameters {
|
||||
|
||||
private CustomInputParameters() {
|
||||
}
|
||||
|
||||
private String secondKey;
|
||||
private String label;
|
||||
|
||||
String getSecondKey() {
|
||||
return secondKey;
|
||||
}
|
||||
|
||||
String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setSecondKey(String secondKey) {
|
||||
this.secondKey = secondKey;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user