Search code examples
javaspringamazon-web-servicesspring-bootaws-parameter-store

Spring boot reading parameters from AWS parameter store


I created spring boot(gradle) application, and included dependency: org.springframework.cloud:spring-cloud-starter-aws-parameter-store-config.

I want to use AWSSimpleSystemsManagement to read configuration from AWS parameter store, but I am forced to write it like this (in aws):

 config/application_dev/server.port: 8080

Is there any way to read something like this from spring boot: dev.application.server.port:8080

currently all of this is managed from autoconfiguration I think, is there a way to override it


Solution

  • In application.properties you can define property server.port=8081.

    The parameter formats supported by spring-cloud-starter-aws-parameter-store-config are:

    • /config/application/server.port
    • /config/application_dev/server.port
    • /config/my-service/server.port
    • /config/my-service_dev/server.port

    By defining the following properties in the bootstrap.properties you can change the format in some way:

    spring.application.name=my-service
    aws.paramstore.prefix=/config
    aws.paramstore.defaultContext=application
    aws.paramstore.profileSeparator=_
    

    But only a simple customisations are supported because the main parameter naming logic is hardcode in the AwsParamStorePropertySourceLocator.

    To dramatically change the parameter format you have to define a custom PropertySourceLocator and register it as bootstrap configuration.

    The problem is that dev.application.server.port is invalid parameter name.

    AWS Systems Manager Parameter Store uses / as a path separator and Spring uses the operation get-parameters-by-path. A workaround is to use name dev.application/server.port.

    But this name is invalid also. Parameter name must be a fully qualified name, so the valid name is /dev.application/server.port.

    To support such parameter format define a custom PropertySourceLocator

    @Configuration
    public class CustomAwsParamStorePropertySourceLocator implements PropertySourceLocator {
    
      private static final Logger LOGGER =
          LoggerFactory.getLogger(CustomAwsParamStorePropertySourceLocator.class);
    
      private AWSSimpleSystemsManagement ssmClient;
    
      private List<String> contexts = new ArrayList<>();
    
      public CustomAwsParamStorePropertySourceLocator(AWSSimpleSystemsManagement ssmClient) {
        this.ssmClient = ssmClient;
      }
    
      public List<String> getContexts() {
        return contexts;
      }
    
      @Override
      public PropertySource<?> locate(Environment environment) {
        if (!(environment instanceof ConfigurableEnvironment)) {
          return null;
        }
    
        ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
    
        List<String> profiles = Arrays.asList(env.getActiveProfiles());
    
        String defaultAppName = "application";
        this.contexts.add("/" + defaultAppName + "/");
        addProfiles(this.contexts, defaultAppName, profiles);
    
        String appName = env.getProperty("spring.application.name");
        this.contexts.add("/" + appName + "/");
        addProfiles(this.contexts, appName, profiles);
    
        Collections.reverse(this.contexts);
    
        CompositePropertySource composite = new CompositePropertySource("custom-aws-param-store");
    
        for (String propertySourceContext : this.contexts) {
          try {
            composite.addPropertySource(create(propertySourceContext));
          } catch (Exception e) {
            LOGGER.warn("Unable to load AWS config from " + propertySourceContext, e);
          }
        }
    
        return composite;
      }
    
      private void addProfiles(List<String> contexts, String appName, List<String> profiles) {
        for (String profile : profiles) {
          contexts.add("/" + profile + "." + appName + "/");
        }
      }
    
      private AwsParamStorePropertySource create(String context) {
        AwsParamStorePropertySource propertySource =
            new AwsParamStorePropertySource(context, this.ssmClient);
        propertySource.init();
        return propertySource;
      }
    }
    

    and register it in the bootstrap context by adding a file META-INF/spring.factories

    org.springframework.cloud.bootstrap.BootstrapConfiguration=\
    com.example.CustomAwsParamStorePropertySourceLocator