diff --git a/docs/src/main/asciidoc/parameter-store.adoc b/docs/src/main/asciidoc/parameter-store.adoc index 98d2549ee..5cdbc481f 100644 --- a/docs/src/main/asciidoc/parameter-store.adoc +++ b/docs/src/main/asciidoc/parameter-store.adoc @@ -61,6 +61,33 @@ spring.config.import[1]=aws-parameterstore=/config/optional-params/ If you add indexed parameter names such as `/config/application/cloud.aws.stack_0_.name`, `/config/application/cloud.aws.stack_1_.name`, ... to Parameter Store, these will become accessible as array properties `cloud.aws.stack[0].name`, `cloud.aws.stack[1].name`, ... in Spring. +==== Adding prefix to property keys + +To avoid property key collisions it is possible to configure a property key prefix that gets added to each resolved parameter. + +As an example, assuming the following parameters are stored under path `/config/my-datasource`: + +|=== +| Parameter Name | Parameter Value + +| `/config/my-datasource/url` | `jdbc:mysql://localhost:3306` + +| `/config/my-datasource/username` | `db-user` + +|=== + +By default, `url` and `username` properties will be added to the Spring environment. To add a prefix to property keys configure `spring.config.import` property with `?prefix=` added to the parameter path: + +[source,properties] +---- +spring.config.import=aws-parameterstore:/config/my-datasource/?prefix=spring.datasource. +---- + +With such config, properties `spring.datasource.url` and `spring.datasource.username` are added to the Spring environment. + +NOTE: Prefixes are added as-is to all property names returned by Parameter Store. If you want key names to be separated with a dot between the prefix and key name, make sure to add a trailing dot to the prefix. + + === Using SsmClient The starter automatically configures and registers a `SsmClient` bean in the Spring application context. The `SsmClient` bean can be used to create or retrieve parameters from Parameter Store. diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java index 70ad29118..e66c6bff1 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java @@ -76,6 +76,7 @@ class ParameterStoreConfigDataLoaderIntegrationTests { static void beforeAll() { putParameter(localstack, "/config/spring/message", "value from tests", REGION); putParameter(localstack, "/config/spring/another-parameter", "another parameter value", REGION); + putParameter(localstack, "/config/second/secondMessage", "second value from tests", REGION); } @Test @@ -91,6 +92,22 @@ void resolvesPropertyFromParameterStore() { } } + @Test + void resolvesPropertiesWithPrefixes() { + SpringApplication application = new SpringApplication(App.class); + application.setWebApplicationType(WebApplicationType.NONE); + + try (ConfigurableApplicationContext context = runApplication(application, + "aws-parameterstore:/config/spring/?prefix=first.;/config/second/?prefix=second.")) { + assertThat(context.getEnvironment().getProperty("first.message")).isEqualTo("value from tests"); + assertThat(context.getEnvironment().getProperty("first.another-parameter")) + .isEqualTo("another parameter value"); + assertThat(context.getEnvironment().getProperty("second.secondMessage")) + .isEqualTo("second value from tests"); + assertThat(context.getEnvironment().getProperty("non-existing-parameter")).isNull(); + } + } + @Test void clientIsConfiguredWithConfigurerProvidedToBootstrapRegistry() { SpringApplication application = new SpringApplication(App.class); diff --git a/spring-cloud-aws-parameter-store/src/main/java/io/awspring/cloud/parameterstore/ParameterStorePropertySource.java b/spring-cloud-aws-parameter-store/src/main/java/io/awspring/cloud/parameterstore/ParameterStorePropertySource.java index c9e8d2457..cedac2146 100644 --- a/spring-cloud-aws-parameter-store/src/main/java/io/awspring/cloud/parameterstore/ParameterStorePropertySource.java +++ b/spring-cloud-aws-parameter-store/src/main/java/io/awspring/cloud/parameterstore/ParameterStorePropertySource.java @@ -40,20 +40,31 @@ public class ParameterStorePropertySource extends AwsPropertySource properties = new LinkedHashMap<>(); public ParameterStorePropertySource(String context, SsmClient ssmClient) { super("aws-parameterstore:" + context, ssmClient); this.context = context; + this.parameterPath = resolveParameterPath(context); + this.prefix = resolvePrefix(context); } @Override public void init() { - GetParametersByPathRequest paramsRequest = GetParametersByPathRequest.builder().path(context).recursive(true) - .withDecryption(true).build(); + GetParametersByPathRequest paramsRequest = GetParametersByPathRequest.builder().path(parameterPath) + .recursive(true).withDecryption(true).build(); getParameters(paramsRequest); } @@ -76,13 +87,45 @@ public Object getProperty(String name) { private void getParameters(GetParametersByPathRequest paramsRequest) { GetParametersByPathResponse paramsResult = this.source.getParametersByPath(paramsRequest); for (Parameter parameter : paramsResult.parameters()) { - String key = parameter.name().replace(this.context, "").replace('/', '.').replaceAll("_(\\d)_", "[$1]"); + String key = parameter.name().replace(this.parameterPath, "").replace('/', '.') + .replaceAll("_(\\d)_", "[$1]"); LOG.debug("Populating property retrieved from AWS Parameter Store: " + key); - this.properties.put(key, parameter.value()); + String propertyKey = prefix != null ? prefix + key : key; + this.properties.put(propertyKey, parameter.value()); } if (paramsResult.nextToken() != null) { getParameters(paramsRequest.toBuilder().nextToken(paramsResult.nextToken()).build()); } } + @Nullable + String getPrefix() { + return prefix; + } + + String getContext() { + return context; + } + + String getParameterPath() { + return parameterPath; + } + + @Nullable + private static String resolvePrefix(String context) { + int prefixIndex = context.indexOf(PREFIX_PART); + if (prefixIndex != -1) { + return context.substring(prefixIndex + PREFIX_PART.length()); + } + return null; + } + + private static String resolveParameterPath(String context) { + int prefixIndex = context.indexOf(PREFIX_PART); + if (prefixIndex != -1) { + return context.substring(0, prefixIndex); + } + return context; + } + } diff --git a/spring-cloud-aws-parameter-store/src/test/java/io/awspring/cloud/parameterstore/ParameterStorePropertySourceTest.java b/spring-cloud-aws-parameter-store/src/test/java/io/awspring/cloud/parameterstore/ParameterStorePropertySourceTest.java index 30ec1c170..827b0dfe8 100644 --- a/spring-cloud-aws-parameter-store/src/test/java/io/awspring/cloud/parameterstore/ParameterStorePropertySourceTest.java +++ b/spring-cloud-aws-parameter-store/src/test/java/io/awspring/cloud/parameterstore/ParameterStorePropertySourceTest.java @@ -90,4 +90,41 @@ void arrayParameterNames() { it.assertThat(propertySource.getProperty("key[1].nested[1].nestedValue")).isEqualTo("key_nestedValue2"); }); } + + @Test + void resolvesPrefixAndParameterPathFromContext() { + ParameterStorePropertySource propertySource = new ParameterStorePropertySource("/config/myservice/?prefix=xxx", + ssmClient); + assertThat(propertySource.getName()).isEqualTo("aws-parameterstore:/config/myservice/?prefix=xxx"); + assertThat(propertySource.getPrefix()).isEqualTo("xxx"); + assertThat(propertySource.getContext()).isEqualTo("/config/myservice/?prefix=xxx"); + assertThat(propertySource.getParameterPath()).isEqualTo("/config/myservice/"); + } + + @Test + void addsPrefixToParameter() { + ParameterStorePropertySource propertySource = new ParameterStorePropertySource("/config/myservice/?prefix=yyy.", + ssmClient); + + Parameter parameter = Parameter.builder().name("key1").value("my parameter").build(); + GetParametersByPathResponse parametersByPathResponse = GetParametersByPathResponse.builder() + .parameters(parameter).build(); + + when(ssmClient.getParametersByPath(any(GetParametersByPathRequest.class))).thenReturn(parametersByPathResponse); + + propertySource.init(); + + assertThat(propertySource.getPropertyNames()).containsExactly("yyy.key1"); + assertThat(propertySource.getProperty("yyy.key1")).isEqualTo("my parameter"); + assertThat(propertySource.getProperty("key1")).isNull(); + } + + @Test + void copyPreservesPrefix() { + ParameterStorePropertySource propertySource = new ParameterStorePropertySource("/config/myservice/?prefix=yyy", + ssmClient); + ParameterStorePropertySource copy = propertySource.copy(); + assertThat(propertySource.getContext()).isEqualTo(copy.getContext()); + assertThat(propertySource.getPrefix()).isEqualTo(copy.getPrefix()); + } }