Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds array properties support to parameter store. (#245) #248

Merged
merged 5 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/main/asciidoc/parameter-store.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Can be overridden with a service-specific property.
|`/config/my-service_production/cloud/aws/stack/auto`
|`cloud.aws.stack.auto`
|Specific to the `my-service` service when a `production` Spring profile is activated.

|`/config/application/cloud.aws.stack_0_.name`
|`cloud.aws.stack[0].name`
|Array indexes for lists properties are denoted by `_INDEX_`.
|===

Note that this module does not support full configuration files to be used as parameter values like e.g. Spring Cloud
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathRequest;
Expand Down Expand Up @@ -58,8 +57,7 @@ public void init() {

@Override
public String[] getPropertyNames() {
Set<String> strings = this.properties.keySet();
return strings.toArray(new String[strings.size()]);
return this.properties.keySet().stream().toArray(String[]::new);
}

@Override
Expand All @@ -70,7 +68,7 @@ public Object getProperty(String name) {
private void getParameters(GetParametersByPathRequest paramsRequest) {
GetParametersByPathResult paramsResult = this.source.getParametersByPath(paramsRequest);
for (Parameter parameter : paramsResult.getParameters()) {
String key = parameter.getName().replace(this.context, "").replace('/', '.');
String key = parameter.getName().replace(this.context, "").replace('/', '.').replaceAll("_(\\d)_", "[$1]");
LOG.debug("Populating property retrieved from AWS Parameter Store: " + key);
this.properties.put(key, parameter.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.junit.jupiter.api.Test;

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;
Expand Down Expand Up @@ -52,4 +53,31 @@ void followsNextToken() {
assertThat(propertySource.getProperty("key3")).isEqualTo("value3");
}

@Test
void arrayParameterNames() {
GetParametersByPathResult result = new GetParametersByPathResult().withParameters(
new Parameter().withName("/config/myservice/key_0_.value").withValue("value1"),
new Parameter().withName("/config/myservice/key_0_.nested_0_.nestedValue")
.withValue("key_nestedValue1"),
new Parameter().withName("/config/myservice/key_0_.nested_1_.nestedValue")
.withValue("key_nestedValue2"),
new Parameter().withName("/config/myservice/key_1_.value").withValue("value2"),
new Parameter().withName("/config/myservice/key_1_.nested_0_.nestedValue")
.withValue("key_nestedValue1"),
new Parameter().withName("/config/myservice/key_1_.nested_1_.nestedValue")
.withValue("key_nestedValue2"));

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");
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,15 @@ export class InfrastructureStack extends cdk.Stack {
parameterName: '/config/spring/message',
stringValue: 'Spring-cloud-aws value!'
});

new ssm.StringParameter(this, 'Parameter2', {
parameterName: '/config/spring/messages_0_',
stringValue: 'Spring-cloud-aws msg0!'
});

new ssm.StringParameter(this, 'Parameter3', {
parameterName: '/config/spring/messages_1_',
stringValue: 'Spring-cloud-aws msg1!'
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,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);
}

}
12 changes: 12 additions & 0 deletions spring-cloud-starter-aws-parameter-store-config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,17 @@
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-core</artifactId>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2013-2022 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 io.awspring.cloud.autoconfigure.paramstore;

import java.io.IOException;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SSM;

/**
* Integration tests for loading configuration properties as List from AWS Parameter
* Store.
*
* @author Maciej Walkowiak
* @author Matej Nedić
*/
@Testcontainers
class ParameterStoreConfigDataLoaderIntegrationTests {

private static final String REGION = "us-east-1";

@Container
static LocalStackContainer localstack = new LocalStackContainer(
DockerImageName.parse("localstack/localstack:0.14.0")).withServices(SSM).withReuse(true);

@BeforeAll
public static void setUpCredentials() {
rmpestano marked this conversation as resolved.
Show resolved Hide resolved
System.setProperty("aws.accessKeyId", "accesskey");
System.setProperty("aws.secretKey", "secretkey");
}

@Test
void followsNextToken() {
SpringApplication application = new SpringApplication(App.class);
application.setWebApplicationType(WebApplicationType.NONE);
putParameter(localstack, "/config/myservice/key1", "value1", REGION);
putParameter(localstack, "/config/myservice/key2", "value2", REGION);
putParameter(localstack, "/config/myservice/key3", "value3", REGION);
putParameter(localstack, "/config/myservice/key4", "value4", REGION);

try (ConfigurableApplicationContext context = runApplication(application,
"aws-parameterstore:/config/myservice/")) {
assertThat(context.getEnvironment().getProperty("key1")).isEqualTo("value1");
assertThat(context.getEnvironment().getProperty("key2")).isEqualTo("value2");
assertThat(context.getEnvironment().getProperty("key3")).isEqualTo("value3");
assertThat(context.getEnvironment().getProperty("key4")).isEqualTo("value4");
}
}

@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");
}
}

private ConfigurableApplicationContext runApplication(SpringApplication application, String springConfigImport) {
return application.run("--spring.config.import=" + springConfigImport,
"--aws.paramstore.endpoint=" + localstack.getEndpointOverride(SSM).toString(),
"--cloud.aws.credentials.accessKey=noop", "--cloud.aws.credentials.secretKey=noop");
}

private static void putParameter(LocalStackContainer localstack, String parameterName, String parameterValue,
String region) {
try {
localstack.execInContainer("awslocal", "ssm", "put-parameter", "--name", parameterName, "--type", "String",
"--value", parameterValue, "--region", region);
}
catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}

@SpringBootApplication
static class App {

}

}