Flattened Bindings

Another common way to use bindings, even without auto-configuration is to
refer to them in a flattened form within other properties. An example would be
`spring.datasource.password=cnb.bindings.my-db.secret.password`.  This change
adds an EnvironmentPostProcessor that contributes these flattened bindings in
a PropertySource.

[resolves #24]

Signed-off-by: Ben Hale <bhale@vmware.com>
This commit is contained in:
Ben Hale
2020-05-11 11:26:16 -07:00
parent 0fd27ed8c1
commit c4721e9510
22 changed files with 381 additions and 148 deletions

View File

@@ -25,7 +25,7 @@
<properties>
<java.version>1.8</java.version>
<jetbrains-annotations.version>19.0.0</jetbrains-annotations.version>
<jsr305.version>3.0.2</jsr305.version>
<junit-pioneer.version>0.6.0</junit-pioneer.version>
<spring-boot.version>2.2.7.RELEASE</spring-boot.version>
@@ -54,9 +54,9 @@
<dependencies>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>${jetbrains-annotations.version}</version>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
<scope>provided</scope>
</dependency>
<dependency>

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.cloud.bindings;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -42,7 +40,7 @@ public final class Binding {
/**
* Creates a new {@code Binding} instance using the specified file system root.
*/
public Binding(@NotNull Path path) {
public Binding(Path path) {
this.name = path.getFileName().toString();
this.path = path;
this.metadata = createFilePerEntryMap(path.resolve("metadata"));
@@ -57,9 +55,7 @@ public final class Binding {
* @param metadata the metadata of the {@code Binding}.
* @param secret the secret of the {@code Binding}.
*/
public Binding(@NotNull String name, @NotNull Path path, @NotNull Map<String, String> metadata,
@NotNull Map<String, String> secret) {
public Binding(String name, Path path, Map<String, String> metadata, Map<String, String> secret) {
this.name = name;
this.path = path;
this.metadata = metadata;
@@ -69,42 +65,42 @@ public final class Binding {
/**
* Returns the name of the binding.
*/
public @NotNull String getName() {
public String getName() {
return name;
}
/**
* Returns the path of the binding.
*/
public @NotNull Path getPath() {
public Path getPath() {
return path;
}
/**
* Returns the metadata of the binding.
*/
public @NotNull Map<String, String> getMetadata() {
public Map<String, String> getMetadata() {
return metadata;
}
/**
* Returns the secret of the binding.
*/
public @NotNull Map<String, String> getSecret() {
public Map<String, String> getSecret() {
return secret;
}
/**
* Returns the kind of the binding. Equivalent to {@code getMetadata().get("kind")}.
*/
public @NotNull String getKind() {
public String getKind() {
return metadata.get("kind");
}
/**
* Returns the provider of the binding. Equivalent to {@code getMetadata().get("provider")}.
*/
public @NotNull String getProvider() {
public String getProvider() {
return metadata.get("provider");
}
@@ -113,7 +109,7 @@ public final class Binding {
*
* @param name the name of the metadata key.
*/
public @NotNull Path getMetadataFilePath(@NotNull String name) {
public Path getMetadataFilePath(String name) {
return this.path.resolve("metadata").resolve(name);
}
@@ -122,7 +118,7 @@ public final class Binding {
*
* @param name the name of the secret key.
*/
public @NotNull Path getSecretFilePath(@NotNull String name) {
public Path getSecretFilePath(String name) {
return this.path.resolve("secret").resolve(name);
}
@@ -142,7 +138,7 @@ public final class Binding {
return Objects.hash(name, path, metadata, secret);
}
private @NotNull Map<String, String> createFilePerEntryMap(@NotNull Path path) {
private Map<String, String> createFilePerEntryMap(Path path) {
try {
return Files.list(path)
.collect(Collectors.toMap(

View File

@@ -15,14 +15,12 @@
*/
package org.springframework.cloud.bindings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.lang.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -58,7 +56,7 @@ public final class Bindings {
*
* @param path the path to populate the {@code Bindings} from.
*/
public Bindings(String path) {
public Bindings(@Nullable String path) {
if (path == null) {
this.bindings = Collections.emptyList();
return;
@@ -89,14 +87,14 @@ public final class Bindings {
*
* @param bindings the {@code Binding}s.
*/
public Bindings(@NotNull Binding... bindings) {
public Bindings(Binding... bindings) {
this.bindings = Arrays.asList(bindings);
}
/**
* Returns all the {@link Binding}s that were found during construction.
*/
public @NotNull List<Binding> getBindings() {
public List<Binding> getBindings() {
return bindings;
}
@@ -106,14 +104,12 @@ public final class Bindings {
* @param name the name of the {@code Binding} to find.
* @return the {@code Binding} with a given name if it exists, {@code null} otherwise.
*/
public @Nullable Binding findBinding(@NotNull String name) {
for (Binding binding : bindings) {
if (binding.getName().equalsIgnoreCase(name)) {
return binding;
}
}
return null;
@Nullable
public Binding findBinding(String name) {
return bindings.stream()
.filter(binding -> binding.getName().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
}
/**
@@ -122,7 +118,7 @@ public final class Bindings {
* @param kind the kind of the {@code Binding} to find.
* @return the collection of {@code Binding}s with a given kind.
*/
public @NotNull List<Binding> filterBindings(@Nullable String kind) {
public List<Binding> filterBindings(@Nullable String kind) {
return filterBindings(kind, null);
}
@@ -134,18 +130,12 @@ public final class Bindings {
* @param provider the provider of {@code Binding} to find
* @return the collection of {@code Binding}s with a given kind and provider.
*/
public @NotNull List<Binding> filterBindings(@Nullable String kind, @Nullable String provider) {
List<Binding> filtered = new ArrayList<>();
for (Binding binding : bindings) {
if ((kind == null || binding.getKind().equalsIgnoreCase(kind)) &&
(provider == null) || binding.getProvider().equalsIgnoreCase(provider)) {
filtered.add(binding);
}
}
return filtered;
public List<Binding> filterBindings(@Nullable String kind, @Nullable String provider) {
return bindings.stream()
.filter(binding ->
(kind == null || binding.getKind().equalsIgnoreCase(kind)) &&
(provider == null) || binding.getProvider().equalsIgnoreCase(provider))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 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
*
* 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.bindings.boot;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.cloud.bindings.boot.PropertySourceContributor.contributePropertySource;
/**
* An implementation of {@link EnvironmentPostProcessor} that generates properties from {@link Bindings} with a
* flattened format: {@code cnb.bindings.{name}.{metadata,secret}.*}.
*/
public final class BindingFlattenedEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
public static final String BINDING_FLATTENED_PROPERTY_SOURCE_NAME = "cnbBindingFlattened";
private final Log log = LogFactory.getLog(getClass());
private final Bindings bindings;
/**
* Creates a new instance of {@code BindingFlattenedEnvironmentPostProcessor} using the {@link Bindings} available
* in the environment.
*/
public BindingFlattenedEnvironmentPostProcessor() {
this(new Bindings());
}
BindingFlattenedEnvironmentPostProcessor(Bindings bindings) {
this.bindings = bindings;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> properties = new HashMap<>();
bindings.getBindings().forEach(binding -> {
binding.getMetadata().forEach((key, value) -> {
properties.put(String.format("cnb.bindings.%s.metadata.%s", binding.getName(), key), value);
});
binding.getSecret().forEach((key, value) -> {
properties.put(String.format("cnb.bindings.%s.secret.%s", binding.getName(), key), value);
});
});
if (properties.isEmpty()) {
log.debug("No properties set from CNB Bindings. Skipping PropertySource creation.");
return;
}
log.info("Creating flattened PropertySource from CNB Bindings");
contributePropertySource(BINDING_FLATTENED_PROPERTY_SOURCE_NAME, properties, environment);
}
@Override
public int getOrder() {
// Before ConfigFileApplicationListener so values there can use values from {@link Bindings}.
return ConfigFileApplicationListener.DEFAULT_ORDER - 1;
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.bindings.boot;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.env.EnvironmentPostProcessor;
@@ -26,8 +25,6 @@ import org.springframework.cloud.bindings.Bindings;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -37,7 +34,7 @@ import java.util.List;
import java.util.Map;
import static org.springframework.cloud.bindings.boot.Guards.isGlobalEnabled;
import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
import static org.springframework.cloud.bindings.boot.PropertySourceContributor.contributePropertySource;
/**
* An implementation of {@link EnvironmentPostProcessor} that delegates properties generation from {@link Bindings}.
@@ -45,14 +42,15 @@ import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LIN
* {@link BindingsPropertiesProcessor} registered with {@link SpringFactoriesLoader} allowing them to generate any
* properties from the contents of the {@code Bindings}.
* <p>
* Must be enabled by setting the {@code org.springframework.cloud.bindings.boot.enable} System Property to {@code true}.
* Must be enabled by setting the {@code org.springframework.cloud.bindings.boot.enable} System Property to
* {@code true}.
*/
public final class BindingsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
public final class BindingSpecificEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* The name of the {@link PropertySource} created by the {@code BindingsEnvironmentPostProcessor}: {@value}.
*/
public static final String BINDINGS_PROPERTY_SOURCE_NAME = "cnbBindings";
public static final String BINDING_SPECIFIC_PROPERTY_SOURCE_NAME = "cnbBindingSpecific";
final List<BindingsPropertiesProcessor> processors;
@@ -61,27 +59,23 @@ public final class BindingsEnvironmentPostProcessor implements EnvironmentPostPr
private final Bindings bindings;
/**
* Creates a new instance of {@code BindingsEnvironmentPostProcessor} using the {@link Bindings} available in the
* environment and the {@link BindingsPropertiesProcessor}s registered with {@link SpringFactoriesLoader}.
* Creates a new instance of {@code BindingSpecificEnvironmentPostProcessor} using the {@link Bindings} available in
* the environment and the {@link BindingsPropertiesProcessor}s registered with {@link SpringFactoriesLoader}.
*/
public BindingsEnvironmentPostProcessor() {
public BindingSpecificEnvironmentPostProcessor() {
this.bindings = new Bindings();
this.processors = SpringFactoriesLoader.
loadFactories(BindingsPropertiesProcessor.class, getClass().getClassLoader());
AnnotationAwareOrderComparator.sort(this.processors);
}
BindingsEnvironmentPostProcessor(@NotNull Bindings bindings,
@NotNull BindingsPropertiesProcessor... processors) {
BindingSpecificEnvironmentPostProcessor(Bindings bindings, BindingsPropertiesProcessor... processors) {
this.bindings = bindings;
this.processors = Arrays.asList(processors);
}
@Override
public void postProcessEnvironment(@NotNull ConfigurableEnvironment environment,
@NotNull SpringApplication application) {
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (!isGlobalEnabled()) {
return;
}
@@ -92,23 +86,14 @@ public final class BindingsEnvironmentPostProcessor implements EnvironmentPostPr
}
Map<String, Object> properties = new HashMap<>();
for (BindingsPropertiesProcessor processor : processors) {
processor.process(bindings, properties);
}
processors.forEach(processor -> processor.process(bindings, properties));
if (properties.isEmpty()) {
log.debug("No properties set from CNB Bindings. Skipping PropertySource creation.");
return;
}
log.info("Creating PropertySource from CNB Bindings");
MutablePropertySources propertySources = environment.getPropertySources();
MapPropertySource propertySource = new MapPropertySource(BINDINGS_PROPERTY_SOURCE_NAME, properties);
if (propertySources.contains(COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(COMMAND_LINE_PROPERTY_SOURCE_NAME, propertySource);
} else {
propertySources.addFirst(propertySource);
}
log.info("Creating binding-specific PropertySource from CNB Bindings");
contributePropertySource(BINDING_SPECIFIC_PROPERTY_SOURCE_NAME, properties, environment);
}
@Override

View File

@@ -17,7 +17,6 @@
package org.springframework.cloud.bindings.boot;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -34,6 +33,6 @@ public interface BindingsPropertiesProcessor {
* @param bindings the {@code Bindings} exposed to the application.
* @param properties the currently accumulated properties.
*/
void process(@NonNull Bindings bindings, @NonNull Map<String, Object> properties);
void process(Bindings bindings, Map<String, Object> properties);
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -36,7 +34,7 @@ public final class CassandraBindingsPropertiesProcessor implements BindingsPrope
public static final String KIND = "Cassandra";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -38,7 +36,7 @@ public final class Db2BindingsPropertiesProcessor implements BindingsPropertiesP
public static final String KIND = "DB2";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
final class Guards {
static boolean isGlobalEnabled() {
@@ -25,7 +23,7 @@ final class Guards {
return Boolean.parseBoolean(value);
}
static boolean isKindEnabled(@NotNull String kind) {
static boolean isKindEnabled(String kind) {
String property = String.format("org.springframework.cloud.bindings.boot.%s.enable", kind.toLowerCase());
String value = System.getProperty(property, "true");
return Boolean.parseBoolean(value);

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.bindings.boot;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -35,7 +34,7 @@ public final class MongoDbBindingsPropertiesProcessor implements BindingsPropert
public static final String KIND = "MongoDB";
@Override
public void process(@NonNull Bindings bindings, @NonNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -38,7 +36,7 @@ public final class MySqlBindingsPropertiesProcessor implements BindingsPropertie
public static final String KIND = "MySQL";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -38,7 +36,7 @@ public final class OracleBindingsPropertiesProcessor implements BindingsProperti
public static final String KIND = "Oracle";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -38,7 +36,7 @@ public final class PostgreSqlBindingsPropertiesProcessor implements BindingsProp
public static final String KIND = "PostgreSQL";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 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
*
* 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.bindings.boot;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import java.util.Map;
import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
final class PropertySourceContributor {
static void contributePropertySource(String name, Map<String, Object> properties, ConfigurableEnvironment environment) {
MapPropertySource propertySource = new MapPropertySource(name, properties);
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains(COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(COMMAND_LINE_PROPERTY_SOURCE_NAME, propertySource);
} else {
propertySources.addFirst(propertySource);
}
}
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -36,7 +34,7 @@ public final class RedisBindingsPropertiesProcessor implements BindingsPropertie
public static final String KIND = "Redis";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -16,10 +16,8 @@
package org.springframework.cloud.bindings.boot;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.lang.NonNull;
import java.util.Map;
@@ -38,7 +36,7 @@ public final class SqlServerBindingsPropertiesProcessor implements BindingsPrope
public static final String KIND = "SQLServer";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
public void process(Bindings bindings, Map<String, Object> properties) {
if (!isKindEnabled(KIND)) {
return;
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 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
*
* 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.
*/
@NonNullApi
package org.springframework.cloud.bindings.boot;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 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
*
* 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.
*/
@NonNullApi
package org.springframework.cloud.bindings;
import org.springframework.lang.NonNullApi;

View File

@@ -1,5 +1,5 @@
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bindings.boot.BindingsEnvironmentPostProcessor
org.springframework.cloud.bindings.boot.BindingSpecificEnvironmentPostProcessor
# Included implementations
org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\
org.springframework.cloud.bindings.boot.CassandraBindingsPropertiesProcessor, \

View File

@@ -0,0 +1,94 @@
/*
* Copyright 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
*
* 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.bindings.boot;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.ClearSystemProperty;
import org.junitpioneer.jupiter.SetSystemProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.mock.env.MockEnvironment;
import java.nio.file.Paths;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Binding-flattened EnvironmentPostProcessor")
final class BindingFlattenedEnvironmentPostProcessorTest {
private final SpringApplication application = new SpringApplication();
private final MockEnvironment environment = new MockEnvironment();
@Test
@DisplayName("is disabled by default")
@ClearSystemProperty(key = "org.springframework.cloud.bindings.boot.enable")
void disabledByDefault() {
new BindingFlattenedEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.emptyMap(), Collections.emptyMap())
)
).postProcessEnvironment(environment, application);
assertThat(environment.getPropertySources()).hasSize(1);
}
@Nested
@DisplayName("when enabled")
@SetSystemProperty(key = "org.springframework.cloud.bindings.boot.enable", value = "true")
final class Enabled {
@Test
@DisplayName("does not create PropertySource if no bindings")
void noBindings() {
new BindingFlattenedEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application);
assertThat(environment.getPropertySources()).hasSize(1);
}
@Test
@DisplayName("creates PropertySource with properties")
void containsProperties() {
new BindingFlattenedEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.singletonMap("test-metadata-key", "test-metadata-value"),
Collections.singletonMap("test-secret-key", "test-secret-value"))
)
).postProcessEnvironment(environment, application);
assertThat(environment.getPropertySources()).hasSize(2);
assertThat(environment.getProperty("cnb.bindings.test-name.metadata.test-metadata-key")).isEqualTo("test-metadata-value");
assertThat(environment.getProperty("cnb.bindings.test-name.secret.test-secret-key")).isEqualTo("test-secret-value");
}
@Test
@DisplayName("has order before ConfigFileApplicationListener")
void order() {
assertThat(new BindingFlattenedEnvironmentPostProcessor(new Bindings()).getOrder())
.isLessThan(ConfigFileApplicationListener.DEFAULT_ORDER);
}
}
}

View File

@@ -25,18 +25,15 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.mock.env.MockEnvironment;
import java.nio.file.Paths;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.bindings.boot.BindingsEnvironmentPostProcessor.BINDINGS_PROPERTY_SOURCE_NAME;
@DisplayName("Bindings EnvironmentPostProcessor")
final class BindingsEnvironmentPostProcessorTest {
@DisplayName("Binding-specific EnvironmentPostProcessor")
final class BindingSpecificEnvironmentPostProcessorTest {
private final SpringApplication application = new SpringApplication();
@@ -46,7 +43,7 @@ final class BindingsEnvironmentPostProcessorTest {
@DisplayName("is disabled by default")
@ClearSystemProperty(key = "org.springframework.cloud.bindings.boot.enable")
void disabledByDefault() {
new BindingsEnvironmentPostProcessor(
new BindingSpecificEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.emptyMap(), Collections.emptyMap())
@@ -65,7 +62,7 @@ final class BindingsEnvironmentPostProcessorTest {
@Test
@DisplayName("does not create PropertySource if no bindings")
void noBindings() {
new BindingsEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application);
new BindingSpecificEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application);
assertThat(environment.getPropertySources()).hasSize(1);
}
@@ -73,7 +70,7 @@ final class BindingsEnvironmentPostProcessorTest {
@Test
@DisplayName("does not create PropertySource if no properties")
void noProperties() {
new BindingsEnvironmentPostProcessor(
new BindingSpecificEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.emptyMap(), Collections.emptyMap())
@@ -86,7 +83,7 @@ final class BindingsEnvironmentPostProcessorTest {
@Test
@DisplayName("creates PropertySource with properties")
void containsProperties() {
new BindingsEnvironmentPostProcessor(
new BindingSpecificEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.emptyMap(), Collections.emptyMap())
@@ -98,51 +95,17 @@ final class BindingsEnvironmentPostProcessorTest {
assertThat(environment.getProperty("test-key")).isEqualTo("test-value");
}
@Test
@DisplayName("adds PropertySource after CommandLinePropertySource")
void withCommandLinePropertySource() {
environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource());
new BindingsEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.emptyMap(), Collections.emptyMap())
),
(environment, properties) -> properties.put("test-key", "test-value")
).postProcessEnvironment(environment, application);
PropertySource<?> propertySource = environment.getPropertySources().get(BINDINGS_PROPERTY_SOURCE_NAME);
assertThat(propertySource).isNotNull();
assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(1);
}
@Test
@DisplayName("adds PropertySource first")
void withoutCommandLinePropertySource() {
new BindingsEnvironmentPostProcessor(
new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.emptyMap(), Collections.emptyMap())
),
(environment, properties) -> properties.put("test-key", "test-value")
).postProcessEnvironment(environment, application);
PropertySource<?> propertySource = environment.getPropertySources().get(BINDINGS_PROPERTY_SOURCE_NAME);
assertThat(propertySource).isNotNull();
assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(0);
}
@Test
@DisplayName("has order before ConfigFileApplicationListener")
void order() {
assertThat(new BindingsEnvironmentPostProcessor(new Bindings()).getOrder())
assertThat(new BindingSpecificEnvironmentPostProcessor(new Bindings()).getOrder())
.isLessThan(ConfigFileApplicationListener.DEFAULT_ORDER);
}
@Test
@DisplayName("included implementations are registered")
void includedImplementations() {
assertThat(new BindingsEnvironmentPostProcessor().processors).hasSize(8);
assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(8);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 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
*
* 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.bindings.boot;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.mock.env.MockEnvironment;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.bindings.boot.PropertySourceContributor.contributePropertySource;
@DisplayName("PropertySource Contributor")
final class PropertySourceContributorTest {
private final MockEnvironment environment = new MockEnvironment();
@Test
@DisplayName("adds PropertySource after CommandLinePropertySource")
void withCommandLinePropertySource() {
environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource());
contributePropertySource("test-name", Collections.singletonMap("test-key", "test-value"), environment);
PropertySource<?> propertySource = environment.getPropertySources().get("test-name");
assertThat(propertySource).isNotNull();
assertThat(propertySource.getProperty("test-key")).isEqualTo("test-value");
assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(1);
}
@Test
@DisplayName("adds PropertySource first")
void withoutCommandLinePropertySource() {
contributePropertySource("test-name", Collections.singletonMap("test-key", "test-value"), environment);
PropertySource<?> propertySource = environment.getPropertySources().get("test-name");
assertThat(propertySource).isNotNull();
assertThat(propertySource.getProperty("test-key")).isEqualTo("test-value");
assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(0);
}
}