Skip to content

Commit

Permalink
Add option to set property prefix on Parameter Store property sources (
Browse files Browse the repository at this point in the history
  • Loading branch information
beccagaspard authored Nov 3, 2023
1 parent e5c1010 commit a28a9d7
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 5 deletions.
27 changes: 27 additions & 0 deletions docs/src/main/asciidoc/parameter-store.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,31 @@ public class ParameterStorePropertySource extends AwsPropertySource<ParameterSto
// logger must stay static non-final so that it can be set with a value in
// ParameterStoreConfigDataLoader
private static Log LOG = LogFactory.getLog(ParameterStorePropertySource.class);

private static final String PREFIX_PART = "?prefix=";
private final String context;

private final String parameterPath;

/**
* Prefix that gets added to resolved property keys. Useful when same property keys are returned by multiple
* parameter paths.
*/
@Nullable
private final String prefix;

private final Map<String, Object> 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);
}

Expand All @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

0 comments on commit a28a9d7

Please sign in to comment.