= Parameter Store Integration

Spring Cloud AWS adds support for loading configuration properties from Parameter Store through Spring Boot https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-files-importing[config import feature].

Maven coordinates, using <<index.adoc#bill-of-materials, Spring Cloud AWS BOM>>:

[source,xml]
----
<dependency>
    <groupId>io.awspring.cloud</groupId>
    <artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
</dependency>
----

== Loading External Configuration

To fetch parameters from Parameter Store and add them to Spring's environment properties, add `spring.config.import` property to `application.properties`:

For example, assuming that the parameters in Parameter Store are stored under path `/config/spring`:

|===
| Parameter Name | Parameter Value

| `/config/spring/message` | `Welcome`

| `/config/spring/httpUrl` | `https://external-service:3030/`
|===

Once `spring.config.import` statement is added:

[source,properties]
----
spring.config.import=aws-parameterstore:/config/spring
----

Two parameters are added to environment: `message` and `httpUrl`.

If a given path in Parameter Store does not exist, application will fail to start. If parameters retrieved from Parameter Store are not required for the application, and it should continue to startup even when the path is missing, add `optional` before prefix:

[source,properties]
----
spring.config.import=optional:aws-parameterstore:/config/spring
----

To load parameters from multiple paths, separate their names with `;`:

[source,properties]
----
spring.config.import=aws-parameterstore:/config/spring;/config/app
----

If some parameters are required, and other ones are optional, list them as separate entries in `spring.config.import` property:

[source,properties]
----
spring.config.import[0]=optional:aws-parameterstore=/config/spring
spring.config.import[1]=aws-parameterstore=/config/optional-params/
----

If you add indexed parameter names such as `/config/application/cloud.aws.stack_0_.name`, `/config/application/cloud.aws.stack_1_.name`, ... to Parameter Store,
these will become accessible as array properties `cloud.aws.stack[0].name`, `cloud.aws.stack[1].name`, ... in Spring.

=== Adding prefix to property keys

To avoid property key collisions it is possible to configure a property key prefix that gets added to each resolved parameter.

As an example, assuming the following parameters are stored under path `/config/my-datasource`:

|===
| Parameter Name | Parameter Value

| `/config/my-datasource/url` | `jdbc:mysql://localhost:3306`

| `/config/my-datasource/username` | `db-user`

|===

By default, `url` and `username` properties will be added to the Spring environment. To add a prefix to property keys configure `spring.config.import` property with `?prefix=` added to the parameter path:

[source,properties]
----
spring.config.import=aws-parameterstore:/config/my-datasource/?prefix=spring.datasource.
----

With such config, properties `spring.datasource.url` and `spring.datasource.username` are added to the Spring environment.

NOTE: Prefixes are added as-is to all property names returned by Parameter Store. If you want key names to be separated with a dot between the prefix and key name, make sure to add a trailing dot to the prefix.

== Using SsmClient

The starter automatically configures and registers a `SsmClient` bean in the Spring application context. The `SsmClient` bean can be used to create or retrieve parameters from Parameter Store.

[source,java]
----
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.ssm.SsmClient;
...
@Autowired
private SsmClient ssmClient;
...
ssmClient.getParametersByPath(request -> request.path("/config/spring/")).parameters();
----

== Customizing SsmClient

To use custom `SsmClient` in `spring.config.import`, provide an implementation of `BootstrapRegistryInitializer`. For example:

[source,java]
----
package com.app;

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ssm.SsmClient;

import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;

class ParameterStoreBootstrapConfiguration implements BootstrapRegistryInitializer {

    @Override
    public void initialize(BootstrapRegistry registry) {
        registry.register(SsmClient.class, context -> {
			AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create("yourAccessKey", "yourSecretKey"));
			return SsmClient.builder().credentialsProvider(awsCredentialsProvider).region(Region.EU_WEST_2).build();
		});
    }
}
----

Note that this class must be listed under `org.springframework.boot.BootstrapRegistryInitializer` key in `META-INF/spring.factories`:

[source, properties]
----
org.springframework.boot.BootstrapRegistryInitializer=com.app.ParameterStoreBootstrapConfiguration
----

