Skip to content

Commit

Permalink
Merge pull request #15 from coveooss/feature/retrieve-ssm-params-from…
Browse files Browse the repository at this point in the history
…-others-regions

Added a flag to support retrieving parameters from different regions.
  • Loading branch information
mcnoreau12 authored Feb 24, 2020
2 parents 197d0f5 + 18a31b7 commit 3c242f8
Show file tree
Hide file tree
Showing 13 changed files with 530 additions and 67 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.coveo</groupId>
<artifactId>spring-boot-parameter-store-integration</artifactId>
<version>1.2.0</version>
<version>1.3.0</version>

<name>Spring Boot Parameter Store Integration</name>
<description>An integration of Amazon Web Services' Systems Manager Parameter Store for Spring Boot's properties injection.</description>
Expand Down
7 changes: 7 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ Spring Cloud has a second application context named bootstrap that gets initiali

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.

## Multi-region support
- 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.
**Reminder**: using other list injecting methods like a yaml list won't work because this property gets loaded too early in the boot process.
- 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.
- Make sure that your service has the necessary permissions to access parameters in the specified regions.
**Important**: If set, it takes precedence over `awsParameterStoreSource.ssmClient.endpointConfiguration.endpoint` and `awsParameterStoreSource.ssmClient.endpointConfiguration.signingRegion`. They are mutually exclusive.

## Contributing
Open an issue to report bugs or to request additional features. Pull requests are always welcome.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.coveo.configuration.parameterstore;

