diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc
index eef8807b..9c33e3bc 100644
--- a/docs/src/main/asciidoc/spring-cloud-config.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-config.adoc
@@ -337,7 +337,7 @@ To correct the above error the RSA key must be converted to PEM format. An examp
Spring Cloud Config Server also supports https://docs.aws.amazon.com/codecommit/latest/userguide/welcome.html[AWS CodeCommit] authentication.
AWS CodeCommit uses an authentication helper when using Git from the command line.
This helper is not used with the JGit library, so a JGit CredentialProvider for AWS CodeCommit is created if the Git URI matches the AWS CodeCommit pattern.
-AWS CodeCommit URIs follow this pattern:
+AWS CodeCommit URIs follow this pattern:
```bash
https//git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/${repo}.
@@ -834,6 +834,18 @@ secret value =
----
+===== AWS Parameter Store
+
+When using AWS Parameter Store as a backend, you can share configuration with all applications by placing properties within the `/application` hierarchy.
+
+For example, if you add parameters with the following names, all applications using the config server will have the properties `foo.bar` and `fred.baz` available to them:
+
+[source]
+----
+/config/application/foo.bar
+/config/application-default/fred.baz
+----
+
==== JDBC Backend
Spring Cloud Config Server supports JDBC (relational database) as a backend for configuration properties.
@@ -932,6 +944,99 @@ Configuration files are stored in your bucket as `{application}-{profile}.proper
NOTE: When no profile is specified `default` will be used.
+==== AWS Parameter Store Backend
+
+Spring Cloud Config Server supports AWS Parameter Store as a backend for configuration properties. You can enable this feature by adding a dependency to the link:https://github.com/aws/aws-sdk-java/tree/master/aws-java-sdk-ssm[AWS Java SDK for SSM].
+
+[source,xml,indent=0]
+.pom.xml
+----
+
+ com.amazonaws
+ aws-java-sdk-ssm
+
+----
+
+The following configuration uses the AWS SSM client to access parameters.
+
+[source,yaml]
+----
+spring:
+ profiles:
+ active: awsparamstore
+ cloud:
+ config:
+ server:
+ awsparamstore:
+ region: eu-west-2
+ endpoint: https://ssm.eu-west-2.amazonaws.com
+ origin: aws:parameter:
+ prefix: /config/service
+ profileSeparator: _
+ recursive: true
+ decryptValues: true
+ maxResults: 5
+----
+
+The following table describes the AWS Parameter Store configuration properties.
+
+.AWS Parameter Store Configuration Properties
+|===
+|Property Name |Required |Default Value |Remarks
+
+|*region*
+|no
+|
+|The region to be used by the AWS Parameter Store client. If it's not explicitly set, the SDK tries to determine the region to use by using the link:https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-region-selection.html#default-region-provider-chain[Default Region Provider Chain].
+
+|*endpoint*
+|no
+|
+|The URL of the entry point for the AWS SSM client. This can be used to specify an alternate endpoint for the API requests.
+
+|*origin*
+|no
+|`aws:ssm:parameter:`
+|The prefix that is added to the property source's name to show their provenance.
+
+|*prefix*
+|no
+|`/config`
+|Prefix indicating L1 level in the parameter hierarchy for every property loaded from the AWS Parameter Store.
+
+|*profileSeparator*
+|no
+|`-`
+|String that separates an appended profile from the context name.
+
+|*recursive*
+|no
+|`true`
+|Flag to indicate the retrieval of all AWS parameters within a hierarchy.
+
+|*decryptValues*
+|no
+|`true`
+|Flag to indicate the retrieval of all AWS parameters with their value decrypted.
+
+|*maxResults*
+|no
+|`10`
+|The maximum number of items to return for an AWS Parameter Store API call.
+
+|===
+
+AWS Parameter Store API credentials are determined using the link:https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default[Default Credential Provider Chain].
+Versioned parameters are already supported with the default behaviour of returning the latest version.
+
+[NOTE]
+====
+- When no application is specified `application` is the default, and when no profile is specified `default` is used.
+- Valid values for `awsparamstore.prefix` must start with a forward slash followed by one or more valid path segments or be empty.
+- Valid values for `awsparamstore.profileSeparator` can only contain dots, dashes and underscores.
+- Valid values for `awsparamstore.maxResults` must be within the *[1, 10]* range.
+====
+
==== AWS Secrets Manager Backend
Spring Cloud Config Server supports link:https://aws.amazon.com/secrets-manager/[AWS Secrets Manager] as a backend for configuration properties.
diff --git a/pom.xml b/pom.xml
index af56ee71..a5ca4c64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
config
3.1.0-SNAPSHOT
- 1.11.903
+ 1.11.911
v1-rev20201112-1.30.10
true
true
@@ -83,6 +83,11 @@
aws-java-sdk-secretsmanager
${aws-java-sdk.version}
+
+ com.amazonaws
+ aws-java-sdk-ssm
+ ${aws-java-sdk.version}
+
com.google.apis
google-api-services-iam
diff --git a/spring-cloud-config-server/pom.xml b/spring-cloud-config-server/pom.xml
index dcd9d737..2117add4 100644
--- a/spring-cloud-config-server/pom.xml
+++ b/spring-cloud-config-server/pom.xml
@@ -104,6 +104,11 @@
aws-java-sdk-secretsmanager
true
+
+ com.amazonaws
+ aws-java-sdk-ssm
+ true
+
org.springframework.boot
spring-boot-autoconfigure-processor
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java
index 271ba57c..a1692f5f 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java
@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
+import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import org.apache.http.client.HttpClient;
import org.eclipse.jgit.api.TransportConfigCallback;
@@ -40,6 +41,9 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.config.server.composite.CompositeEnvironmentBeanFactoryPostProcessor;
import org.springframework.cloud.config.server.composite.ConditionalOnMissingSearchPathLocator;
import org.springframework.cloud.config.server.composite.ConditionalOnSearchPathLocator;
+import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentProperties;
+import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentRepository;
+import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentProperties;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentRepository;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentRepositoryFactory;
@@ -106,18 +110,20 @@ import org.springframework.vault.core.VaultTemplate;
* @author Alberto C. RĂos
* @author Scott Frederick
* @author Tejas Pandilwar
+ * @author Iulian Antohe
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class, CredhubEnvironmentProperties.class,
JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class,
RedisEnvironmentProperties.class, AwsS3EnvironmentProperties.class,
- AwsSecretsManagerEnvironmentProperties.class, GoogleSecretManagerEnvironmentProperties.class })
+ AwsSecretsManagerEnvironmentProperties.class, AwsParameterStoreEnvironmentProperties.class, GoogleSecretManagerEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultConfiguration.class,
VaultRepositoryConfiguration.class, SpringVaultRepositoryConfiguration.class, CredhubConfiguration.class,
CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class,
GitRepositoryConfiguration.class, RedisRepositoryConfiguration.class, GoogleCloudSourceConfiguration.class,
AwsS3RepositoryConfiguration.class, AwsSecretsManagerRepositoryConfiguration.class,
- GoogleSecretManagerRepositoryConfiguration.class, DefaultRepositoryConfiguration.class })
+ AwsParameterStoreRepositoryConfiguration.class,,
+ GoogleSecretManagerRepositoryConfiguration.class, DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
@Bean
@@ -225,6 +231,18 @@ public class EnvironmentRepositoryConfiguration {
}
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(AWSSimpleSystemsManagement.class)
+ static class AwsParameterStoreFactoryConfig {
+
+ @Bean
+ public AwsParameterStoreEnvironmentRepositoryFactory awsParameterStoreEnvironmentRepositoryFactory(
+ ConfigServerProperties server) {
+ return new AwsParameterStoreEnvironmentRepositoryFactory(server);
+ }
+
+ }
+
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SVNException.class)
static class SvnFactoryConfig {
@@ -385,6 +403,20 @@ class AwsS3RepositoryConfiguration {
}
+@Configuration(proxyBeanMethods = false)
+@Profile("awsparamstore")
+class AwsParameterStoreRepositoryConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(AwsParameterStoreEnvironmentRepository.class)
+ public AwsParameterStoreEnvironmentRepository awsParameterStoreEnvironmentRepository(
+ AwsParameterStoreEnvironmentRepositoryFactory factory,
+ AwsParameterStoreEnvironmentProperties environmentProperties) {
+ return factory.build(environmentProperties);
+ }
+
+}
+
@Configuration(proxyBeanMethods = false)
@Profile("awssecretsmanager")
class AwsSecretsManagerRepositoryConfiguration {
@@ -516,8 +548,7 @@ class GoogleSecretManagerRepositoryConfiguration {
@Bean
public GoogleSecretManagerEnvironmentRepository googleSecretManagerEnvironmentRepository(
GoogleSecretManagerEnvironmentRepositoryFactory factory,
- GoogleSecretManagerEnvironmentProperties environmentProperties)
- throws Exception {
+ GoogleSecretManagerEnvironmentProperties environmentProperties) throws Exception {
return factory.build(environmentProperties);
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentProperties.java
new file mode 100644
index 00000000..c2768513
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentProperties.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2018-2020 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 org.springframework.cloud.config.server.environment;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
+import org.springframework.core.Ordered;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * @author Iulian Antohe
+ */
+@Validated
+@ConfigurationProperties("spring.cloud.config.server.awsparamstore")
+public class AwsParameterStoreEnvironmentProperties
+ implements EnvironmentRepositoryProperties {
+
+ static final String DEFAULT_PATH_SEPARATOR = "/";
+
+ private static final String DEFAULT_ORIGIN = "aws:ssm:parameter:";
+
+ private static final String DEFAULT_PREFIX = DEFAULT_PATH_SEPARATOR + "config";
+
+ private static final String DEFAULT_PROFILE_SEPARATOR = "-";
+
+ /**
+ * The order of the environment repository.
+ */
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+ /**
+ * The region to be used by the AWS Parameter Store client.
+ */
+ private String region;
+
+ /**
+ * The service endpoint to be used by the AWS Parameter Store client.
+ */
+ private String endpoint;
+
+ /**
+ * Prefix indicating the property's origin. Defaults to "aws:ssm:parameter:".
+ */
+ @NotNull
+ private String origin = DEFAULT_ORIGIN;
+
+ /**
+ * Prefix indicating first level for every property loaded from the AWS Parameter
+ * Store. Value must start with a forward slash followed by one or more valid path
+ * segments or be empty. Defaults to "/config".
+ */
+ @NotNull
+ @Pattern(regexp = "(/[a-zA-Z0-9.\\-_]+)*")
+ private String prefix = DEFAULT_PREFIX;
+
+ /**
+ * String that separates an appended profile from the context name. Note that an AWS
+ * parameter name can only contain dots, dashes and underscores next to alphanumeric
+ * characters. Defaults to "-".
+ */
+ @NotBlank
+ @Pattern(regexp = "[a-zA-Z0-9.\\-_/]+")
+ private String profileSeparator = DEFAULT_PROFILE_SEPARATOR;
+
+ /**
+ * Flag to indicate the retrieval of all AWS parameters within a hierarchy. Defaults
+ * to "true".
+ */
+ private boolean recursive = true;
+
+ /**
+ * Flag to indicate the retrieval of all AWS parameters in a hierarchy with their
+ * value decrypted. Defaults to "true".
+ */
+ private boolean decryptValues = true;
+
+ /**
+ * The maximum number of items to return for an AWS Parameter Store API call. Defaults
+ * to "10".
+ */
+ @Min(1)
+ @Max(10)
+ private int maxResults = 10;
+
+ public int getOrder() {
+ return order;
+ }
+
+ @Override
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public String getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(String origin) {
+ this.origin = origin;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getProfileSeparator() {
+ return profileSeparator;
+ }
+
+ public void setProfileSeparator(String profileSeparator) {
+ this.profileSeparator = profileSeparator;
+ }
+
+ public boolean isRecursive() {
+ return recursive;
+ }
+
+ public void setRecursive(boolean recursive) {
+ this.recursive = recursive;
+ }
+
+ public boolean isDecryptValues() {
+ return decryptValues;
+ }
+
+ public void setDecryptValues(boolean decryptValues) {
+ this.decryptValues = decryptValues;
+ }
+
+ public int getMaxResults() {
+ return maxResults;
+ }
+
+ public void setMaxResults(int maxResults) {
+ this.maxResults = maxResults;
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepository.java
new file mode 100644
index 00000000..c454291f
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepository.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2013-2020 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 org.springframework.cloud.config.server.environment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
+import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathRequest;
+import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathResult;
+import com.amazonaws.services.simplesystemsmanagement.model.Parameter;
+
+import org.springframework.cloud.config.environment.Environment;
+import org.springframework.cloud.config.environment.PropertySource;
+import org.springframework.cloud.config.server.config.ConfigServerProperties;
+import org.springframework.util.StringUtils;
+
+import static org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentProperties.DEFAULT_PATH_SEPARATOR;
+
+/**
+ * @author Iulian Antohe
+ */
+public class AwsParameterStoreEnvironmentRepository implements EnvironmentRepository {
+
+ private final AWSSimpleSystemsManagement awsSsmClient;
+
+ private final ConfigServerProperties configServerProperties;
+
+ private final AwsParameterStoreEnvironmentProperties environmentProperties;
+
+ public AwsParameterStoreEnvironmentRepository(AWSSimpleSystemsManagement awsSsmClient,
+ ConfigServerProperties configServerProperties,
+ AwsParameterStoreEnvironmentProperties environmentProperties) {
+ this.awsSsmClient = awsSsmClient;
+ this.configServerProperties = configServerProperties;
+ this.environmentProperties = environmentProperties;
+ }
+
+ @Override
+ public Environment findOne(String application, String profile, String label) {
+ if (!StringUtils.hasLength(application)) {
+ application = configServerProperties.getDefaultApplicationName();
+ }
+
+ if (!StringUtils.hasLength(profile)) {
+ profile = configServerProperties.getDefaultProfile();
+ }
+
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ Environment result = new Environment(application, profiles, label, null, null);
+
+ Set paths = buildParameterPaths(application, profiles);
+ List propertySources = getPropertySources(paths);
+
+ result.addAll(propertySources);
+
+ return result;
+ }
+
+ private Set buildParameterPaths(String application, String[] profiles) {
+ Set result = new LinkedHashSet<>();
+
+ String prefix = environmentProperties.getPrefix();
+ String defaultApplication = configServerProperties.getDefaultApplicationName();
+ String profileSeparator = environmentProperties.getProfileSeparator();
+ String defaultProfile = configServerProperties.getDefaultProfile();
+
+ List orderedProfiles = Stream
+ .concat(Arrays.stream(profiles).filter(p -> !p.equals(defaultProfile)),
+ Arrays.stream(new String[] { defaultProfile }))
+ .collect(Collectors.toList());
+
+ if (application.equals(defaultApplication)) {
+ for (String profile : orderedProfiles) {
+ result.add(prefix + DEFAULT_PATH_SEPARATOR + defaultApplication
+ + profileSeparator + profile + DEFAULT_PATH_SEPARATOR);
+ }
+ }
+ else {
+ for (String profile : orderedProfiles) {
+ result.add(prefix + DEFAULT_PATH_SEPARATOR + application
+ + profileSeparator + profile + DEFAULT_PATH_SEPARATOR);
+
+ result.add(prefix + DEFAULT_PATH_SEPARATOR + defaultApplication
+ + profileSeparator + profile + DEFAULT_PATH_SEPARATOR);
+ }
+
+ result.add(prefix + DEFAULT_PATH_SEPARATOR + application
+ + DEFAULT_PATH_SEPARATOR);
+ }
+
+ result.add(prefix + DEFAULT_PATH_SEPARATOR + defaultApplication
+ + DEFAULT_PATH_SEPARATOR);
+
+ return result;
+ }
+
+ private List getPropertySources(Set parameterPaths) {
+ List result = new ArrayList<>();
+
+ for (String path : parameterPaths) {
+ String name = environmentProperties.getOrigin() + path;
+ Map source = getPropertiesByParameterPath(path);
+
+ if (!source.isEmpty()) {
+ result.add(new PropertySource(name, source));
+ }
+ }
+
+ Map overrides = configServerProperties.getOverrides();
+
+ if (!overrides.isEmpty()) {
+ result.add(0, new PropertySource("overrides", overrides));
+ }
+
+ return result;
+ }
+
+ private Map getPropertiesByParameterPath(String path) {
+ Map result = new HashMap<>();
+
+ GetParametersByPathRequest request = new GetParametersByPathRequest()
+ .withPath(path).withRecursive(environmentProperties.isRecursive())
+ .withWithDecryption(environmentProperties.isDecryptValues())
+ .withMaxResults(environmentProperties.getMaxResults());
+
+ GetParametersByPathResult response = awsSsmClient.getParametersByPath(request);
+
+ if (response != null) {
+ addParametersToProperties(path, response.getParameters(), result);
+
+ while (StringUtils.hasLength(response.getNextToken())) {
+ response = awsSsmClient.getParametersByPath(request.withNextToken(response.getNextToken()));
+
+ addParametersToProperties(path, response.getParameters(), result);
+ }
+ }
+
+ return result;
+ }
+
+ private void addParametersToProperties(String path, List parameters,
+ Map properties) {
+ for (Parameter parameter : parameters) {
+ String name = StringUtils.delete(parameter.getName(), path)
+ .replace(DEFAULT_PATH_SEPARATOR, ".");
+
+ properties.put(name, parameter.getValue());
+ }
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepositoryFactory.java
new file mode 100644
index 00000000..9ee52d91
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepositoryFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018-2020 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 org.springframework.cloud.config.server.environment;
+
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
+import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
+
+import org.springframework.cloud.config.server.config.ConfigServerProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Iulian Antohe
+ */
+public class AwsParameterStoreEnvironmentRepositoryFactory implements
+ EnvironmentRepositoryFactory {
+
+ private final ConfigServerProperties configServerProperties;
+
+ public AwsParameterStoreEnvironmentRepositoryFactory(
+ ConfigServerProperties configServerProperties) {
+ this.configServerProperties = configServerProperties;
+ }
+
+ @Override
+ public AwsParameterStoreEnvironmentRepository build(
+ AwsParameterStoreEnvironmentProperties environmentProperties) {
+ AWSSimpleSystemsManagementClientBuilder clientBuilder = AWSSimpleSystemsManagementClientBuilder
+ .standard();
+
+ String region = environmentProperties.getRegion();
+
+ if (StringUtils.hasLength(region)) {
+ Regions awsRegion = Regions.fromName(region);
+
+ clientBuilder.withRegion(awsRegion);
+
+ String endpoint = environmentProperties.getEndpoint();
+
+ if (StringUtils.hasLength(endpoint)) {
+ AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
+ endpoint, awsRegion.getName());
+
+ clientBuilder.withEndpointConfiguration(endpointConfiguration);
+ }
+ }
+
+ AWSSimpleSystemsManagement client = clientBuilder.build();
+
+ return new AwsParameterStoreEnvironmentRepository(client, configServerProperties,
+ environmentProperties);
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentProperties.java
index 2c1bbd7e..dd472d39 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentProperties.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentProperties.java
@@ -24,8 +24,7 @@ import org.springframework.core.Ordered;
* @author Jose Maria Alvarez
*/
@ConfigurationProperties("spring.cloud.config.server.gcp-secret-manager")
-public class GoogleSecretManagerEnvironmentProperties
- implements EnvironmentRepositoryProperties {
+public class GoogleSecretManagerEnvironmentProperties implements EnvironmentRepositoryProperties {
private int order = Ordered.LOWEST_PRECEDENCE;
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepository.java
index f76da9fc..ee30ad81 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepository.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepository.java
@@ -49,14 +49,12 @@ public class GoogleSecretManagerEnvironmentRepository implements EnvironmentRepo
private GoogleConfigProvider configProvider;
- public GoogleSecretManagerEnvironmentRepository(
- ObjectProvider request, RestTemplate rest,
- GoogleSecretManagerEnvironmentProperties properties) {
+ public GoogleSecretManagerEnvironmentRepository(ObjectProvider request, RestTemplate rest,
+ GoogleSecretManagerEnvironmentProperties properties) {
this.applicationLabel = properties.getApplicationLabel();
this.profileLabel = properties.getProfileLabel();
this.configProvider = new HttpHeaderGoogleConfigProvider(request);
- this.accessStrategy = GoogleSecretManagerAccessStrategyFactory.forVersion(rest,
- configProvider, properties);
+ this.accessStrategy = GoogleSecretManagerAccessStrategyFactory.forVersion(rest, configProvider, properties);
this.tokenMandatory = properties.getTokenMandatory();
}
@@ -72,7 +70,7 @@ public class GoogleSecretManagerEnvironmentRepository implements EnvironmentRepo
profile = "default," + profile;
}
String[] profiles = org.springframework.util.StringUtils
- .trimArrayElements(org.springframework.util.StringUtils.commaDelimitedListToStringArray(profile));
+ .trimArrayElements(org.springframework.util.StringUtils.commaDelimitedListToStringArray(profile));
Environment result = new Environment(application, profile, label, null, null);
if (tokenMandatory) {
if (accessStrategy.checkRemotePermissions()) {
@@ -85,13 +83,11 @@ public class GoogleSecretManagerEnvironmentRepository implements EnvironmentRepo
return result;
}
- private void addPropertySource(String application, String[] profiles,
- Environment result) {
+ private void addPropertySource(String application, String[] profiles, Environment result) {
for (String profileUnit : profiles) {
Map, ?> secrets = getSecrets(application, profileUnit);
if (!secrets.isEmpty()) {
- result.add(new PropertySource("gsm:" + application + "-" + profileUnit,
- secrets));
+ result.add(new PropertySource("gsm:" + application + "-" + profileUnit, secrets));
}
}
}
@@ -103,23 +99,16 @@ public class GoogleSecretManagerEnvironmentRepository implements EnvironmentRepo
*/
private Map, ?> getSecrets(String application, String profile) {
Map result = new HashMap<>();
- String prefix = configProvider
- .getValue(HttpHeaderGoogleConfigProvider.PREFIX_HEADER, false);
+ String prefix = configProvider.getValue(HttpHeaderGoogleConfigProvider.PREFIX_HEADER, false);
for (Secret secret : accessStrategy.getSecrets()) {
- if (secret.getLabelsOrDefault(applicationLabel, "application")
- .equalsIgnoreCase(application)
- && secret.getLabelsOrDefault(profileLabel, "profile")
- .equalsIgnoreCase(profile)) {
- result.put(accessStrategy.getSecretName(secret), accessStrategy
- .getSecretValue(secret, new GoogleSecretComparatorByVersion()));
+ if (secret.getLabelsOrDefault(applicationLabel, "application").equalsIgnoreCase(application)
+ && secret.getLabelsOrDefault(profileLabel, "profile").equalsIgnoreCase(profile)) {
+ result.put(accessStrategy.getSecretName(secret),
+ accessStrategy.getSecretValue(secret, new GoogleSecretComparatorByVersion()));
}
- else if (StringUtils.isNotBlank(prefix)
- && accessStrategy.getSecretName(secret).startsWith(prefix)) {
- result.put(
- StringUtils.removeStart(accessStrategy.getSecretName(secret),
- prefix),
- accessStrategy.getSecretValue(secret,
- new GoogleSecretComparatorByVersion()));
+ else if (StringUtils.isNotBlank(prefix) && accessStrategy.getSecretName(secret).startsWith(prefix)) {
+ result.put(StringUtils.removeStart(accessStrategy.getSecretName(secret), prefix),
+ accessStrategy.getSecretValue(secret, new GoogleSecretComparatorByVersion()));
}
}
return result;
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryFactory.java
index 063967de..16ddf3d0 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryFactory.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryFactory.java
@@ -29,17 +29,14 @@ public class GoogleSecretManagerEnvironmentRepositoryFactory implements
private final ObjectProvider request;
- public GoogleSecretManagerEnvironmentRepositoryFactory(
- ObjectProvider request) {
+ public GoogleSecretManagerEnvironmentRepositoryFactory(ObjectProvider request) {
this.request = request;
}
@Override
public GoogleSecretManagerEnvironmentRepository build(
- GoogleSecretManagerEnvironmentProperties environmentProperties)
- throws Exception {
- return new GoogleSecretManagerEnvironmentRepository(request, new RestTemplate(),
- environmentProperties);
+ GoogleSecretManagerEnvironmentProperties environmentProperties) throws Exception {
+ return new GoogleSecretManagerEnvironmentRepository(request, new RestTemplate(), environmentProperties);
}
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerAccessStrategyFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerAccessStrategyFactory.java
index 9ef225ba..030bf834 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerAccessStrategyFactory.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerAccessStrategyFactory.java
@@ -28,40 +28,33 @@ public final class GoogleSecretManagerAccessStrategyFactory {
throw new IllegalStateException("Can't instantiate an utility class");
}
- public static GoogleSecretManagerAccessStrategy forVersion(RestTemplate rest,
- GoogleConfigProvider configProvider,
- GoogleSecretManagerEnvironmentProperties properties) {
+ public static GoogleSecretManagerAccessStrategy forVersion(RestTemplate rest, GoogleConfigProvider configProvider,
+ GoogleSecretManagerEnvironmentProperties properties) {
switch (properties.getVersion()) {
case 1:
try {
- return new GoogleSecretManagerV1AccessStrategy(rest, configProvider,
- properties.getServiceAccount());
+ return new GoogleSecretManagerV1AccessStrategy(rest, configProvider, properties.getServiceAccount());
}
catch (Exception e) {
throw new RepositoryException("Cannot create service client", e);
}
default:
throw new IllegalArgumentException(
- "No support for given Google Secret manager backend version "
- + properties.getVersion());
+ "No support for given Google Secret manager backend version " + properties.getVersion());
}
}
- public static GoogleSecretManagerAccessStrategy forVersion(RestTemplate rest,
- GoogleConfigProvider configProvider,
- GoogleSecretManagerEnvironmentProperties properties,
- SecretManagerServiceClient client) {
+ public static GoogleSecretManagerAccessStrategy forVersion(RestTemplate rest, GoogleConfigProvider configProvider,
+ GoogleSecretManagerEnvironmentProperties properties, SecretManagerServiceClient client) {
switch (properties.getVersion()) {
case 1:
- return new GoogleSecretManagerV1AccessStrategy(rest, configProvider,
- client);
+ return new GoogleSecretManagerV1AccessStrategy(rest, configProvider, client);
default:
throw new IllegalArgumentException(
- "No support for given Google Secret manager backend version "
- + properties.getVersion());
+ "No support for given Google Secret manager backend version " + properties.getVersion());
}
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerV1AccessStrategy.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerV1AccessStrategy.java
index 6b4b5943..57a94b21 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerV1AccessStrategy.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/GoogleSecretManagerV1AccessStrategy.java
@@ -55,8 +55,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
-public class GoogleSecretManagerV1AccessStrategy
- implements GoogleSecretManagerAccessStrategy {
+public class GoogleSecretManagerV1AccessStrategy implements GoogleSecretManagerAccessStrategy {
private final SecretManagerServiceClient client;
@@ -68,19 +67,14 @@ public class GoogleSecretManagerV1AccessStrategy
private static final String ACCESS_SECRET_PERMISSION = "secretmanager.versions.access";
- private static Log logger = LogFactory
- .getLog(GoogleSecretManagerV1AccessStrategy.class);
+ private static Log logger = LogFactory.getLog(GoogleSecretManagerV1AccessStrategy.class);
- public GoogleSecretManagerV1AccessStrategy(RestTemplate rest,
- GoogleConfigProvider configProvider, String serviceAccountFile)
- throws IOException {
+ public GoogleSecretManagerV1AccessStrategy(RestTemplate rest, GoogleConfigProvider configProvider,
+ String serviceAccountFile) throws IOException {
if (StringUtils.isNotEmpty(serviceAccountFile)) {
- GoogleCredentials creds = GoogleCredentials
- .fromStream(new FileInputStream(new File(serviceAccountFile)));
- this.client = SecretManagerServiceClient.create(SecretManagerServiceSettings
- .newBuilder()
- .setCredentialsProvider(FixedCredentialsProvider.create(creds))
- .build());
+ GoogleCredentials creds = GoogleCredentials.fromStream(new FileInputStream(new File(serviceAccountFile)));
+ this.client = SecretManagerServiceClient.create(SecretManagerServiceSettings.newBuilder()
+ .setCredentialsProvider(FixedCredentialsProvider.create(creds)).build());
}
else {
this.client = SecretManagerServiceClient.create();
@@ -89,8 +83,8 @@ public class GoogleSecretManagerV1AccessStrategy
this.configProvider = configProvider;
}
- public GoogleSecretManagerV1AccessStrategy(RestTemplate rest,
- GoogleConfigProvider configProvider, SecretManagerServiceClient client) {
+ public GoogleSecretManagerV1AccessStrategy(RestTemplate rest, GoogleConfigProvider configProvider,
+ SecretManagerServiceClient client) {
this.client = client;
this.rest = rest;
this.configProvider = configProvider;
@@ -102,12 +96,11 @@ public class GoogleSecretManagerV1AccessStrategy
ProjectName project = ProjectName.of(getProjectId());
// Create the request.
- ListSecretsRequest listSecretRequest = ListSecretsRequest.newBuilder()
- .setParent(project.toString()).build();
+ ListSecretsRequest listSecretRequest = ListSecretsRequest.newBuilder().setParent(project.toString()).build();
// Get all secrets.
SecretManagerServiceClient.ListSecretsPagedResponse pagedListSecretResponse = client
- .listSecrets(listSecretRequest);
+ .listSecrets(listSecretRequest);
List result = new ArrayList();
pagedListSecretResponse.iterateAll().forEach(result::add);
@@ -120,12 +113,12 @@ public class GoogleSecretManagerV1AccessStrategy
SecretName parent = SecretName.parse(secret.getName());
// Create the request.
- ListSecretVersionsRequest listVersionRequest = ListSecretVersionsRequest
- .newBuilder().setParent(parent.toString()).build();
+ ListSecretVersionsRequest listVersionRequest = ListSecretVersionsRequest.newBuilder()
+ .setParent(parent.toString()).build();
// Get all versions.
SecretManagerServiceClient.ListSecretVersionsPagedResponse pagedListVersionResponse = client
- .listSecretVersions(listVersionRequest);
+ .listSecretVersions(listVersionRequest);
List result = new ArrayList();
pagedListVersionResponse.iterateAll().forEach(result::add);
return result;
@@ -137,17 +130,16 @@ public class GoogleSecretManagerV1AccessStrategy
List versions = getSecretVersions(secret);
SecretVersion winner = null;
for (SecretVersion secretVersion : versions) {
- if ((secretVersion.getState()
- .getNumber() == SecretVersion.State.ENABLED_VALUE)
- && comparator.compare(secretVersion, winner) > 0) {
+ if ((secretVersion.getState().getNumber() == SecretVersion.State.ENABLED_VALUE)
+ && comparator.compare(secretVersion, winner) > 0) {
winner = secretVersion;
}
}
if (winner != null) {
SecretVersionName name = SecretVersionName.parse(winner.getName());
// Access the secret version.
- AccessSecretVersionRequest request = AccessSecretVersionRequest.newBuilder()
- .setName(name.toString()).build();
+ AccessSecretVersionRequest request = AccessSecretVersionRequest.newBuilder().setName(name.toString())
+ .build();
AccessSecretVersionResponse response = client.accessSecretVersion(request);
result = response.getPayload().getData().toStringUtf8();
}
@@ -166,27 +158,22 @@ public class GoogleSecretManagerV1AccessStrategy
try {
AccessToken accessToken = new AccessToken(getAccessToken(), null);
GoogleCredentials credential = new GoogleCredentials(accessToken);
- HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(
- credential);
- service = new CloudResourceManager.Builder(
- GoogleNetHttpTransport.newTrustedTransport(),
- JacksonFactory.getDefaultInstance(), requestInitializer)
- .setApplicationName(APPLICATION_NAME).build();
+ HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credential);
+ service = new CloudResourceManager.Builder(GoogleNetHttpTransport.newTrustedTransport(),
+ JacksonFactory.getDefaultInstance(), requestInitializer).setApplicationName(APPLICATION_NAME)
+ .build();
List permissionsList = Arrays.asList(ACCESS_SECRET_PERMISSION);
- TestIamPermissionsRequest requestBody = new TestIamPermissionsRequest()
- .setPermissions(permissionsList);
+ TestIamPermissionsRequest requestBody = new TestIamPermissionsRequest().setPermissions(permissionsList);
TestIamPermissionsResponse testIamPermissionsResponse = service.projects()
- .testIamPermissions(getProjectId(), requestBody).execute();
+ .testIamPermissions(getProjectId(), requestBody).execute();
- if (testIamPermissionsResponse.getPermissions() != null
- && testIamPermissionsResponse.size() >= 1) {
+ if (testIamPermissionsResponse.getPermissions() != null && testIamPermissionsResponse.size() >= 1) {
return Boolean.TRUE;
}
else {
- logger.warn(
- "Access token has no permissions to access secrets in project");
+ logger.warn("Access token has no permissions to access secrets in project");
return Boolean.FALSE;
}
}
@@ -197,26 +184,22 @@ public class GoogleSecretManagerV1AccessStrategy
}
private String getAccessToken() {
- return configProvider.getValue(HttpHeaderGoogleConfigProvider.ACCESS_TOKEN_HEADER,
- true);
+ return configProvider.getValue(HttpHeaderGoogleConfigProvider.ACCESS_TOKEN_HEADER, true);
}
/**
- * @return
+ * @return the Project Id.
*/
private String getProjectId() {
String result = null;
try {
- result = configProvider
- .getValue(HttpHeaderGoogleConfigProvider.PROJECT_ID_HEADER, true);
+ result = configProvider.getValue(HttpHeaderGoogleConfigProvider.PROJECT_ID_HEADER, true);
}
catch (Exception e) {
// not in GCP
- HttpEntity entity = new HttpEntity("parameters",
- getMetadataHttpHeaders());
- result = rest.exchange(
- GoogleSecretManagerEnvironmentProperties.GOOGLE_METADATA_PROJECT_URL,
- HttpMethod.GET, entity, String.class).getBody();
+ HttpEntity entity = new HttpEntity("parameters", getMetadataHttpHeaders());
+ result = rest.exchange(GoogleSecretManagerEnvironmentProperties.GOOGLE_METADATA_PROJECT_URL, HttpMethod.GET,
+ entity, String.class).getBody();
}
return result;
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/HttpHeaderGoogleConfigProvider.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/HttpHeaderGoogleConfigProvider.java
index cee762e2..653c8729 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/HttpHeaderGoogleConfigProvider.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/secretmanager/HttpHeaderGoogleConfigProvider.java
@@ -53,8 +53,7 @@ public class HttpHeaderGoogleConfigProvider implements GoogleConfigProvider {
}
String value = request.getHeader(key);
if (!StringUtils.hasLength(value) && mandatory) {
- throw new IllegalArgumentException(
- "Missing required header in HttpServletRequest: " + key);
+ throw new IllegalArgumentException("Missing required header in HttpServletRequest: " + key);
}
return value;
}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/AdhocTestSuite.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/AdhocTestSuite.java
index 0c434be3..025e1aeb 100644
--- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/AdhocTestSuite.java
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/AdhocTestSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2019 the original author or authors.
+ * Copyright 2013-2020 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.
@@ -32,6 +32,7 @@ import org.springframework.cloud.config.server.encryption.EncryptionControllerTe
import org.springframework.cloud.config.server.encryption.EncryptionIntegrationTests;
import org.springframework.cloud.config.server.encryption.EnvironmentPrefixHelperTests;
import org.springframework.cloud.config.server.encryption.KeyStoreTextEncryptorLocatorTests;
+import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentRepositoryTests;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentRepositoryTests;
import org.springframework.cloud.config.server.environment.CompositeEnvironmentRepositoryTests;
import org.springframework.cloud.config.server.environment.EnvironmentControllerIntegrationTests;
@@ -86,7 +87,7 @@ import org.springframework.cloud.config.server.ssh.SshUriPropertyProcessorTest;
SshPropertyValidatorTest.class, CompositeIntegrationTests.class, SubversionConfigServerIntegrationTests.class,
ConfigServerHealthIndicatorTests.class, CustomCompositeEnvironmentRepositoryTests.class,
CustomEnvironmentRepositoryTests.class, BootstrapConfigServerIntegrationTests.class,
- AwsS3EnvironmentRepositoryTests.class })
+ AwsS3EnvironmentRepositoryTests.class, AwsParameterStoreEnvironmentRepositoryTests.class })
@Ignore
public class AdhocTestSuite {
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepositoryTests.java
new file mode 100644
index 00000000..7c0efed7
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/AwsParameterStoreEnvironmentRepositoryTests.java
@@ -0,0 +1,856 @@
+/*
+ * Copyright 2013-2020 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 org.springframework.cloud.config.server.environment;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
+import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathRequest;
+import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathResult;
+import com.amazonaws.services.simplesystemsmanagement.model.Parameter;
+import com.amazonaws.services.simplesystemsmanagement.model.ParameterType;
+import org.apache.commons.lang3.RandomUtils;
+import org.junit.Test;
+
+import org.springframework.cloud.config.environment.Environment;
+import org.springframework.cloud.config.environment.PropertySource;
+import org.springframework.cloud.config.server.config.ConfigServerProperties;
+import org.springframework.util.StringUtils;
+
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentProperties.DEFAULT_PATH_SEPARATOR;
+
+/**
+ * @author Iulian Antohe
+ */
+public class AwsParameterStoreEnvironmentRepositoryTests {
+
+ private static final Map SHARED_PROPERTIES = new HashMap() {
+ {
+ put("logging.level.root", "warn");
+ put("spring.cache.redis.time-to-live", "0");
+ }
+ };
+
+ private static final Map SHARED_DEFAULT_PROPERTIES = new HashMap() {
+ {
+ put("logging.level.root", "error");
+ put("spring.cache.redis.time-to-live", "1000");
+ }
+ };
+
+ private static final Map SHARED_PRODUCTION_PROPERTIES = new HashMap() {
+ {
+ put("logging.level.root", "fatal");
+ put("spring.cache.redis.time-to-live", "5000");
+ }
+ };
+
+ private static final Map APPLICATION_SPECIFIC_PROPERTIES = new HashMap() {
+ {
+ put("logging.level.com.example.service", "trace");
+ put("spring.cache.redis.time-to-live", "30000");
+ }
+ };
+
+ private static final Map APPLICATION_SPECIFIC_DEFAULT_PROPERTIES = new HashMap() {
+ {
+ put("logging.level.com.example.service", "debug");
+ put("spring.cache.redis.time-to-live", "60000");
+ }
+ };
+
+ private static final Map APPLICATION_SPECIFIC_PRODUCTION_PROPERTIES = new HashMap() {
+ {
+ put("logging.level.com.example.service", "info");
+ put("spring.cache.redis.time-to-live", "300000");
+ }
+ };
+
+ private final AWSSimpleSystemsManagement awsSsmClientMock = mock(
+ AWSSimpleSystemsManagement.class, "aws-ssm-client-mock");
+
+ private final ConfigServerProperties configServerProperties = new ConfigServerProperties();
+
+ private final AwsParameterStoreEnvironmentProperties environmentProperties = new AwsParameterStoreEnvironmentProperties();
+
+ private final AwsParameterStoreEnvironmentRepository repository = new AwsParameterStoreEnvironmentRepository(
+ awsSsmClientMock, configServerProperties, environmentProperties);
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithNullApplicationAndNullProfile() {
+ // Arrange
+ String application = null;
+ String profile = null;
+ String defaultApp = configServerProperties.getDefaultApplicationName();
+ String defaultProfile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(defaultProfile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(defaultApp, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithNullApplicationAndDefaultProfile() {
+ // Arrange
+ String application = null;
+ String profile = configServerProperties.getDefaultProfile();
+ String defaultApp = configServerProperties.getDefaultApplicationName();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(defaultApp, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithNullApplicationAndNonExistentProfile() {
+ // Arrange
+ String application = null;
+ String profile = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String defaultApp = configServerProperties.getDefaultApplicationName();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String name = "aws:ssm:parameter:/config/application/";
+ PropertySource ps = new PropertySource(name, SHARED_PROPERTIES);
+
+ Environment expected = new Environment(defaultApp, profiles, null, null, null);
+ expected.add(ps);
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithNullApplicationAndExistentProfile() {
+ // Arrange
+ String application = null;
+ String profile = "production";
+ String defaultApp = configServerProperties.getDefaultApplicationName();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedProdParamsPsName = "aws:ssm:parameter:/config/application-production/";
+ PropertySource sharedProdParamsPs = new PropertySource(sharedProdParamsPsName,
+ SHARED_PRODUCTION_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(defaultApp, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedProdParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithDefaultApplicationAndNullProfile() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = null;
+ String defaultProfile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(defaultProfile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithDefaultApplicationAndDefaultProfile() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithDefaultApplicationAndNonExistentProfile() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String name = "aws:ssm:parameter:/config/application/";
+ PropertySource ps = new PropertySource(name, SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.add(ps);
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithDefaultApplicationAndExistentProfile() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = "production";
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedProdParamsPsName = "aws:ssm:parameter:/config/application-production/";
+ PropertySource sharedProdParamsPs = new PropertySource(sharedProdParamsPsName,
+ SHARED_PRODUCTION_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedProdParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithNonExistentApplicationAndNullProfile() {
+ // Arrange
+ String application = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String profile = null;
+ String defaultProfile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(defaultProfile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithNonExistentApplicationAndDefaultProfile() {
+ // Arrange
+ String application = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithNonExistentApplicationAndNonExistentProfile() {
+ // Arrange
+ String application = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String profile = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String name = "aws:ssm:parameter:/config/application/";
+ PropertySource ps = new PropertySource(name, SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.add(ps);
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithNonExistentApplicationAndExistentProfile() {
+ // Arrange
+ String application = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String profile = "production";
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedProdParamsPsName = "aws:ssm:parameter:/config/application-production/";
+ PropertySource sharedProdParamsPs = new PropertySource(sharedProdParamsPsName,
+ SHARED_PRODUCTION_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedProdParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ @SuppressWarnings("ConstantConditions")
+ public void testFindOneWithExistentApplicationAndNullProfile() {
+ // Arrange
+ String application = "service";
+ String profile = null;
+ String defaultProfile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(defaultProfile);
+
+ String appSpecificDefaultParamsPsName = "aws:ssm:parameter:/config/service-default/";
+ PropertySource appSpecificDefaultParamsPs = new PropertySource(
+ appSpecificDefaultParamsPsName, APPLICATION_SPECIFIC_DEFAULT_PROPERTIES);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String appSpecificParamsPsName = "aws:ssm:parameter:/config/service/";
+ PropertySource appSpecificParamsPs = new PropertySource(appSpecificParamsPsName,
+ APPLICATION_SPECIFIC_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+
+ expected.addAll(Arrays.asList(appSpecificDefaultParamsPs, sharedDefaultParamsPs,
+ appSpecificParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithExistentApplicationAndDefaultProfile() {
+ // Arrange
+ String application = "service";
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String appSpecificDefaultParamsPsName = "aws:ssm:parameter:/config/service-default/";
+ PropertySource appSpecificDefaultParamsPs = new PropertySource(
+ appSpecificDefaultParamsPsName, APPLICATION_SPECIFIC_DEFAULT_PROPERTIES);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String appSpecificParamsPsName = "aws:ssm:parameter:/config/service/";
+ PropertySource appSpecificParamsPs = new PropertySource(appSpecificParamsPsName,
+ APPLICATION_SPECIFIC_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+
+ expected.addAll(Arrays.asList(appSpecificDefaultParamsPs, sharedDefaultParamsPs,
+ appSpecificParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithExistentApplicationAndNonExistentProfile() {
+ // Arrange
+ String application = "service";
+ String profile = randomAlphabetic(RandomUtils.nextInt(3, 33));
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String appSpecificParamsPsName = "aws:ssm:parameter:/config/service/";
+ PropertySource appSpecificParamsPs = new PropertySource(appSpecificParamsPsName,
+ APPLICATION_SPECIFIC_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(appSpecificParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithExistentApplicationAndExistentProfile() {
+ // Arrange
+ String application = "service";
+ String profile = "production";
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String appSpecificProdParamsPsName = "aws:ssm:parameter:/config/service-production/";
+ PropertySource appSpecificProdParamsPs = new PropertySource(
+ appSpecificProdParamsPsName, APPLICATION_SPECIFIC_PRODUCTION_PROPERTIES);
+
+ String sharedProdParamsPsName = "aws:ssm:parameter:/config/application-production/";
+ PropertySource sharedProdParamsPs = new PropertySource(sharedProdParamsPsName,
+ SHARED_PRODUCTION_PROPERTIES);
+
+ String appSpecificParamsPsName = "aws:ssm:parameter:/config/service/";
+ PropertySource appSpecificParamsPs = new PropertySource(appSpecificParamsPsName,
+ APPLICATION_SPECIFIC_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+
+ expected.addAll(Arrays.asList(appSpecificProdParamsPs, sharedProdParamsPs,
+ appSpecificParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithExistentApplicationAndMultipleExistentProfiles() {
+ // Arrange
+ String application = "service";
+ String profile = configServerProperties.getDefaultProfile() + ",production";
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String appSpecificProdParamsPsName = "aws:ssm:parameter:/config/service-production/";
+ PropertySource appSpecificProdParamsPs = new PropertySource(
+ appSpecificProdParamsPsName, APPLICATION_SPECIFIC_PRODUCTION_PROPERTIES);
+
+ String sharedProdParamsPsName = "aws:ssm:parameter:/config/application-production/";
+ PropertySource sharedProdParamsPs = new PropertySource(sharedProdParamsPsName,
+ SHARED_PRODUCTION_PROPERTIES);
+
+ String appSpecificDefaultParamsPsName = "aws:ssm:parameter:/config/service-default/";
+ PropertySource appSpecificDefaultParamsPs = new PropertySource(
+ appSpecificDefaultParamsPsName, APPLICATION_SPECIFIC_DEFAULT_PROPERTIES);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String appSpecificParamsPsName = "aws:ssm:parameter:/config/service/";
+ PropertySource appSpecificParamsPs = new PropertySource(appSpecificParamsPsName,
+ APPLICATION_SPECIFIC_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+
+ expected.addAll(Arrays.asList(appSpecificProdParamsPs, sharedProdParamsPs,
+ appSpecificDefaultParamsPs, sharedDefaultParamsPs, appSpecificParamsPs,
+ sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithOverrides() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ Map overrides = new HashMap(4) {
+ {
+ put("logging.level.root", "boom");
+ put("logging.level.com.example.service", "boom");
+ put("spring.cache.redis.time-to-live", "-1");
+ }
+ };
+
+ configServerProperties.setOverrides(overrides);
+
+ PropertySource overridesPs = new PropertySource("overrides", overrides);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+
+ expected.addAll(
+ Arrays.asList(overridesPs, sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithSlashesInTheParameterKeyPath() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected, true, false);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithPaginatedAwsSsmClientResponse() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ environmentProperties.setMaxResults(1);
+
+ String sharedDefaultParamsPsName = "aws:ssm:parameter:/config/application-default/";
+ PropertySource sharedDefaultParamsPs = new PropertySource(
+ sharedDefaultParamsPsName, SHARED_DEFAULT_PROPERTIES);
+
+ String sharedParamsPsName = "aws:ssm:parameter:/config/application/";
+ PropertySource sharedParamsPs = new PropertySource(sharedParamsPsName,
+ SHARED_PROPERTIES);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+ expected.addAll(Arrays.asList(sharedDefaultParamsPs, sharedParamsPs));
+
+ setupAwsSsmClientMocks(expected, false, true);
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ @Test
+ public void testFindOneWithNoParametersInThePaths() {
+ // Arrange
+ String application = configServerProperties.getDefaultApplicationName();
+ String profile = configServerProperties.getDefaultProfile();
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+
+ Environment expected = new Environment(application, profiles, null, null, null);
+
+ when(awsSsmClientMock.getParametersByPath(any(GetParametersByPathRequest.class)))
+ .thenReturn(new GetParametersByPathResult());
+
+ // Act
+ Environment result = repository.findOne(application, profile, null);
+
+ // Assert
+ assertThat(result).usingRecursiveComparison().withStrictTypeChecking()
+ .isEqualTo(expected);
+ }
+
+ private void setupAwsSsmClientMocks(Environment environment) {
+ setupAwsSsmClientMocks(environment, false, false);
+ }
+
+ private void setupAwsSsmClientMocks(Environment environment,
+ boolean withSlashesForPropertyName, boolean paginatedResponse) {
+ for (PropertySource ps : environment.getPropertySources()) {
+ String path = StringUtils.delete(ps.getName(),
+ environmentProperties.getOrigin());
+
+ GetParametersByPathRequest request = new GetParametersByPathRequest()
+ .withPath(path).withRecursive(environmentProperties.isRecursive())
+ .withWithDecryption(environmentProperties.isDecryptValues())
+ .withMaxResults(environmentProperties.getMaxResults());
+
+ Set parameters = getParameters(ps, path,
+ withSlashesForPropertyName);
+
+ GetParametersByPathResult response = new GetParametersByPathResult()
+ .withParameters(parameters);
+
+ if (paginatedResponse
+ && environmentProperties.getMaxResults() < parameters.size()) {
+ List> chunks = splitParametersIntoChunks(parameters);
+
+ String nextToken = null;
+
+ for (int i = 0; i < chunks.size(); i++) {
+ Set chunk = chunks.get(i);
+
+ if (i == 0) {
+ nextToken = generateNextToken();
+
+ GetParametersByPathResult responseClone = response.clone()
+ .withParameters(chunk).withNextToken(nextToken);
+
+ when(awsSsmClientMock.getParametersByPath(eq(request)))
+ .thenReturn(responseClone);
+ }
+ else if (i == chunks.size() - 1) {
+ GetParametersByPathRequest requestClone = request.clone().withNextToken(nextToken);
+ GetParametersByPathResult responseClone = response.clone().withParameters(chunk);
+
+ when(awsSsmClientMock.getParametersByPath(eq(requestClone)))
+ .thenReturn(responseClone);
+ }
+ else {
+ String newNextToken = generateNextToken();
+
+ GetParametersByPathRequest requestClone = request.clone()
+ .withNextToken(nextToken);
+
+ GetParametersByPathResult responseClone = response.clone()
+ .withParameters(chunk).withNextToken(newNextToken);
+
+ when(awsSsmClientMock.getParametersByPath(eq(requestClone)))
+ .thenReturn(responseClone);
+
+ nextToken = newNextToken;
+ }
+ }
+ }
+ else {
+ when(awsSsmClientMock.getParametersByPath(eq(request)))
+ .thenReturn(response);
+ }
+ }
+ }
+
+ private Set getParameters(PropertySource propertySource, String path,
+ boolean withSlashesForPropertyName) {
+ Function, Parameter> mapper = p -> new Parameter()
+ .withName(path + (withSlashesForPropertyName
+ ? ((String) p.getKey()).replace(".", DEFAULT_PATH_SEPARATOR)
+ : p.getKey()))
+ .withType(ParameterType.String).withValue((String) p.getValue())
+ .withVersion(1L);
+
+ return propertySource.getSource().entrySet().stream().map(mapper)
+ .collect(Collectors.toSet());
+ }
+
+ private List> splitParametersIntoChunks(Set parameters) {
+ AtomicInteger counter = new AtomicInteger();
+
+ Collector>> collector = Collectors
+ .groupingBy(
+ p -> counter.getAndIncrement()
+ / environmentProperties.getMaxResults(),
+ Collectors.toSet());
+
+ return new ArrayList<>(parameters.stream().collect(collector).values());
+ }
+
+ private String generateNextToken() {
+ String random = randomAlphabetic(RandomUtils.nextInt(3, 33));
+
+ return Base64.getEncoder()
+ .encodeToString(random.getBytes(StandardCharsets.UTF_8));
+ }
+
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryTests.java
index e6377b13..b0528e73 100644
--- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryTests.java
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/GoogleSecretManagerEnvironmentRepositoryTests.java
@@ -53,8 +53,8 @@ public class GoogleSecretManagerEnvironmentRepositoryTests {
GoogleSecretManagerEnvironmentProperties properties = new GoogleSecretManagerEnvironmentProperties();
SecretManagerServiceClient mock = mock(SecretManagerServiceClient.class);
properties.setVersion(1);
- assertThat(GoogleSecretManagerAccessStrategyFactory.forVersion(null, null,
- properties, mock) instanceof GoogleSecretManagerV1AccessStrategy).isTrue();
+ assertThat(GoogleSecretManagerAccessStrategyFactory.forVersion(null, null, properties,
+ mock) instanceof GoogleSecretManagerV1AccessStrategy).isTrue();
}
@Test(expected = IllegalArgumentException.class)
@@ -70,19 +70,16 @@ public class GoogleSecretManagerEnvironmentRepositoryTests {
public void testGetSecrets() throws IOException {
RestTemplate rest = mock(RestTemplate.class);
GoogleConfigProvider provider = mock(HttpHeaderGoogleConfigProvider.class);
- when(provider.getValue(HttpHeaderGoogleConfigProvider.PROJECT_ID_HEADER, true))
- .thenReturn("test-project");
+ when(provider.getValue(HttpHeaderGoogleConfigProvider.PROJECT_ID_HEADER, true)).thenReturn("test-project");
SecretManagerServiceClient mock = mock(SecretManagerServiceClient.class);
SecretManagerServiceClient.ListSecretsPagedResponse response = mock(
SecretManagerServiceClient.ListSecretsPagedResponse.class);
- Secret secret = Secret.newBuilder().setName("projects/test-project/secrets/test")
- .build();
+ Secret secret = Secret.newBuilder().setName("projects/test-project/secrets/test").build();
List secrets = new ArrayList();
secrets.add(secret);
when(response.iterateAll()).thenReturn(secrets);
Mockito.doReturn(response).when(mock).listSecrets(any(ListSecretsRequest.class));
- GoogleSecretManagerV1AccessStrategy strategy = new GoogleSecretManagerV1AccessStrategy(
- rest, provider, mock);
+ GoogleSecretManagerV1AccessStrategy strategy = new GoogleSecretManagerV1AccessStrategy(rest, provider, mock);
assertThat(strategy.getSecrets().size()).isEqualTo(1);
}
@@ -91,27 +88,21 @@ public class GoogleSecretManagerEnvironmentRepositoryTests {
public void testGetSecretValues() throws IOException {
RestTemplate rest = mock(RestTemplate.class);
GoogleConfigProvider provider = mock(HttpHeaderGoogleConfigProvider.class);
- when(provider.getValue(HttpHeaderGoogleConfigProvider.PROJECT_ID_HEADER, true))
- .thenReturn("test-project");
+ when(provider.getValue(HttpHeaderGoogleConfigProvider.PROJECT_ID_HEADER, true)).thenReturn("test-project");
SecretManagerServiceClient mock = mock(SecretManagerServiceClient.class);
SecretManagerServiceClient.ListSecretVersionsPagedResponse response = mock(
SecretManagerServiceClient.ListSecretVersionsPagedResponse.class);
- SecretVersion secret1 = SecretVersion.newBuilder()
- .setName("projects/test-project/secrets/test/versions/1")
+ SecretVersion secret1 = SecretVersion.newBuilder().setName("projects/test-project/secrets/test/versions/1")
.setState(SecretVersion.State.ENABLED).build();
- SecretVersion secret2 = SecretVersion.newBuilder()
- .setName("projects/test-project/secrets/test/versions/2")
+ SecretVersion secret2 = SecretVersion.newBuilder().setName("projects/test-project/secrets/test/versions/2")
.setState(SecretVersion.State.DISABLED).build();
List secrets = new ArrayList();
secrets.add(secret1);
secrets.add(secret2);
when(response.iterateAll()).thenReturn(secrets);
- Mockito.doReturn(response).when(mock)
- .listSecretVersions(any(ListSecretVersionsRequest.class));
- GoogleSecretManagerV1AccessStrategy strategy = new GoogleSecretManagerV1AccessStrategy(
- rest, provider, mock);
- AccessSecretVersionResponse accessSecretVersionResponse = mock(
- AccessSecretVersionResponse.class);
+ Mockito.doReturn(response).when(mock).listSecretVersions(any(ListSecretVersionsRequest.class));
+ GoogleSecretManagerV1AccessStrategy strategy = new GoogleSecretManagerV1AccessStrategy(rest, provider, mock);
+ AccessSecretVersionResponse accessSecretVersionResponse = mock(AccessSecretVersionResponse.class);
SecretPayload payload = mock(SecretPayload.class);
ByteString data = mock(ByteString.class);
when(accessSecretVersionResponse.getPayload()).thenReturn(payload);
@@ -119,19 +110,15 @@ public class GoogleSecretManagerEnvironmentRepositoryTests {
when(data.toStringUtf8()).thenReturn("test-value");
ArgumentMatcher matcher = new ArgumentMatcher() {
@Override
- public boolean matches(
- AccessSecretVersionRequest accessSecretVersionRequest) {
- if (accessSecretVersionRequest.getName()
- .equals("projects/test-project/secrets/test/versions/1")) {
+ public boolean matches(AccessSecretVersionRequest accessSecretVersionRequest) {
+ if (accessSecretVersionRequest.getName().equals("projects/test-project/secrets/test/versions/1")) {
return true;
}
return false;
}
};
- Mockito.doReturn(accessSecretVersionResponse).when(mock)
- .accessSecretVersion(ArgumentMatchers.argThat(matcher));
- assertThat(strategy.getSecretValue(
- Secret.newBuilder().setName("projects/test-project/secrets/test").build(),
+ Mockito.doReturn(accessSecretVersionResponse).when(mock).accessSecretVersion(ArgumentMatchers.argThat(matcher));
+ assertThat(strategy.getSecretValue(Secret.newBuilder().setName("projects/test-project/secrets/test").build(),
new GoogleSecretComparatorByVersion())).isEqualTo("test-value");
}