Skip to content

Commit 3c242f8

Browse files
authored
Merge pull request #15 from coveooss/feature/retrieve-ssm-params-from-others-regions
Added a flag to support retrieving parameters from different regions.
2 parents 197d0f5 + 18a31b7 commit 3c242f8

13 files changed

+530
-67
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.coveo</groupId>
77
<artifactId>spring-boot-parameter-store-integration</artifactId>
8-
<version>1.2.0</version>
8+
<version>1.3.0</version>
99

1010
<name>Spring Boot Parameter Store Integration</name>
1111
<description>An integration of Amazon Web Services' Systems Manager Parameter Store for Spring Boot's properties injection.</description>

readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ Spring Cloud has a second application context named bootstrap that gets initiali
8181

8282
If you still want the post processor to run twice or if you are using [spring-boot-devtools](https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-devtools-restart), you can set the optional property `awsParameterStorePropertySource.supportMultipleApplicationContexts` to `true`. The default property value is `false`to prevent multiple initializations. If you are also using Spring Cloud, this property will only work if set in the bootstrap properties.
8383

84+
## Multi-region support
85+
- Set `awsParameterStoreSource.multiRegion.ssmClient.regions` with the regions from which you need to retrieve parameters using a **comma-separated** list such as `us-east-1,us-east-2`. It adds a `ParameterStorePropertySource` to the property sources for each region specified. It will start looking from the first region and so on until it finds the property so put the regions in order of precedence.
86+
**Reminder**: using other list injecting methods like a yaml list won't work because this property gets loaded too early in the boot process.
87+
- If you want to halt the boot when a property isn't found in any of the specified regions, just set `awsParameterStorePropertySource.haltBoot` to `true` in your properties.
88+
- Make sure that your service has the necessary permissions to access parameters in the specified regions.
89+
**Important**: If set, it takes precedence over `awsParameterStoreSource.ssmClient.endpointConfiguration.endpoint` and `awsParameterStoreSource.ssmClient.endpointConfiguration.signingRegion`. They are mutually exclusive.
90+
8491
## Contributing
8592
Open an issue to report bugs or to request additional features. Pull requests are always welcome.
8693

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.coveo.configuration.parameterstore;
2+
3+
public final class ParameterStorePropertySourceConfigurationProperties
4+
{
5+
private static final String PROPERTY_SOURCE_PREFIX = "awsParameterStorePropertySource";
6+
private static final String SOURCE_PREFIX = "awsParameterStoreSource";
7+
private static final String SSM_CLIENT_ENDPOINT_CONFIG_PREFIX = joinWithDot(SOURCE_PREFIX,
8+
"ssmClient",
9+
"endpointConfiguration");
10+
11+
public static final String ENABLED_PROFILE = "awsParameterStorePropertySourceEnabled";
12+
13+
public static final String ENABLED = joinWithDot(PROPERTY_SOURCE_PREFIX, "enabled");
14+
public static final String ACCEPTED_PROFILES = joinWithDot(PROPERTY_SOURCE_PREFIX, "enabledProfiles");
15+
public static final String HALT_BOOT = joinWithDot(PROPERTY_SOURCE_PREFIX, "haltBoot");
16+
public static final String SUPPORT_MULTIPLE_APPLICATION_CONTEXTS = joinWithDot(PROPERTY_SOURCE_PREFIX,
17+
"supportMultipleApplicationContexts");
18+
19+
public static final String SSM_CLIENT_CUSTOM_ENDPOINT = joinWithDot(SSM_CLIENT_ENDPOINT_CONFIG_PREFIX, "endpoint");
20+
public static final String SSM_CLIENT_SIGNING_REGION = joinWithDot(SSM_CLIENT_ENDPOINT_CONFIG_PREFIX,
21+
"signingRegion");
22+
public static final String MULTI_REGION_SSM_CLIENT_REGIONS = joinWithDot(SOURCE_PREFIX,
23+
"multiRegion",
24+
"ssmClient",
25+
"regions");
26+
27+
private static String joinWithDot(String... elements)
28+
{
29+
return String.join(".", elements);
30+
}
31+
}

src/main/java/com/coveo/configuration/parameterstore/ParameterStorePropertySourceEnvironmentPostProcessor.java

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,63 +5,54 @@
55
import org.springframework.core.env.ConfigurableEnvironment;
66
import org.springframework.util.ObjectUtils;
77