public final class ParameterStorePropertySourceConfigurationProperties
{
private static final String PROPERTY_SOURCE_PREFIX = "awsParameterStorePropertySource";
private static final String SOURCE_PREFIX = "awsParameterStoreSource";
private static final String SSM_CLIENT_ENDPOINT_CONFIG_PREFIX = joinWithDot(SOURCE_PREFIX,
"ssmClient",
"endpointConfiguration");

public static final String ENABLED_PROFILE = "awsParameterStorePropertySourceEnabled";

public static final String ENABLED = joinWithDot(PROPERTY_SOURCE_PREFIX, "enabled");
public static final String ACCEPTED_PROFILES = joinWithDot(PROPERTY_SOURCE_PREFIX, "enabledProfiles");
public static final String HALT_BOOT = joinWithDot(PROPERTY_SOURCE_PREFIX, "haltBoot");
public static final String SUPPORT_MULTIPLE_APPLICATION_CONTEXTS = joinWithDot(PROPERTY_SOURCE_PREFIX,
"supportMultipleApplicationContexts");

public static final String SSM_CLIENT_CUSTOM_ENDPOINT = joinWithDot(SSM_CLIENT_ENDPOINT_CONFIG_PREFIX, "endpoint");
public static final String SSM_CLIENT_SIGNING_REGION = joinWithDot(SSM_CLIENT_ENDPOINT_CONFIG_PREFIX,
"signingRegion");
public static final String MULTI_REGION_SSM_CLIENT_REGIONS = joinWithDot(SOURCE_PREFIX,
"multiRegion",
"ssmClient",
"regions");

private static String joinWithDot(String... elements)
{
return String.join(".", elements);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,54 @@
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ObjectUtils;

import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
import com.amazonaws.regions.DefaultAwsRegionProviderChain;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
import com.coveo.configuration.parameterstore.strategy.ParameterStorePropertySourceConfigurationStrategy;
import com.coveo.configuration.parameterstore.strategy.ParameterStorePropertySourceConfigurationStrategyFactory;
import com.coveo.configuration.parameterstore.strategy.StrategyType;

public class ParameterStorePropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor
{
static final String PARAMETER_STORE_ACCEPTED_PROFILE = "awsParameterStorePropertySourceEnabled";

static final String PARAMETER_STORE_ACCEPTED_PROFILES_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.enabledProfiles";
static final String PARAMETER_STORE_ENABLED_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.enabled";
static final String PARAMETER_STORE_HALT_BOOT_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.haltBoot";
static final String PARAMETER_STORE_CLIENT_ENDPOINT_CONFIGURATION_PROPERTY = "awsParameterStoreSource.ssmClient.endpointConfiguration.endpoint";
static final String PARAMETER_STORE_CLIENT_ENDPOINT_SIGNING_REGION_CONFIGURATION_PROPERTY = "awsParameterStoreSource.ssmClient.endpointConfiguration.signingRegion";
static final String PARAMETER_STORE_SUPPORT_MULTIPLE_APPLICATION_CONTEXTS_CONFIGURATION_PROPERTY = "awsParameterStorePropertySource.supportMultipleApplicationContexts";

private static final String PARAMETER_STORE_PROPERTY_SOURCE_NAME = "AWSParameterStorePropertySource";

static boolean initialized;
static ParameterStorePropertySourceConfigurationStrategyFactory strategyFactory = new ParameterStorePropertySourceConfigurationStrategyFactory();

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application)
{
if (!initialized && isParameterStorePropertySourceEnabled(environment)) {
environment.getPropertySources()
.addFirst(new ParameterStorePropertySource(PARAMETER_STORE_PROPERTY_SOURCE_NAME,
new ParameterStoreSource(buildAWSSimpleSystemsManagementClient(environment),
environment.getProperty(PARAMETER_STORE_HALT_BOOT_CONFIGURATION_PROPERTY,
Boolean.class,
Boolean.FALSE))));
if (!environment.getProperty(PARAMETER_STORE_SUPPORT_MULTIPLE_APPLICATION_CONTEXTS_CONFIGURATION_PROPERTY,
Boolean.class,
Boolean.FALSE)) {
getParameterStorePropertySourceConfigurationStrategy(environment).configureParameterStorePropertySources(environment);

if (doesNotSupportMultipleApplicationContexts(environment)) {
initialized = true;
}
}
}

private ParameterStorePropertySourceConfigurationStrategy getParameterStorePropertySourceConfigurationStrategy(ConfigurableEnvironment environment)
{
StrategyType type = isMultiRegionEnabled(environment) ? StrategyType.MULTI_REGION : StrategyType.DEFAULT;
return strategyFactory.getStrategy(type);
}

private boolean isParameterStorePropertySourceEnabled(ConfigurableEnvironment environment)
{
String[] userDefinedEnabledProfiles = environment.getProperty(PARAMETER_STORE_ACCEPTED_PROFILES_CONFIGURATION_PROPERTY,
String[] userDefinedEnabledProfiles = environment.getProperty(ParameterStorePropertySourceConfigurationProperties.ACCEPTED_PROFILES,
String[].class);
return environment.getProperty(PARAMETER_STORE_ENABLED_CONFIGURATION_PROPERTY, Boolean.class, Boolean.FALSE)
|| environment.acceptsProfiles(PARAMETER_STORE_ACCEPTED_PROFILE)
return environment.getProperty(ParameterStorePropertySourceConfigurationProperties.ENABLED,
Boolean.class,
Boolean.FALSE)
|| environment.acceptsProfiles(ParameterStorePropertySourceConfigurationProperties.ENABLED_PROFILE)
|| (!ObjectUtils.isEmpty(userDefinedEnabledProfiles)
&& environment.acceptsProfiles(userDefinedEnabledProfiles));
}

private AWSSimpleSystemsManagement buildAWSSimpleSystemsManagementClient(ConfigurableEnvironment environment)
private boolean doesNotSupportMultipleApplicationContexts(ConfigurableEnvironment environment)
{
if (environment.containsProperty(PARAMETER_STORE_CLIENT_ENDPOINT_CONFIGURATION_PROPERTY)) {
return AWSSimpleSystemsManagementClientBuilder.standard()
.withEndpointConfiguration(new EndpointConfiguration(environment.getProperty(PARAMETER_STORE_CLIENT_ENDPOINT_CONFIGURATION_PROPERTY),
environment.getProperty(PARAMETER_STORE_CLIENT_ENDPOINT_SIGNING_REGION_CONFIGURATION_PROPERTY,
new DefaultAwsRegionProviderChain().getRegion())))
.build();
}
return AWSSimpleSystemsManagementClientBuilder.defaultClient();
return !environment.getProperty(ParameterStorePropertySourceConfigurationProperties.SUPPORT_MULTIPLE_APPLICATION_CONTEXTS,
Boolean.class,
Boolean.FALSE);
}

private boolean isMultiRegionEnabled(ConfigurableEnvironment environment)
{
return environment.containsProperty(ParameterStorePropertySourceConfigurationProperties.MULTI_REGION_SSM_CLIENT_REGIONS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.coveo.configuration.parameterstore.strategy;

import org.springframework.core.env.ConfigurableEnvironment;

import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
import com.amazonaws.regions.AwsRegionProviderChain;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;

import com.coveo.configuration.parameterstore.ParameterStorePropertySource;
import com.coveo.configuration.parameterstore.ParameterStorePropertySourceConfigurationProperties;
import com.coveo.configuration.parameterstore.ParameterStoreSource;

public class DefaultParameterStorePropertySourceConfigurationStrategy
implements ParameterStorePropertySourceConfigurationStrategy
{
private static final String PARAMETER_STORE_PROPERTY_SOURCE_NAME = "AWSParameterStorePropertySource";

private AwsRegionProviderChain awsRegionProviderChain;

public DefaultParameterStorePropertySourceConfigurationStrategy(AwsRegionProviderChain awsRegionProviderChain)
{
this.awsRegionProviderChain = awsRegionProviderChain;
}

@Override
public void configureParameterStorePropertySources(ConfigurableEnvironment environment)
{
boolean haltBoot = environment.getProperty(ParameterStorePropertySourceConfigurationProperties.HALT_BOOT,
Boolean.class,
Boolean.FALSE);
environment.getPropertySources()
.addFirst(buildParameterStorePropertySource(buildSSMClient(environment), haltBoot));
}

private ParameterStorePropertySource buildParameterStorePropertySource(AWSSimpleSystemsManagement ssmClient,
boolean haltBoot)
{
return new ParameterStorePropertySource(PARAMETER_STORE_PROPERTY_SOURCE_NAME,
new ParameterStoreSource(ssmClient, haltBoot));
}

private AWSSimpleSystemsManagement buildSSMClient(ConfigurableEnvironment environment)
{
if (hasCustomEndpoint(environment)) {
return AWSSimpleSystemsManagementClientBuilder.standard()
.withEndpointConfiguration(new EndpointConfiguration(getCustomEndpoint(environment),
getSigningRegion(environment)))
.build();
}
return AWSSimpleSystemsManagementClientBuilder.defaultClient();
}

private boolean hasCustomEndpoint(ConfigurableEnvironment environment)
{
return environment.containsProperty(ParameterStorePropertySourceConfigurationProperties.SSM_CLIENT_CUSTOM_ENDPOINT);
}

private String getCustomEndpoint(ConfigurableEnvironment environment)
{
return environment.getProperty(ParameterStorePropertySourceConfigurationProperties.SSM_CLIENT_CUSTOM_ENDPOINT);
}

private String getSigningRegion(ConfigurableEnvironment environment)
{
return environment.getProperty(ParameterStorePropertySourceConfigurationProperties.SSM_CLIENT_SIGNING_REGION,
awsRegionProviderChain.getRegion());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.coveo.configuration.parameterstore.strategy;

import java.util.Collections;
import java.util.List;

import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.CollectionUtils;

import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;

import com.coveo.configuration.parameterstore.ParameterStorePropertySource;
import com.coveo.configuration.parameterstore.ParameterStorePropertySourceConfigurationProperties;
import com.coveo.configuration.parameterstore.ParameterStoreSource;

public class MultiRegionParameterStorePropertySourceConfigurationStrategy
implements ParameterStorePropertySourceConfigurationStrategy
{
private static final String PARAMETER_STORE_PROPERTY_SOURCE_NAME = "MultiRegionAWSParameterStorePropertySource_";

@Override
public void configureParameterStorePropertySources(ConfigurableEnvironment environment)
{
boolean haltBoot = environment.getProperty(ParameterStorePropertySourceConfigurationProperties.HALT_BOOT,
Boolean.class,
Boolean.FALSE);

List<String> regions = getRegions(environment);

// To keep the order of precedence, we have to iterate from the last region to the first one.
// If we want the first region specified to be the first property source, we have to add it last.
// We cannot use addLast since it adds the property source with lowest precedence and we want the
// Parameter store property sources to have highest precedence on the other property sources
Collections.reverse(regions);
String lastRegion = regions.get(0);

// We only want to halt boot (if true) for the last region
environment.getPropertySources().addFirst(buildParameterStorePropertySource(lastRegion, haltBoot));

regions.stream()
.skip(1)
.forEach(region -> environment.getPropertySources()
.addFirst(buildParameterStorePropertySource(region, false)));
}

private ParameterStorePropertySource buildParameterStorePropertySource(String region, boolean haltBoot)
{
return new ParameterStorePropertySource(PARAMETER_STORE_PROPERTY_SOURCE_NAME + region,
new ParameterStoreSource(buildSSMClient(region), haltBoot));
}

private AWSSimpleSystemsManagement buildSSMClient(String region)
{
return AWSSimpleSystemsManagementClientBuilder.standard().withRegion(region).build();
}

private List<String> getRegions(ConfigurableEnvironment environment)
{
List<String> regions = CollectionUtils.arrayToList(environment.getProperty(ParameterStorePropertySourceConfigurationProperties.MULTI_REGION_SSM_CLIENT_REGIONS,
String[].class));

if (CollectionUtils.isEmpty(regions)) {
throw new IllegalArgumentException(String.format("To enable multi region support, the property '%s' must not be empty.",
ParameterStorePropertySourceConfigurationProperties.MULTI_REGION_SSM_CLIENT_REGIONS));
}

return regions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.coveo.configuration.parameterstore.strategy;

import org.springframework.core.env.ConfigurableEnvironment;

public interface ParameterStorePropertySourceConfigurationStrategy
{
void configureParameterStorePropertySources(ConfigurableEnvironment environment);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.coveo.configuration.parameterstore.strategy;

import java.util.EnumMap;

import com.amazonaws.regions.DefaultAwsRegionProviderChain;

public class ParameterStorePropertySourceConfigurationStrategyFactory
{
private static EnumMap<StrategyType, ParameterStorePropertySourceConfigurationStrategy> strategies = new EnumMap<>(StrategyType.class);

static {
strategies.put(StrategyType.DEFAULT,
new DefaultParameterStorePropertySourceConfigurationStrategy(new DefaultAwsRegionProviderChain()));
strategies.put(StrategyType.MULTI_REGION,
new MultiRegionParameterStorePropertySourceConfigurationStrategy());
}

public ParameterStorePropertySourceConfigurationStrategy getStrategy(StrategyType strategyType)
{
return strategies.get(strategyType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.coveo.configuration.parameterstore.strategy;

public enum StrategyType
{
DEFAULT, MULTI_REGION;
}
Loading

0 comments on commit 3c242f8

Please sign in to comment.