If you want to use autoconfigured `SsmClient` but change underlying SDKClient or ClientOverrideConfiguration you will need to register bean of type `AwsClientConfigurerParameterStore`:
Autoconfiguration will configure `SsmClient` Bean with provided values after that, for example:

[source,java]
----
package com.app;

import io.awspring.cloud.autoconfigure.config.parameterstore.AwsParameterStoreClientCustomizer;
import java.time.Duration;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.ssm.SsmClientBuilder;

class ParameterStoreBootstrapConfiguration implements BootstrapRegistryInitializer {

	@Override
	public void initialize(BootstrapRegistry registry) {
		registry.register(AwsParameterStoreClientCustomizer.class,
            context -> new AwsParameterStoreClientCustomizer() {

                @Override
                public ClientOverrideConfiguration overrideConfiguration() {
                    return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(500))
                            .build();
                }

                @Override
                public SdkHttpClient httpClient() {
                    return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1000)).build();
                }
            });
    }
}
----

== `PropertySource` Reload

Some applications may need to detect changes on external property sources and update their internal status to reflect the new configuration.
The reload feature of Spring Cloud AWS Parameter Store integration is able to trigger an application reload when a related parameter value changes.

By default, this feature is disabled. You can enable it by using the `spring.cloud.aws.parameterstore.reload.strategy` configuration property (for example, in the `application.properties` file).

The following levels of reload are supported (by setting the `spring.cloud.aws.parameterstore.reload.strategy` property):

* `refresh` (default): Only configuration beans annotated with `@ConfigurationProperties` or `@RefreshScope` are reloaded.
This reload level leverages the refresh feature of Spring Cloud Context.

* `restart_context`: the whole Spring `ApplicationContext` is gracefully restarted. Beans are recreated with the new configuration.
In order for the restart context functionality to work properly you must enable and expose the restart actuator endpoint
[source,yaml]
====
----
management:
  endpoint:
    restart:
      enabled: true
  endpoints:
    web:
      exposure:
        include: restart
----
====

Assuming that the reload feature is enabled with default settings (`refresh` mode), the following bean is refreshed when the secret changes:

====
[java, source]
----
@Configuration
@ConfigurationProperties(prefix = "bean")
public class MyConfig {

    private String message = "a message that can be changed live";

    // getter and setters

}
----
====

To see that changes effectively happen, you can create another bean that prints the message periodically, as follows

====
[source,java]
----
@Component
public class MyBean {

    @Autowired
    private MyConfig config;

    @Scheduled(fixedDelay = 5000)
    public void hello() {
        System.out.println("The message is: " + config.getMessage());
    }
}
----
====

The reload feature periodically re-creates the configuration from config maps and secrets to see if it has changed.
You can configure the polling period by using the `spring.cloud.aws.parameter.reload.period` (default value is 1 minute).

== Configuration

The Spring Boot Starter for Parameter Store provides the following configuration options:

[cols="4,3,1,1"]
|===
| Name | Description | Required | Default value

| `spring.cloud.aws.parameterstore.endpoint` | Configures endpoint used by `SsmClient`. | No | `null`
| `spring.cloud.aws.parameterstore.region` | Configures region used by `SsmClient`. | No | `null`
| `spring.cloud.aws.parameterstore.reload.strategy` | `Enum` | `refresh` | The strategy to use when firing a reload (`refresh`, `restart_context`)
| `spring.cloud.aws.parameterstore.reload.period` | `Duration`| `15s` | The period for verifying changes
| `spring.cloud.aws.parameterstore.reload.max-wait-time-for-restart` | `Duration`| `2s` | The maximum time between the detection of changes in property source and the application context restart when `restart_context` strategy is used.
|===

== IAM Permissions
Following IAM permissions are required by Spring Cloud AWS:

[cols="2"]
|===
| Get parameter from specific path
| `ssm:GetParametersByPath`
|===

Sample IAM policy granting access to Parameter Store:

[source,json,indent=0]
----
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ssm:GetParametersByPath",
            "Resource": "yourArn"
        }
    ]
}
----