8-
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
9-
import com.amazonaws.regions.DefaultAwsRegionProviderChain;
10-
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
11-
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
8+
import com.coveo.configuration.parameterstore.strategy.ParameterStorePropertySourceConfigurationStrategy;
9+
import com.coveo.configuration.parameterstore.strategy.ParameterStorePropertySourceConfigurationStrategyFactory;
10+
import com.coveo.configuration.parameterstore.strategy.StrategyType;
1211

1312
public class ParameterStorePropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor
1413
{
15-
static final String PARAMETER_STORE_ACCEPTED_PROFILE = "awsParameterStorePropertySourceEnabled";
16-
17-
static final String PARAMETER_STORE_ACCEPTED_PROFILES_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.enabledProfiles";
18-
static final String PARAMETER_STORE_ENABLED_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.enabled";
19-
static final String PARAMETER_STORE_HALT_BOOT_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.haltBoot";
20-
static final String PARAMETER_STORE_CLIENT_ENDPOINT_CONFIGURATION_PROPERTY = "awsParameterStoreSource.ssmClient.endpointConfiguration.endpoint";
21-
static final String PARAMETER_STORE_CLIENT_ENDPOINT_SIGNING_REGION_CONFIGURATION_PROPERTY = "awsParameterStoreSource.ssmClient.endpointConfiguration.signingRegion";
22-
static final String PARAMETER_STORE_SUPPORT_MULTIPLE_APPLICATION_CONTEXTS_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.supportMultipleApplicationContexts";
23-
24-
private static final String PARAMETER_STORE_PROPERTY_SOURCE_NAME = "AWSParameterStorePropertySource";
25-
2614
static boolean initialized;
15+
static ParameterStorePropertySourceConfigurationStrategyFactory strategyFactory = new ParameterStorePropertySourceConfigurationStrategyFactory();
2716

2817
@Override
2918
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application)
3019
{
3120
if (!initialized && isParameterStorePropertySourceEnabled(environment)) {
32-
environment.getPropertySources()
33-
.addFirst(new ParameterStorePropertySource(PARAMETER_STORE_PROPERTY_SOURCE_NAME,
34-
new ParameterStoreSource(buildAWSSimpleSystemsManagementClient(environment),
35-
environment.getProperty(PARAMETER_STORE_HALT_BOOT_CONFIGURATION_PROPERTY,
36-
Boolean.class,
37-
Boolean.FALSE))));
38-
if (!environment.getProperty(PARAMETER_STORE_SUPPORT_MULTIPLE_APPLICATION_CONTEXTS_CONFIGURATION_PROPERTY,
39-
Boolean.class,
40-
Boolean.FALSE)) {
21+
getParameterStorePropertySourceConfigurationStrategy(environment).configureParameterStorePropertySources(environment);
22+
23+
if (doesNotSupportMultipleApplicationContexts(environment)) {
4124
initialized = true;
4225
}
4326
}
4427
}
4528

29+
private ParameterStorePropertySourceConfigurationStrategy getParameterStorePropertySourceConfigurationStrategy(ConfigurableEnvironment environment)
30+
{
31+
StrategyType type = isMultiRegionEnabled(environment) ? StrategyType.MULTI_REGION : StrategyType.DEFAULT;
32+
return strategyFactory.getStrategy(type);
33+
}
34+
4635
private boolean isParameterStorePropertySourceEnabled(ConfigurableEnvironment environment)
4736
{
48-
String[] userDefinedEnabledProfiles = environment.getProperty(PARAMETER_STORE_ACCEPTED_PROFILES_CONFIGURATION_PROPERTY,
37+
String[] userDefinedEnabledProfiles = environment.getProperty(ParameterStorePropertySourceConfigurationProperties.ACCEPTED_PROFILES,
4938
String[].class);
50-
return environment.getProperty(PARAMETER_STORE_ENABLED_CONFIGURATION_PROPERTY, Boolean.class, Boolean.FALSE)
51-
|| environment.acceptsProfiles(PARAMETER_STORE_ACCEPTED_PROFILE)
39+
return environment.getProperty(ParameterStorePropertySourceConfigurationProperties.ENABLED,
40+
Boolean.class,
41+
Boolean.FALSE)
42+
|| environment.acceptsProfiles(ParameterStorePropertySourceConfigurationProperties.ENABLED_PROFILE)
5243
|| (!ObjectUtils.isEmpty(userDefinedEnabledProfiles)
5344
&& environment.acceptsProfiles(userDefinedEnabledProfiles));
5445
}
5546

56-
private AWSSimpleSystemsManagement buildAWSSimpleSystemsManagementClient(ConfigurableEnvironment environment)
47+
private boolean doesNotSupportMultipleApplicationContexts(ConfigurableEnvironment environment)
5748
{
58-
if (environment.containsProperty(PARAMETER_STORE_CLIENT_ENDPOINT_CONFIGURATION_PROPERTY)) {
59-
return AWSSimpleSystemsManagementClientBuilder.standard()
60-
.withEndpointConfiguration(new EndpointConfiguration(environment.getProperty(PARAMETER_STORE_CLIENT_ENDPOINT_CONFIGURATION_PROPERTY),
61-
environment.getProperty(PARAMETER_STORE_CLIENT_ENDPOINT_SIGNING_REGION_CONFIGURATION_PROPERTY,
62-
new DefaultAwsRegionProviderChain().getRegion())))
63-
.build();
64-
}
65-
return AWSSimpleSystemsManagementClientBuilder.defaultClient();
49+
return !environment.getProperty(ParameterStorePropertySourceConfigurationProperties.SUPPORT_MULTIPLE_APPLICATION_CONTEXTS,
50+
Boolean.class,
51+
Boolean.FALSE);
52+
}
53+
54+
private boolean isMultiRegionEnabled(ConfigurableEnvironment environment)
55+
{
56+
return environment.containsProperty(ParameterStorePropertySourceConfigurationProperties.MULTI_REGION_SSM_CLIENT_REGIONS);
6657
}
6758
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.coveo.configuration.parameterstore.strategy;
2+
3+
import org.springframework.core.env.ConfigurableEnvironment;
4+
5+
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
6+
import com.amazonaws.regions.AwsRegionProviderChain;
7+
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
8+
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
9+
10+
import com.coveo.configuration.parameterstore.ParameterStorePropertySource;
11+
import com.coveo.configuration.parameterstore.ParameterStorePropertySourceConfigurationProperties;
12+
import com.coveo.configuration.parameterstore.ParameterStoreSource;
13+
14+
public class DefaultParameterStorePropertySourceConfigurationStrategy
15+
implements ParameterStorePropertySourceConfigurationStrategy
16+
{
17+
private static final String PARAMETER_STORE_PROPERTY_SOURCE_NAME = "AWSParameterStorePropertySource";
18+
19+
private AwsRegionProviderChain awsRegionProviderChain;
20+
21+
public DefaultParameterStorePropertySourceConfigurationStrategy(AwsRegionProviderChain awsRegionProviderChain)
22+
{
23+
this.awsRegionProviderChain = awsRegionProviderChain;
24+
}
25+
26+
@Override
27+
public void configureParameterStorePropertySources(ConfigurableEnvironment environment)
28+
{
29+
boolean haltBoot = environment.getProperty(ParameterStorePropertySourceConfigurationProperties.HALT_BOOT,
30+
Boolean.class,
31+
Boolean.FALSE);
32+
environment.getPropertySources()
33+
.addFirst(buildParameterStorePropertySource(buildSSMClient(environment), haltBoot));
34+
}
35+
36+
private ParameterStorePropertySource buildParameterStorePropertySource(AWSSimpleSystemsManagement ssmClient,
37+
boolean haltBoot)
38+
{
39+
return new ParameterStorePropertySource(PARAMETER_STORE_PROPERTY_SOURCE_NAME,
40+
new ParameterStoreSource(ssmClient, haltBoot));
41+
}
42+
43+
private AWSSimpleSystemsManagement buildSSMClient(ConfigurableEnvironment environment)
44+
{
45+
if (hasCustomEndpoint(environment)) {
46+
return AWSSimpleSystemsManagementClientBuilder.standard()
47+
.withEndpointConfiguration(new EndpointConfiguration(getCustomEndpoint(environment),
48+
getSigningRegion(environment)))
49+
.build();
50+
}
51+
return AWSSimpleSystemsManagementClientBuilder.defaultClient();
52+
}
53+
54+
private boolean hasCustomEndpoint(ConfigurableEnvironment environment)
55+
{
56+
return environment.containsProperty(ParameterStorePropertySourceConfigurationProperties.SSM_CLIENT_CUSTOM_ENDPOINT);
57+
}
58+
59+
private String getCustomEndpoint(ConfigurableEnvironment environment)
60+
{
61+
return environment.getProperty(ParameterStorePropertySourceConfigurationProperties.SSM_CLIENT_CUSTOM_ENDPOINT);
62+
}
63+
64+
private String getSigningRegion(ConfigurableEnvironment environment)
65+
{
66+
return environment.getProperty(ParameterStorePropertySourceConfigurationProperties.SSM_CLIENT_SIGNING_REGION,
67+
awsRegionProviderChain.getRegion());
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.coveo.configuration.parameterstore.strategy;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
6+
import org.springframework.core.env.ConfigurableEnvironment;
7+
import org.springframework.util.CollectionUtils;
8+
9+
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
10+
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
11+
12+
import com.coveo.configuration.parameterstore.ParameterStorePropertySource;
13+
import com.coveo.configuration.parameterstore.ParameterStorePropertySourceConfigurationProperties;
14+
import com.coveo.configuration.parameterstore.ParameterStoreSource;
15+
16+
public class MultiRegionParameterStorePropertySourceConfigurationStrategy
17+
implements ParameterStorePropertySourceConfigurationStrategy
18+
{
19+
private static final String PARAMETER_STORE_PROPERTY_SOURCE_NAME = "MultiRegionAWSParameterStorePropertySource_";
20+
21+
@Override
22+
public void configureParameterStorePropertySources(ConfigurableEnvironment environment)
23+
{
24+
boolean haltBoot = environment.getProperty(ParameterStorePropertySourceConfigurationProperties.HALT_BOOT,
25+
Boolean.class,
26+
Boolean.FALSE);
27+
28+
List<String> regions = getRegions(environment);
29+
30+
// To keep the order of precedence, we have to iterate from the last region to the first one.
31+
// If we want the first region specified to be the first property source, we have to add it last.
32+
// We cannot use addLast since it adds the property source with lowest precedence and we want the
33+
// Parameter store property sources to have highest precedence on the other property sources
34+
Collections.reverse(regions);
35+
String lastRegion = regions.get(0);
36+
37+
// We only want to halt boot (if true) for the last region
38+
environment.getPropertySources().addFirst(buildParameterStorePropertySource(lastRegion, haltBoot));
39+
40+
regions.stream()
41+
.skip(1)
42+
.forEach(region -> environment.getPropertySources()
43+
.addFirst(buildParameterStorePropertySource(region, false)));
44+
}
45+
46+
private ParameterStorePropertySource buildParameterStorePropertySource(String region, boolean haltBoot)
47+
{
48+
return new ParameterStorePropertySource(PARAMETER_STORE_PROPERTY_SOURCE_NAME + region,
49+
new ParameterStoreSource(buildSSMClient(region), haltBoot));
50+
}
51+
52+
private AWSSimpleSystemsManagement buildSSMClient(String region)
53+
{
54+
return AWSSimpleSystemsManagementClientBuilder.standard().withRegion(region).build();
55+
}
56+
57+
private List<String> getRegions(ConfigurableEnvironment environment)
58+
{
59+
List<String> regions = CollectionUtils.arrayToList(environment.getProperty(ParameterStorePropertySourceConfigurationProperties.MULTI_REGION_SSM_CLIENT_REGIONS,
60+
String[].class));
61+
62+
if (CollectionUtils.isEmpty(regions)) {
63+
throw new IllegalArgumentException(String.format("To enable multi region support, the property '%s' must not be empty.",
64+
ParameterStorePropertySourceConfigurationProperties.MULTI_REGION_SSM_CLIENT_REGIONS));
65+
}
66+
67+
return regions;
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.coveo.configuration.parameterstore.strategy;
2+
3+
import org.springframework.core.env.ConfigurableEnvironment;
4+
5+
public interface ParameterStorePropertySourceConfigurationStrategy
6+
{
7+
void configureParameterStorePropertySources(ConfigurableEnvironment environment);
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.coveo.configuration.parameterstore.strategy;
2+
3+
import java.util.EnumMap;
4+
5+
import com.amazonaws.regions.DefaultAwsRegionProviderChain;
6+
7+
public class ParameterStorePropertySourceConfigurationStrategyFactory
8+
{
9+
private static EnumMap<StrategyType, ParameterStorePropertySourceConfigurationStrategy> strategies = new EnumMap<>(StrategyType.class);
10+
11+
static {
12+
strategies.put(StrategyType.DEFAULT,
13+
new DefaultParameterStorePropertySourceConfigurationStrategy(new DefaultAwsRegionProviderChain()));
14+
strategies.put(StrategyType.MULTI_REGION,
15+
new MultiRegionParameterStorePropertySourceConfigurationStrategy());
16+
}
17+
18+
public ParameterStorePropertySourceConfigurationStrategy getStrategy(StrategyType strategyType)
19+
{
20+
return strategies.get(strategyType);
21+
}
22+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.coveo.configuration.parameterstore.strategy;
2+
3+
public enum StrategyType
4+
{
5+
DEFAULT, MULTI_REGION;
6+
}

0 commit comments

Comments
 (0)