Search code examples
springspring-bootspring-cloud-configspring-boot-starter

Including profiles via a Spring Boot starter


I'm having issues with a custom Spring Boot starter. How can a starter cause a profile to be included and pull related configuration from a config server?

Perhaps my use case is unique, because I haven't found any helpful information online. I'm working in an enterprise environment and this starter is for use by my team, so we're able to control some things (like profile names) that perhaps wouldn't make sense in the open source world.

Here is the scenario: We have a Spring Cloud Config Server running to provide configuration. Across our Spring Boot projects, we have standardized on certain profile names such as "prod" and "nonprod" to control configuration in our different environments. I am trying to create a starter to provide reusable functionality. For example purposes, let's say I'm creating a starter that provides an interface to an appliance that performs cryptographic work for us. This starter will need the IP address of the appliance and various other configuration which differs between production and non-production.

Within the config repo, I will have files such as application.yml, application-nonprod.yml, application-nonprodEncryption.yml, etc.

My goal is to have the custom encryption starter automatically include the nonprodEncryption profile when that starter is included in an application. By doing this, apps which don't need encryption do not load the encryption related properties.

Here are my experimental findings so far:

  • Within an application's bootstrap.yml, I can put a block such as

    spring.profiles: nonprod
    spring:
      profiles:
        include:
          - nonprodEncryption
    

    and that produces the desired result (i.e. the application-nonprodEncryption.yml file is loaded from the config server and used), but this is an undesirable solution as every app that uses my custom starter would need to include this boilerplate configuration.

  • When I move the above configuration to the starter's bootstrap.yml, it seems to have no effect.

  • When I move the above configuration to the starter's application.yml, it seems to be applied (i.e. it shows up in the The following profiles are active: list), but it is too late in the lifecycle to cause the appropriate configuration to be pulled from the config server.

Other things I've considered:

  • Why not just put all of the configuration into the main profile config file (e.g. application-nonprod.yml)? From a separation of concerns and maintenance standpoint, I'd like to keep configuration for individual starters isolated from each other. Also, some configuration data is more sensitive than other config data, so I don't like the idea of exposing all of the configuration to all apps, since many apps won't need some of the more sensitive configuration. Yes, they could get to it, but why load it into their memory if they don't need it?

  • Why not just specify the extra profiles when we launch the app? These apps will be running on a cloud platform. The platform will specify either "prod" or "nonprod" as the profile based on which tier the app is running in. I want to manage that at the platform level rather than the app level, so I want the list of profiles provided at app launch to be uniform across all apps (add adding, for example, nonprodEncryption to the list just gets me into the same situation as above - all apps would have all of the configuration, so I might as well just put it all in a single file).

We are currently using Spring Boot 1.5.10.

Any thoughts on how to achieve what I'm trying to do?


Solution

  • I finally found a solution (in case anyone else finds themselves in the same spot).

    Step 1: Add a configuration class like this to your starter:

    package com.company.bootstrap;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.Profile;
    
    @Configuration
    @Profile("nonprod")
    public class BootstrapNonprod {
        public BootstrapNonprod(ConfigurableApplicationContext ctx) {
            ctx.getEnvironment().addActiveProfile("nonprodEncryption");
        }
    }
    

    This will conditionally add a profile. In this example, whenever the "nonprod" profile is active, this class will add the "nonprodEncryption" profile.

    Step 2: In your starter's spring.factories file, add a line such as this:

    org.springframework.cloud.bootstrap.BootstrapConfiguration=com.company.bootstrap.BootstrapNonprod
    

    It seems like it is just that simple.