From c7d2b7124b360206e06214282e093640bbb428db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Thu, 3 Dec 2020 23:24:45 -0600 Subject: [PATCH] Add aws-secretsmanager: prefix config import (spring-cloud/spring-cloud-aws#721) In `spring-boot` 2.4, `Volume Mounted Config Directory Trees` was added. This commit introduces the prefix `aws-secretsmanager:` which will resolve the values given the configuration properties supported by secrets manager integration. Also, if keys are added after the prefix then just these will be resolved. Use: `aws-secretsmanager:` or `aws-secretsmanager:my-secret-key` or `aws-secretsmanager:my-secret-key;my-anoter-secret-key` Closes spring-cloud/spring-cloud-aws#655 Closes spring-cloud/spring-cloud-aws#515 Co-authored-by: Maciej Walkowiak --- docs/src/main/asciidoc/secrets-manager.adoc | 16 ++ pom.xml | 4 +- spring-cloud-aws-dependencies/pom.xml | 2 +- ...wsSecretsManagerPropertySourceLocator.java | 56 ++----- .../AwsSecretsManagerPropertySources.java | 94 +++++++++++ ...sSecretsManagerBootstrapConfiguration.java | 14 ++ .../AwsSecretsManagerConfigDataLoader.java | 48 ++++++ ...retsManagerConfigDataLocationResolver.java | 148 ++++++++++++++++++ .../AwsSecretsManagerConfigDataResource.java | 79 ++++++++++ .../main/resources/META-INF/spring.factories | 8 + ...ManagerConfigDataLocationResolverTest.java | 91 +++++++++++ 11 files changed, 511 insertions(+), 49 deletions(-) create mode 100644 spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySources.java create mode 100644 spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLoader.java create mode 100644 spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolver.java create mode 100644 spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataResource.java create mode 100644 spring-cloud-starter-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolverTest.java diff --git a/docs/src/main/asciidoc/secrets-manager.adoc b/docs/src/main/asciidoc/secrets-manager.adoc index 5c15a42a4..bef0501a9 100644 --- a/docs/src/main/asciidoc/secrets-manager.adoc +++ b/docs/src/main/asciidoc/secrets-manager.adoc @@ -69,3 +69,19 @@ dots, dashes, forward slashes, backward slashes and underscores next to alphanum |`true` |Can be used to disable the Secrets Manager Configuration support even though the auto-configuration is on the classpath. |=== + +In `spring-cloud` `2020.0.0` (aka Ilford), the bootstrap phase is no longer enabled by default. In order +enable it you need an additional dependency: + +[source,xml,indent=0] +---- + + org.springframework.cloud + spring-cloud-starter-bootstrap + {spring-cloud-version} + +---- + +However, starting at `spring-cloud-aws` `2.3`, allows import default aws' secretsmanager keys +(`spring.config.import=aws-secretsmanager:`) or individual keys +(`spring.config.import=aws-secretsmanager:secret-key;other-secret-key`) diff --git a/pom.xml b/pom.xml index 40ff78010..2dadac45c 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-build - 3.0.0-M4 + 3.0.0-SNAPSHOT @@ -48,7 +48,7 @@ 1.5.5 2.8.2 1.2.0 - 3.0.0-M4 + 3.0.0-SNAPSHOT 0.0.25 diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml index 2c1f87493..e78b01774 100644 --- a/spring-cloud-aws-dependencies/pom.xml +++ b/spring-cloud-aws-dependencies/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-dependencies-parent - 2.3.2.BUILD-SNAPSHOT + 3.0.0-SNAPSHOT spring-cloud-aws-dependencies diff --git a/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java b/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java index b6129d6ff..1daadb6bb 100644 --- a/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java +++ b/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java @@ -31,7 +31,6 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; -import org.springframework.util.ReflectionUtils; /** * Builds a {@link CompositePropertySource} with various @@ -41,17 +40,18 @@ * * @author Fabio Maia * @author Matej Nedic + * @author Eddú Meléndez * @since 2.0.0 */ public class AwsSecretsManagerPropertySourceLocator implements PropertySourceLocator { - private String propertySourceName; + private final String propertySourceName; - private AWSSecretsManager smClient; + private final AWSSecretsManager smClient; - private AwsSecretsManagerProperties properties; + private final AwsSecretsManagerProperties properties; - private final Set contexts = new LinkedHashSet(); + private final Set contexts = new LinkedHashSet<>(); private Log logger = LogFactory.getLog(getClass()); @@ -78,56 +78,20 @@ public PropertySource locate(Environment environment) { ConfigurableEnvironment env = (ConfigurableEnvironment) environment; - String appName = properties.getName(); - - if (appName == null) { - appName = env.getProperty("spring.application.name"); - } + AwsSecretsManagerPropertySources sources = new AwsSecretsManagerPropertySources(properties, logger); List profiles = Arrays.asList(env.getActiveProfiles()); - - String prefix = this.properties.getPrefix(); - - String appContext = prefix + "/" + appName; - addProfiles(this.contexts, appContext, profiles); - this.contexts.add(appContext); - - String defaultContext = prefix + "/" + this.properties.getDefaultContext(); - addProfiles(this.contexts, defaultContext, profiles); - this.contexts.add(defaultContext); + this.contexts.addAll(sources.getAutomaticContexts(profiles)); CompositePropertySource composite = new CompositePropertySource(this.propertySourceName); for (String propertySourceContext : this.contexts) { - try { - composite.addPropertySource(create(propertySourceContext)); - } - catch (Exception e) { - if (this.properties.isFailFast()) { - logger.error( - "Fail fast is set and there was an error reading configuration from AWS Secrets Manager:\n" - + e.getMessage()); - ReflectionUtils.rethrowRuntimeException(e); - } - else { - logger.warn("Unable to load AWS secret from " + propertySourceContext, e); - } - } + PropertySource propertySource = sources.createPropertySource(propertySourceContext, true, + this.smClient); + composite.addPropertySource(propertySource); } return composite; } - private AwsSecretsManagerPropertySource create(String context) { - AwsSecretsManagerPropertySource propertySource = new AwsSecretsManagerPropertySource(context, this.smClient); - propertySource.init(); - return propertySource; - } - - private void addProfiles(Set contexts, String baseContext, List profiles) { - for (String profile : profiles) { - contexts.add(baseContext + this.properties.getProfileSeparator() + profile); - } - } - } diff --git a/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySources.java b/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySources.java new file mode 100644 index 000000000..9ff2b9557 --- /dev/null +++ b/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySources.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013-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.aws.secretsmanager; + +import java.util.ArrayList; +import java.util.List; + +import com.amazonaws.services.secretsmanager.AWSSecretsManager; +import org.apache.commons.logging.Log; + +import org.springframework.util.StringUtils; + +public class AwsSecretsManagerPropertySources { + + private final AwsSecretsManagerProperties properties; + + private final Log log; + + public AwsSecretsManagerPropertySources(AwsSecretsManagerProperties properties, Log log) { + this.properties = properties; + this.log = log; + } + + public List getAutomaticContexts(List profiles) { + List contexts = new ArrayList<>(); + String prefix = this.properties.getPrefix(); + String defaultContext = getContext(prefix, this.properties.getDefaultContext()); + + String appName = this.properties.getName(); + + String appContext = prefix + "/" + appName; + addProfiles(contexts, appContext, profiles); + contexts.add(appContext); + + addProfiles(contexts, defaultContext, profiles); + contexts.add(defaultContext); + return contexts; + } + + protected String getContext(String prefix, String context) { + if (StringUtils.hasLength(prefix)) { + return prefix + "/" + context; + } + return context; + } + + private void addProfiles(List contexts, String baseContext, List profiles) { + for (String profile : profiles) { + contexts.add(baseContext + this.properties.getProfileSeparator() + profile); + } + } + + public AwsSecretsManagerPropertySource createPropertySource(String context, boolean optional, + AWSSecretsManager client) { + try { + AwsSecretsManagerPropertySource propertySource = new AwsSecretsManagerPropertySource(context, client); + propertySource.init(); + return propertySource; + // TODO: howto call close when /refresh + } + catch (Exception e) { + if (this.properties.isFailFast() || !optional) { + throw new AwsSecretsManagerPropertySourceNotFoundException(e); + } + else { + log.warn("Unable to load AWS secret from " + context, e); + } + } + return null; + } + + static class AwsSecretsManagerPropertySourceNotFoundException extends RuntimeException { + + AwsSecretsManagerPropertySourceNotFoundException(Exception source) { + super(source); + } + + } + +} diff --git a/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerBootstrapConfiguration.java b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerBootstrapConfiguration.java index ddcbeccf4..2d5f6d0f2 100644 --- a/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerBootstrapConfiguration.java +++ b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerBootstrapConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySourceLocator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; /** * Spring Cloud Bootstrap Configuration for setting up an @@ -46,15 +47,28 @@ @ConditionalOnProperty(prefix = AwsSecretsManagerProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true) public class AwsSecretsManagerBootstrapConfiguration { + private final Environment environment; + + public AwsSecretsManagerBootstrapConfiguration(Environment environment) { + this.environment = environment; + } + @Bean AwsSecretsManagerPropertySourceLocator awsSecretsManagerPropertySourceLocator(AWSSecretsManager smClient, AwsSecretsManagerProperties properties) { + if (StringUtils.isNullOrEmpty(properties.getName())) { + properties.setName(this.environment.getProperty("spring.application.name")); + } return new AwsSecretsManagerPropertySourceLocator(smClient, properties); } @Bean @ConditionalOnMissingBean AWSSecretsManager smClient(AwsSecretsManagerProperties properties) { + return createSecretsManagerClient(properties); + } + + public static AWSSecretsManager createSecretsManagerClient(AwsSecretsManagerProperties properties) { AWSSecretsManagerClientBuilder builder = AWSSecretsManagerClientBuilder.standard() .withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration()); if (!StringUtils.isNullOrEmpty(properties.getRegion())) { diff --git a/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLoader.java b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLoader.java new file mode 100644 index 000000000..586f2ef88 --- /dev/null +++ b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLoader.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-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.aws.autoconfigure.secretsmanager; + +import java.util.Collections; + +import com.amazonaws.services.secretsmanager.AWSSecretsManager; + +import org.springframework.boot.context.config.ConfigData; +import org.springframework.boot.context.config.ConfigDataLoader; +import org.springframework.boot.context.config.ConfigDataLoaderContext; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; +import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySource; + +/** + * @author Eddú Meléndez + * @since 2.3.0 + */ +public class AwsSecretsManagerConfigDataLoader implements ConfigDataLoader { + + @Override + public ConfigData load(ConfigDataLoaderContext context, AwsSecretsManagerConfigDataResource resource) { + try { + AWSSecretsManager ssm = context.getBootstrapContext().get(AWSSecretsManager.class); + AwsSecretsManagerPropertySource propertySource = resource.getPropertySources() + .createPropertySource(resource.getContext(), resource.isOptional(), ssm); + return new ConfigData(Collections.singletonList(propertySource)); + } + catch (Exception e) { + throw new ConfigDataResourceNotFoundException(resource, e); + } + } + +} diff --git a/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolver.java b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolver.java new file mode 100644 index 000000000..ff2e368ee --- /dev/null +++ b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolver.java @@ -0,0 +1,148 @@ +/* + * Copyright 2013-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.aws.autoconfigure.secretsmanager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.amazonaws.services.secretsmanager.AWSSecretsManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; +import org.springframework.boot.context.config.ConfigDataLocationResolver; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.Profiles; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerProperties; +import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySources; +import org.springframework.util.StringUtils; + +/** + * @author Eddú Meléndez + * @since 2.3.0 + */ +public class AwsSecretsManagerConfigDataLocationResolver + implements ConfigDataLocationResolver { + + private static final Log log = LogFactory.getLog(AwsSecretsManagerConfigDataLocationResolver.class); + + /** + * AWS Parameter Store Config Data prefix. + */ + public static final String PREFIX = "aws-secretsmanager:"; + + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + if (!location.hasPrefix(PREFIX)) { + return false; + } + return context.getBinder().bind(AwsSecretsManagerProperties.CONFIG_PREFIX + ".enabled", Boolean.class) + .orElse(true); + } + + @Override + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) throws ConfigDataLocationNotFoundException { + return Collections.emptyList(); + } + + @Override + public List resolveProfileSpecific( + ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location, Profiles profiles) + throws ConfigDataLocationNotFoundException { + registerBean(resolverContext, AwsSecretsManagerProperties.class, loadProperties(resolverContext.getBinder())); + + registerAndPromoteBean(resolverContext, AWSSecretsManager.class, this::createAwsSecretsManagerClient); + + AwsSecretsManagerProperties properties = loadConfigProperties(resolverContext.getBinder()); + + AwsSecretsManagerPropertySources propertySources = new AwsSecretsManagerPropertySources(properties, log); + + List contexts = location.getValue().equals(PREFIX) + ? propertySources.getAutomaticContexts(profiles.getAccepted()) + : getCustomContexts(location.getNonPrefixedValue(PREFIX)); + + List locations = new ArrayList<>(); + contexts.forEach( + propertySourceContext -> locations.add(new AwsSecretsManagerConfigDataResource(propertySourceContext, + location.isOptional(), propertySources))); + + return locations; + } + + private List getCustomContexts(String keys) { + if (StringUtils.hasLength(keys)) { + return Arrays.asList(keys.split(";")); + } + return Collections.emptyList(); + } + + protected void registerAndPromoteBean(ConfigDataLocationResolverContext context, Class type, + BootstrapRegistry.InstanceSupplier supplier) { + registerBean(context, type, supplier); + context.getBootstrapContext().addCloseListener(event -> { + T instance = event.getBootstrapContext().get(type); + event.getApplicationContext().getBeanFactory().registerSingleton("configData" + type.getSimpleName(), + instance); + }); + } + + public void registerBean(ConfigDataLocationResolverContext context, Class type, T instance) { + context.getBootstrapContext().registerIfAbsent(type, BootstrapRegistry.InstanceSupplier.of(instance)); + } + + protected void registerBean(ConfigDataLocationResolverContext context, Class type, + BootstrapRegistry.InstanceSupplier supplier) { + ConfigurableBootstrapContext bootstrapContext = context.getBootstrapContext(); + bootstrapContext.registerIfAbsent(type, supplier); + } + + protected AWSSecretsManager createAwsSecretsManagerClient(BootstrapContext context) { + AwsSecretsManagerProperties properties = context.get(AwsSecretsManagerProperties.class); + + return AwsSecretsManagerBootstrapConfiguration.createSecretsManagerClient(properties); + } + + protected AwsSecretsManagerProperties loadProperties(Binder binder) { + AwsSecretsManagerProperties awsSecretsManagerProperties = binder + .bind(AwsSecretsManagerProperties.CONFIG_PREFIX, Bindable.of(AwsSecretsManagerProperties.class)) + .orElseGet(AwsSecretsManagerProperties::new); + + return awsSecretsManagerProperties; + } + + protected AwsSecretsManagerProperties loadConfigProperties(Binder binder) { + AwsSecretsManagerProperties properties = binder + .bind(AwsSecretsManagerProperties.CONFIG_PREFIX, Bindable.of(AwsSecretsManagerProperties.class)) + .orElseGet(AwsSecretsManagerProperties::new); + + if (!StringUtils.hasLength(properties.getName())) { + properties.setName(binder.bind("spring.application.name", String.class).orElse("application")); + } + + return properties; + } + +} diff --git a/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataResource.java b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataResource.java new file mode 100644 index 000000000..f4c51de8d --- /dev/null +++ b/spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataResource.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-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.aws.autoconfigure.secretsmanager; + +import java.util.Objects; + +import org.springframework.boot.context.config.ConfigDataResource; +import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySources; +import org.springframework.core.style.ToStringCreator; + +/** + * @author Eddú Meléndez + * @since 2.3.0 + */ +public class AwsSecretsManagerConfigDataResource extends ConfigDataResource { + + private final String context; + + private final boolean optional; + + private final AwsSecretsManagerPropertySources propertySources; + + public AwsSecretsManagerConfigDataResource(String context, boolean optional, + AwsSecretsManagerPropertySources propertySources) { + this.context = context; + this.optional = optional; + this.propertySources = propertySources; + } + + public String getContext() { + return this.context; + } + + public boolean isOptional() { + return this.optional; + } + + public AwsSecretsManagerPropertySources getPropertySources() { + return this.propertySources; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AwsSecretsManagerConfigDataResource that = (AwsSecretsManagerConfigDataResource) o; + return this.optional == that.optional && this.context.equals(that.context); + } + + @Override + public int hashCode() { + return Objects.hash(this.optional, this.context); + } + + @Override + public String toString() { + return new ToStringCreator(this).append("context", context).append("optional", optional).toString(); + + } + +} diff --git a/spring-cloud-starter-aws-secrets-manager-config/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-aws-secrets-manager-config/src/main/resources/META-INF/spring.factories index 8535e7f9d..7c622e7b4 100644 --- a/spring-cloud-starter-aws-secrets-manager-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-starter-aws-secrets-manager-config/src/main/resources/META-INF/spring.factories @@ -1,2 +1,10 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.aws.autoconfigure.secretsmanager.AwsSecretsManagerBootstrapConfiguration + +# ConfigData Location Resolvers +org.springframework.boot.context.config.ConfigDataLocationResolver=\ +org.springframework.cloud.aws.autoconfigure.secretsmanager.AwsSecretsManagerConfigDataLocationResolver + +# ConfigData Loaders +org.springframework.boot.context.config.ConfigDataLoader=\ +org.springframework.cloud.aws.autoconfigure.secretsmanager.AwsSecretsManagerConfigDataLoader diff --git a/spring-cloud-starter-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolverTest.java b/spring-cloud-starter-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolverTest.java new file mode 100644 index 000000000..7754b0a54 --- /dev/null +++ b/spring-cloud-starter-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLocationResolverTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-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.aws.autoconfigure.secretsmanager; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.Profiles; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AwsSecretsManagerConfigDataLocationResolverTest { + + @Test + void testResolveProfileSpecificWithAutomaticPaths() { + String location = "aws-secretsmanager:"; + List locations = testResolveProfileSpecific(location); + assertThat(locations).hasSize(4); + assertThat(toContexts(locations)).containsExactly("/secret/testapp_dev", "/secret/testapp", + "/secret/application_dev", "/secret/application"); + } + + @Test + void testResolveProfileSpecificWithCustomPaths() { + String location = "aws-secretsmanager:/mypath1;/mypath2;/mypath3"; + List locations = testResolveProfileSpecific(location); + assertThat(locations).hasSize(3); + assertThat(toContexts(locations)).containsExactly("/mypath1", "/mypath2", "/mypath3"); + } + + private List toContexts(List locations) { + return locations.stream().map(AwsSecretsManagerConfigDataResource::getContext).collect(Collectors.toList()); + } + + private List testResolveProfileSpecific(String location) { + AwsSecretsManagerConfigDataLocationResolver resolver = createResolver(); + ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class); + MockEnvironment env = new MockEnvironment(); + env.setProperty("spring.application.name", "testapp"); + when(context.getBinder()).thenReturn(Binder.get(env)); + Profiles profiles = mock(Profiles.class); + when(profiles.getAccepted()).thenReturn(Collections.singletonList("dev")); + return resolver.resolveProfileSpecific(context, ConfigDataLocation.of(location), profiles); + } + + private AwsSecretsManagerConfigDataLocationResolver createResolver() { + return new AwsSecretsManagerConfigDataLocationResolver() { + @Override + public void registerBean(ConfigDataLocationResolverContext context, Class type, T instance) { + // do nothing + } + + @Override + protected void registerBean(ConfigDataLocationResolverContext context, Class type, + BootstrapRegistry.InstanceSupplier supplier) { + // do nothing + } + + @Override + protected void registerAndPromoteBean(ConfigDataLocationResolverContext context, Class type, + BootstrapRegistry.InstanceSupplier supplier) { + // do nothing + } + }; + } + +}