From 8c5238b048c6d5dea0aba06ad7cec27b68aeb11e Mon Sep 17 00:00:00 2001 From: Dennis Kieselhorst Date: Mon, 23 Oct 2023 14:41:49 +0200 Subject: [PATCH] Adds array properties support to parameter store (#852, merge #248 from 2.4.x) (#894) Co-authored-by: Rafael Pestano Fixes #852 --- docs/src/main/asciidoc/parameter-store.adoc | 3 ++ ...StoreConfigDataLoaderIntegrationTests.java | 27 ++++++++++++++++++ .../ParameterStorePropertySource.java | 6 ++-- .../ParameterStorePropertySourceTest.java | 28 +++++++++++++++++++ .../infrastructure/InfrastructureStack.java | 6 ++++ .../SpringCloudAwsParameterStoreSample.java | 8 +++--- 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/docs/src/main/asciidoc/parameter-store.adoc b/docs/src/main/asciidoc/parameter-store.adoc index b2e1b2cd8..98d2549ee 100644 --- a/docs/src/main/asciidoc/parameter-store.adoc +++ b/docs/src/main/asciidoc/parameter-store.adoc @@ -58,6 +58,9 @@ spring.config.import[0]=optional:aws-parameterstore=/config/spring 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. + === 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 54a731de7..70ad29118 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 @@ -218,6 +218,33 @@ void parameterStoreClientUsesGlobalRegion() { } } + @Test + void arrayParameterNames() { + SpringApplication application = new SpringApplication(App.class); + application.setWebApplicationType(WebApplicationType.NONE); + + putParameter(localstack, "/config/myservice/key_0_.value", "value1", REGION); + putParameter(localstack, "/config/myservice/key_0_.nested_0_.nestedValue", "key_nestedValue1", REGION); + putParameter(localstack, "/config/myservice/key_0_.nested_1_.nestedValue", "key_nestedValue2", REGION); + putParameter(localstack, "/config/myservice/key_1_.value", "value2", REGION); + putParameter(localstack, "/config/myservice/key_1_.nested_0_.nestedValue", "key_nestedValue3", REGION); + putParameter(localstack, "/config/myservice/key_1_.nested_1_.nestedValue", "key_nestedValue4", REGION); + + try (ConfigurableApplicationContext context = runApplication(application, + "aws-parameterstore:/config/myservice/")) { + assertThat(context.getEnvironment().getProperty("key[0].value")).isEqualTo("value1"); + assertThat(context.getEnvironment().getProperty("key[0].nested[0].nestedValue")) + .isEqualTo("key_nestedValue1"); + assertThat(context.getEnvironment().getProperty("key[0].nested[1].nestedValue")) + .isEqualTo("key_nestedValue2"); + assertThat(context.getEnvironment().getProperty("key[1].value")).isEqualTo("value2"); + assertThat(context.getEnvironment().getProperty("key[1].nested[0].nestedValue")) + .isEqualTo("key_nestedValue3"); + assertThat(context.getEnvironment().getProperty("key[1].nested[1].nestedValue")) + .isEqualTo("key_nestedValue4"); + } + } + @Nested class ReloadConfigurationTests { 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 e447a9928..c9e8d2457 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 @@ -18,7 +18,6 @@ import io.awspring.cloud.core.config.AwsPropertySource; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.lang.Nullable; @@ -65,8 +64,7 @@ public ParameterStorePropertySource copy() { @Override public String[] getPropertyNames() { - Set strings = this.properties.keySet(); - return strings.toArray(new String[strings.size()]); + return this.properties.keySet().stream().toArray(String[]::new); } @Override @@ -78,7 +76,7 @@ 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('/', '.'); + String key = parameter.name().replace(this.context, "").replace('/', '.').replaceAll("_(\\d)_", "[$1]"); LOG.debug("Populating property retrieved from AWS Parameter Store: " + key); this.properties.put(key, parameter.value()); } 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 0cddb2eec..30ec1c170 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 @@ -16,6 +16,7 @@ package io.awspring.cloud.parameterstore; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -62,4 +63,31 @@ void followsNextToken() { assertThat(propertySource.getProperty("key3")).isEqualTo("value3"); } + @Test + void arrayParameterNames() { + GetParametersByPathResponse result = GetParametersByPathResponse.builder() + .parameters(Parameter.builder().name("/config/myservice/key_0_.value").value("value1").build(), + Parameter.builder().name("/config/myservice/key_0_.nested_0_.nestedValue") + .value("key_nestedValue1").build(), + Parameter.builder().name("/config/myservice/key_0_.nested_1_.nestedValue") + .value("key_nestedValue2").build(), + Parameter.builder().name("/config/myservice/key_1_.value").value("value2").build(), + Parameter.builder().name("/config/myservice/key_1_.nested_0_.nestedValue") + .value("key_nestedValue1").build(), + Parameter.builder().name("/config/myservice/key_1_.nested_1_.nestedValue") + .value("key_nestedValue2").build()) + .build(); + + when(ssmClient.getParametersByPath(any(GetParametersByPathRequest.class))).thenReturn(result); + + propertySource.init(); + + assertSoftly(it -> { + it.assertThat(propertySource.getPropertyNames()).containsExactly("key[0].value", + "key[0].nested[0].nestedValue", "key[0].nested[1].nestedValue", "key[1].value", + "key[1].nested[0].nestedValue", "key[1].nested[1].nestedValue"); + it.assertThat(propertySource.getProperty("key[0].value")).isEqualTo("value1"); + it.assertThat(propertySource.getProperty("key[1].nested[1].nestedValue")).isEqualTo("key_nestedValue2"); + }); + } } diff --git a/spring-cloud-aws-samples/infrastructure/src/main/java/io/awspring/cloud/samples/infrastructure/InfrastructureStack.java b/spring-cloud-aws-samples/infrastructure/src/main/java/io/awspring/cloud/samples/infrastructure/InfrastructureStack.java index 7318e7d21..7cb71c1af 100644 --- a/spring-cloud-aws-samples/infrastructure/src/main/java/io/awspring/cloud/samples/infrastructure/InfrastructureStack.java +++ b/spring-cloud-aws-samples/infrastructure/src/main/java/io/awspring/cloud/samples/infrastructure/InfrastructureStack.java @@ -41,6 +41,12 @@ public InfrastructureStack(final Construct scope, final String id, final StackPr StringParameter.Builder.create(this, "Parameter").parameterName("/config/spring/message") .stringValue("Spring-cloud-aws value!").build(); + StringParameter.Builder.create(this, "Parameter2").parameterName("/config/spring/messages_0_") + .stringValue("Spring-cloud-aws msg0!").build(); + + StringParameter.Builder.create(this, "Parameter3").parameterName("/config/spring/messages_1_") + .stringValue("Spring-cloud-aws msg1!").build(); + // Secrets Manager SecretStringGenerator secretStringGenerator = SecretStringGenerator.builder().generateStringKey("password") .secretStringTemplate("{}").build(); diff --git a/spring-cloud-aws-samples/spring-cloud-aws-parameter-store-sample/src/main/java/io/awspring/cloud/samples/parameterstore/SpringCloudAwsParameterStoreSample.java b/spring-cloud-aws-samples/spring-cloud-aws-parameter-store-sample/src/main/java/io/awspring/cloud/samples/parameterstore/SpringCloudAwsParameterStoreSample.java index 6d66a883c..6de03cd85 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-parameter-store-sample/src/main/java/io/awspring/cloud/samples/parameterstore/SpringCloudAwsParameterStoreSample.java +++ b/spring-cloud-aws-samples/spring-cloud-aws-parameter-store-sample/src/main/java/io/awspring/cloud/samples/parameterstore/SpringCloudAwsParameterStoreSample.java @@ -33,10 +33,10 @@ public static void main(String[] args) { } @Bean - ApplicationRunner applicationRunner(@Value("${message}") String message) { - return args -> { - LOGGER.info("`message` loaded from the AWS Parameter store: {}", message); - }; + ApplicationRunner applicationRunner(@Value("${message}") String message, @Value("${messages[0]}") String msg1, + @Value("${messages[1]}") String msg2) { + return args -> LOGGER.info("`messages` loaded from the AWS Parameter store: {}, {} and {}", message, msg1, + msg2); } }