diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index ea2dd79a..93facf98 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -4,6 +4,7 @@ |spring.cloud.compatibility-verifier.compatible-boot-versions | | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.4.x} |spring.cloud.compatibility-verifier.enabled | `+++false+++` | Enables creation of Spring Cloud compatibility verification. |spring.cloud.config.allow-override | `+++true+++` | Flag to indicate that {@link #isOverrideSystemProperties() systemPropertiesOverride} can be used. Set to false to prevent users from changing the default accidentally. Default true. +|spring.cloud.config.initialize-on-context-refresh | `+++false+++` | Flag to initialize bootstrap configuration on context refresh event. Default false. |spring.cloud.config.override-none | `+++false+++` | Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is true, external properties should take lowest priority and should not override any existing property sources (including local config files). Default false. |spring.cloud.config.override-system-properties | `+++true+++` | Flag to indicate that the external properties should override system properties. Default true. |spring.cloud.decrypt-environment-post-processor.enabled | `+++true+++` | Enable the DecryptEnvironmentPostProcessor. diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index c6698e83..e26008e8 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -54,6 +54,10 @@ The additional property sources are: An example would be properties from the Spring Cloud Config Server. See "`<>`" for how to customize the contents of this property source. +NOTE: Prior to Spring Cloud 2021.0.7 `PropertySourceLocators` (including the ones for Spring Cloud Config) were run during +the main application context and not in the Bootstrap context. You can force `PropertySourceLocators` to be run during the +Bootstrap context by setting `spring.cloud.config.initialize-on-context-refresh=true` in `bootstrap.[properties | yaml]`. + * "`applicationConfig: [classpath:bootstrap.yml]`" (and related files if Spring profiles are active): If you have a `bootstrap.yml` (or `.properties`), those properties are used to configure the bootstrap context. Then they get added to the child context when its parent is set. They have lower precedence than the `application.yml` (or `.properties`) and any other property sources that are added to the child as a normal part of the process of creating a Spring Boot application. @@ -146,6 +150,12 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomP ---- ==== +As of Spring Cloud 2021.0.8, Spring Cloud will now call `PropertySourceLocators` twice. The first fetch +will retrieve any property sources without any profiles. These property sources will have the opportunity to +activate profiles using `spring.profiles.active`. After the main application context starts `PropertySourceLocators` +will be called a second time, this time with any active profiles allowing `PropertySourceLocators` to locate +any additional `PropertySources` with profiles. + === Logging Configuration If you use Spring Boot to configure log settings, you should place this configuration in `bootstrap.[yml | properties]` if you would like it to apply to all events. diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java index 76e2fba7..dd4d616c 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java @@ -294,6 +294,13 @@ public class BootstrapApplicationListener implements ApplicationListener, Ordered { +public class PropertySourceBootstrapConfiguration implements ApplicationListener, + ApplicationContextInitializer, Ordered { /** * Bootstrap property source name. @@ -76,6 +81,9 @@ public class PropertySourceBootstrapConfiguration @Autowired(required = false) private List propertySourceLocators = new ArrayList<>(); + @Autowired + private PropertySourceBootstrapProperties bootstrapProperties; + @Override public int getOrder() { return this.order; @@ -85,8 +93,25 @@ public class PropertySourceBootstrapConfiguration this.propertySourceLocators = new ArrayList<>(propertySourceLocators); } + /* + * The ApplicationListener is called when the main application context is initialized. + * This will be called after the ApplicationListener ContextRefreshedEvent is fired + * during the bootstrap phase. This method is also what added PropertySources prior to + * Spring Cloud 2021.0.7, this is why it will be called when + * spring.cloud.config.initialize-on-context-refresh is false. When + * spring.cloud.config.initialize-on-context-refresh is true this method provides a + * "second fetch" of configuration data to fetch any additional configuration data + * from profiles that have been activated. + */ @Override public void initialize(ConfigurableApplicationContext applicationContext) { + if (!bootstrapProperties.isInitializeOnContextRefresh() || !applicationContext.getEnvironment() + .getPropertySources().contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) { + doInitialize(applicationContext); + } + } + + private void doInitialize(ConfigurableApplicationContext applicationContext) { List> composite = new ArrayList<>(); AnnotationAwareOrderComparator.sort(this.propertySourceLocators); boolean empty = true; @@ -122,7 +147,7 @@ public class PropertySourceBootstrapConfiguration insertPropertySources(propertySources, composite); reinitializeLoggingSystem(environment, logConfig, logFile); setLogLevels(applicationContext, environment); - handleIncludedProfiles(environment); + handleProfiles(environment); } } @@ -172,7 +197,12 @@ public class PropertySourceBootstrapConfiguration if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) { for (PropertySource p : reversedComposite) { - propertySources.addFirst(p); + if (propertySources.contains(DECRYPTED_PROPERTY_SOURCE_NAME)) { + propertySources.addAfter(DECRYPTED_PROPERTY_SOURCE_NAME, p); + } + else { + propertySources.addFirst(p); + } } return; } @@ -210,43 +240,98 @@ public class PropertySourceBootstrapConfiguration return environment; } - private void handleIncludedProfiles(ConfigurableEnvironment environment) { + private void handleProfiles(ConfigurableEnvironment environment) { + if (bootstrapProperties.isInitializeOnContextRefresh() && !environment.getPropertySources() + .contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) { + // In the case that spring.cloud.config.initialize-on-context-refresh is true + // this method will + // be called during the bootstrap phase and the main application startup. We + // only manipulate the environment profiles in the bootstrap phase as we are + // fetching + // any additional profile specific configuration when this method would be + // called during the + // main application startup, and it is not valid to activate profiles in + // profile specific + // configuration properties, so we should not run this method then. + return; + } Set includeProfiles = new TreeSet<>(); + List activeProfiles = new ArrayList<>(); + for (PropertySource propertySource : environment.getPropertySources()) { - addIncludedProfilesTo(includeProfiles, propertySource); + addIncludedProfilesTo(includeProfiles, propertySource, environment); + addActiveProfilesTo(activeProfiles, propertySource, environment); } - List activeProfiles = new ArrayList<>(); - Collections.addAll(activeProfiles, environment.getActiveProfiles()); // If it's already accepted we assume the order was set intentionally includeProfiles.removeAll(activeProfiles); - if (includeProfiles.isEmpty()) { - return; - } // Prepend each added profile (last wins in a property key clash) for (String profile : includeProfiles) { activeProfiles.add(0, profile); } + List activeProfilesFromEnvironment = Arrays.stream(environment.getActiveProfiles()) + .collect(Collectors.toList()); + if (!activeProfiles.containsAll(activeProfilesFromEnvironment)) { + activeProfiles.addAll(activeProfilesFromEnvironment); + + } environment.setActiveProfiles(activeProfiles.toArray(new String[activeProfiles.size()])); } - private Set addIncludedProfilesTo(Set profiles, PropertySource propertySource) { + private Set addIncludedProfilesTo(Set profiles, PropertySource propertySource, + ConfigurableEnvironment environment) { + return addProfilesTo(profiles, propertySource, Profiles.INCLUDE_PROFILES_PROPERTY_NAME, environment); + } + + private List addActiveProfilesTo(List profiles, PropertySource propertySource, + ConfigurableEnvironment environment) { + return addProfilesTo(profiles, propertySource, AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, environment); + } + + private > T addProfilesTo(T profiles, PropertySource propertySource, + String property, ConfigurableEnvironment environment) { if (propertySource instanceof CompositePropertySource) { for (PropertySource nestedPropertySource : ((CompositePropertySource) propertySource) .getPropertySources()) { - addIncludedProfilesTo(profiles, nestedPropertySource); + addProfilesTo(profiles, nestedPropertySource, property, environment); } } else { - Collections.addAll(profiles, getProfilesForValue( - propertySource.getProperty(ConfigFileApplicationListener.INCLUDE_PROFILES_PROPERTY))); + Collections.addAll(profiles, getProfilesForValue(propertySource.getProperty(property), environment)); } return profiles; } - private String[] getProfilesForValue(Object property) { + private String[] getProfilesForValue(Object property, ConfigurableEnvironment environment) { final String value = (property == null ? null : property.toString()); - return property == null ? new String[0] : StringUtils.tokenizeToStringArray(value, ","); + return property == null ? new String[0] : resolvePlaceholdersInProfiles(value, environment); + } + + private String[] resolvePlaceholdersInProfiles(String profiles, ConfigurableEnvironment environment) { + return Arrays.stream(StringUtils.tokenizeToStringArray(profiles, ",")).map(s -> { + if (s.startsWith("${") && s.endsWith("}")) { + return environment.resolvePlaceholders(s); + } + else { + return s; + } + }).toArray(String[]::new); + } + + /* + * The ConextRefreshedEvent gets called at the end of the boostrap phase after config + * data is loaded during bootstrap. This will run and do an "initial fetch" of + * configuration data during bootstrap but before the main applicaiton context starts. + */ + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (bootstrapProperties.isInitializeOnContextRefresh() + && event.getApplicationContext() instanceof ConfigurableApplicationContext) { + if (((ConfigurableApplicationContext) event.getApplicationContext()).getEnvironment().getPropertySources() + .contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) { + doInitialize((ConfigurableApplicationContext) event.getApplicationContext()); + } + } } } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java index b442a80e..01164988 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java @@ -46,6 +46,19 @@ public class PropertySourceBootstrapProperties { */ private boolean overrideNone = false; + /** + * Flag to initialize bootstrap configuration on context refresh event. Default false. + */ + private boolean initializeOnContextRefresh = false; + + public boolean isInitializeOnContextRefresh() { + return initializeOnContextRefresh; + } + + public void setInitializeOnContextRefresh(boolean initializeOnContextRefresh) { + this.initializeOnContextRefresh = initializeOnContextRefresh; + } + public boolean isOverrideNone() { return this.overrideNone; } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java index 7d9228ae..90209b2d 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java @@ -32,10 +32,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.cloud.bootstrap.BootstrapApplicationListener; import org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration; -import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -77,11 +78,20 @@ public class BootstrapConfigurationTests { @Test public void pickupOnlyExternalBootstrapProperties() { String externalPropertiesPath = getExternalProperties(); + pickupOnlyExternalBootstrapProperties("spring.cloud.bootstrap.location=" + externalPropertiesPath, + "spring.config.use-legacy-processing=true"); + } + + @Test + public void pickupOnlyExternalBootstrapPropertiesWithAppListener() { + String externalPropertiesPath = getExternalProperties(); + pickupOnlyExternalBootstrapProperties("spring.cloud.bootstrap.location=" + externalPropertiesPath, + "spring.config.use-legacy-processing=true", "spring.cloud.config.initialize-on-context-refresh=true"); + } + private void pickupOnlyExternalBootstrapProperties(String... properties) { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) - .properties("spring.cloud.bootstrap.location=" + externalPropertiesPath, - "spring.config.use-legacy-processing=true") - .run(); + .properties(properties).run(); then(this.context.getEnvironment().getProperty("info.name")).isEqualTo("externalPropertiesInfoName"); then(this.context.getEnvironment().getProperty("info.desc")).isNull(); then(this.context.getEnvironment().getPropertySources() @@ -92,11 +102,22 @@ public class BootstrapConfigurationTests { @Test public void pickupAdditionalExternalBootstrapProperties() { String externalPropertiesPath = getExternalProperties(); + pickupAdditionalExternalBootstrapProperties( + "spring.cloud.bootstrap.additional-location=" + externalPropertiesPath, + "spring.config.use-legacy-processing=true"); + } + + @Test + public void pickupAdditionalExternalBootstrapPropertiesWithAppListener() { + String externalPropertiesPath = getExternalProperties(); + pickupAdditionalExternalBootstrapProperties( + "spring.cloud.bootstrap.additional-location=" + externalPropertiesPath, + "spring.config.use-legacy-processing=true", "spring.cloud.config.initialize-on-context-refresh=true"); + } + private void pickupAdditionalExternalBootstrapProperties(String... properties) { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) - .properties("spring.cloud.bootstrap.additional-location=" + externalPropertiesPath, - "spring.config.use-legacy-processing=true") - .run(); + .properties(properties).run(); then(this.context.getEnvironment().getProperty("info.name")).isEqualTo("externalPropertiesInfoName"); then(this.context.getEnvironment().getProperty("info.desc")).isEqualTo("defaultPropertiesInfoDesc"); then(this.context.getEnvironment().getPropertySources() @@ -106,14 +127,20 @@ public class BootstrapConfigurationTests { @Test public void bootstrapPropertiesAvailableInInitializer() { - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class) - .initializers(new ApplicationContextInitializer() { - @Override - public void initialize(ConfigurableApplicationContext applicationContext) { - // This property is defined in bootstrap.properties - then(applicationContext.getEnvironment().getProperty("info.name")).isEqualTo("child"); - } + bootstrapPropertiesAvailableInInitializer("spring.config.use-legacy-processing=true"); + } + + @Test + public void bootstrapPropertiesAvailableInInitializerWithAppContext() { + bootstrapPropertiesAvailableInInitializer("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void bootstrapPropertiesAvailableInInitializer(String... properties) { + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).initializers(applicationContext -> { + // This property is defined in bootstrap.properties + then(applicationContext.getEnvironment().getProperty("info.name")).isEqualTo("child"); }).run(); then(this.context.getEnvironment().getPropertySources() .contains(PropertySourceBootstrapConfiguration.BOOTSTRAP_PROPERTY_SOURCE_NAME + "-testBootstrap")) @@ -132,9 +159,19 @@ public class BootstrapConfigurationTests { @Test public void picksUpAdditionalPropertySource() { + picksUpAdditionalPropertySource("spring.config.use-legacy-processing=true"); + } + + @Test + public void picksUpAdditionalPropertySourceWithAppContext() { + picksUpAdditionalPropertySource("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void picksUpAdditionalPropertySource(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); then(this.context.getEnvironment().getPropertySources() .contains(PropertySourceBootstrapConfiguration.BOOTSTRAP_PROPERTY_SOURCE_NAME + "-testBootstrap")) @@ -143,35 +180,77 @@ public class BootstrapConfigurationTests { @Test public void failsOnPropertySource() { + failsOnPropertySource("spring.config.use-legacy-processing=true"); + } + + @Test + public void failsOnPropertySourceWithAppContext() { + failsOnPropertySource("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void failsOnPropertySource(String... properties) { System.setProperty("expected.fail", "true"); Throwable throwable = Assertions.assertThrows(RuntimeException.class, () -> { - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).run(); }); then(throwable.getMessage().equals("Planned")); } @Test public void overrideSystemPropertySourceByDefault() { + overrideSystemPropertySourceByDefault("spring.config.use-legacy-processing=true"); + + } + + @Test + public void overrideSystemPropertySourceByDefaultWithAppContext() { + overrideSystemPropertySourceByDefault("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + + } + + private void overrideSystemPropertySourceByDefault(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); System.setProperty("bootstrap.foo", "system"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); } @Test public void systemPropertyOverrideFalse() { + systemPropertyOverrideFalse("spring.config.use-legacy-processing=true"); + } + + @Test + public void systemPropertyOverrideFalseWithAppContext() { + systemPropertyOverrideFalse("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void systemPropertyOverrideFalse(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); PropertySourceConfiguration.MAP.put("spring.cloud.config.overrideSystemProperties", "false"); System.setProperty("bootstrap.foo", "system"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("system"); } @Test public void systemPropertyOverrideWhenOverrideDisallowed() { + systemPropertyOverrideWhenOverrideDisallowed("spring.config.use-legacy-processing=true"); + } + + @Test + public void systemPropertyOverrideWhenOverrideDisallowedWithAppContext() { + systemPropertyOverrideWhenOverrideDisallowed("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void systemPropertyOverrideWhenOverrideDisallowed(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); PropertySourceConfiguration.MAP.put("spring.cloud.config.overrideSystemProperties", "false"); // If spring.cloud.config.allowOverride=false is in the remote property sources @@ -179,42 +258,71 @@ public class BootstrapConfigurationTests { // their own remote property source. PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "false"); System.setProperty("bootstrap.foo", "system"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); } @Test public void systemPropertyOverrideFalseWhenOverrideAllowed() { + systemPropertyOverrideFalseWhenOverrideAllowed("spring.config.use-legacy-processing=true"); + } + + @Test + public void systemPropertyOverrideFalseWhenOverrideAllowedWithAppContext() { + systemPropertyOverrideFalseWhenOverrideAllowed("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void systemPropertyOverrideFalseWhenOverrideAllowed(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); PropertySourceConfiguration.MAP.put("spring.cloud.config.overrideSystemProperties", "false"); PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "true"); System.setProperty("bootstrap.foo", "system"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("system"); } @Test public void overrideAllWhenOverrideAllowed() { + overrideAllWhenOverrideAllowed("spring.config.use-legacy-processing=true"); + } + + @Test + public void overrideAllWhenOverrideAllowedWithAppContext() { + overrideAllWhenOverrideAllowed("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void overrideAllWhenOverrideAllowed(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); PropertySourceConfiguration.MAP.put("spring.cloud.config.overrideNone", "true"); PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "true"); ConfigurableEnvironment environment = new StandardEnvironment(); environment.getPropertySources().addLast( new MapPropertySource("last", Collections.singletonMap("bootstrap.foo", "splat"))); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").environment(environment) - .sources(BareConfiguration.class).run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .environment(environment).sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("splat"); } @Test public void applicationNameInBootstrapAndMain() { + applicationNameInBootstrapAndMain("spring.cloud.bootstrap.name:other", + "spring.config.use-legacy-processing=true", "spring.config.name:plain"); + } + + @Test + public void applicationNameInBootstrapAndMainWithAppContext() { + applicationNameInBootstrapAndMain("spring.cloud.bootstrap.name:other", + "spring.config.use-legacy-processing=true", "spring.config.name:plain", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void applicationNameInBootstrapAndMain(String... properties) { System.setProperty("expected.name", "main"); - this.context = new SpringApplicationBuilder() - .web(WebApplicationType.NONE).properties("spring.cloud.bootstrap.name:other", - "spring.config.use-legacy-processing=true", "spring.config.name:plain") + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("spring.application.name")).isEqualTo("app"); // The parent is called "main" because spring.application.name is specified in @@ -228,10 +336,20 @@ public class BootstrapConfigurationTests { @Test public void applicationNameNotInBootstrap() { + applicationNameNotInBootstrap("spring.cloud.bootstrap.name:application", + "spring.config.use-legacy-processing=true", "spring.config.name:other"); + } + + @Test + public void applicationNameNotInBootstrapWithAppContext() { + applicationNameNotInBootstrap("spring.cloud.bootstrap.name:application", + "spring.config.use-legacy-processing=true", "spring.config.name:other", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void applicationNameNotInBootstrap(String... properties) { System.setProperty("expected.name", "main"); - this.context = new SpringApplicationBuilder() - .web(WebApplicationType.NONE).properties("spring.cloud.bootstrap.name:application", - "spring.config.use-legacy-processing=true", "spring.config.name:other") + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("spring.application.name")).isEqualTo("main"); // The parent has no name because spring.application.name is not @@ -241,9 +359,18 @@ public class BootstrapConfigurationTests { @Test public void applicationNameOnlyInBootstrap() { + applicationNameOnlyInBootstrap("spring.cloud.bootstrap.name:other", "spring.config.use-legacy-processing=true"); + } + + @Test + public void applicationNameOnlyInBootstrapWithAppContext() { + applicationNameOnlyInBootstrap("spring.cloud.bootstrap.name:other", "spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void applicationNameOnlyInBootstrap(String... properties) { System.setProperty("expected.name", "main"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.cloud.bootstrap.name:other", "spring.config.use-legacy-processing=true") + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) .sources(BareConfiguration.class).run(); // The main context is called "main" because spring.application.name is specified // in other.properties (and not in the main config file) @@ -256,10 +383,20 @@ public class BootstrapConfigurationTests { @Test public void environmentEnrichedOnceWhenSharedWithChildContext() { + environmentEnrichedOnceWhenSharedWithChildContext("spring.config.use-legacy-processing=true"); + } + + @Test + public void environmentEnrichedOnceWhenSharedWithChildContextWithAppContext() { + environmentEnrichedOnceWhenSharedWithChildContext("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void environmentEnrichedOnceWhenSharedWithChildContext(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); - this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").environment(new StandardEnvironment()) - .child(BareConfiguration.class).web(WebApplicationType.NONE).run(); + this.context = new SpringApplicationBuilder().sources(BareConfiguration.class).properties(properties) + .environment(new StandardEnvironment()).child(BareConfiguration.class).web(WebApplicationType.NONE) + .run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); then(this.context.getParent().getEnvironment()).isEqualTo(this.context.getEnvironment()); MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); @@ -271,11 +408,20 @@ public class BootstrapConfigurationTests { @Test public void onlyOneBootstrapContext() { + onlyOneBootstrapContext("spring.config.use-legacy-processing=true"); + } + + @Test + public void onlyOneBootstrapContextWithAppContext() { + onlyOneBootstrapContext("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void onlyOneBootstrapContext(String... properties) { TestHigherPriorityBootstrapConfiguration.count.set(0); PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); - this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").child(BareConfiguration.class) - .web(WebApplicationType.NONE).run(); + this.context = new SpringApplicationBuilder().sources(BareConfiguration.class).properties(properties) + .child(BareConfiguration.class).web(WebApplicationType.NONE).run(); then(TestHigherPriorityBootstrapConfiguration.count.get()).isEqualTo(1); then(this.context.getParent()).isNotNull(); then(this.context.getParent().getParent().getId()).isEqualTo("bootstrap"); @@ -285,9 +431,18 @@ public class BootstrapConfigurationTests { @Test public void listOverride() { - this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").child(BareConfiguration.class) - .web(WebApplicationType.NONE).run(); + listOverride("spring.config.use-legacy-processing=true"); + } + + @Test + public void listOverrideWithAppContext() { + listOverride("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void listOverride(String... properties) { + this.context = new SpringApplicationBuilder().sources(BareConfiguration.class).properties(properties) + .child(BareConfiguration.class).web(WebApplicationType.NONE).run(); ListProperties listProperties = new ListProperties(); Binder.get(this.context.getEnvironment()).bind("list", Bindable.ofInstance(listProperties)); then(listProperties.getFoo().size()).isEqualTo(1); @@ -296,10 +451,20 @@ public class BootstrapConfigurationTests { @Test public void bootstrapContextSharedBySiblings() { + bootstrapContextSharedBySiblings("spring.config.use-legacy-processing=true"); + } + + @Test + public void bootstrapContextSharedBySiblingsWithAppContext() { + bootstrapContextSharedBySiblings("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void bootstrapContextSharedBySiblings(String... properties) { TestHigherPriorityBootstrapConfiguration.count.set(0); PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); - SpringApplicationBuilder builder = new SpringApplicationBuilder() - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class); + SpringApplicationBuilder builder = new SpringApplicationBuilder().properties(properties) + .sources(BareConfiguration.class); this.sibling = builder.child(BareConfiguration.class).properties("spring.application.name=sibling") .web(WebApplicationType.NONE).run(); this.context = builder.child(BareConfiguration.class).properties("spring.application.name=context") @@ -319,10 +484,19 @@ public class BootstrapConfigurationTests { @Test public void environmentEnrichedInParentContext() { + environmentEnrichedInParentContext("spring.config.use-legacy-processing=true"); + } + + @Test + public void environmentEnrichedInParentContextWithAppContext() { + environmentEnrichedInParentContext("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void environmentEnrichedInParentContext(String... properties) { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); - this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").child(BareConfiguration.class) - .web(WebApplicationType.NONE).run(); + this.context = new SpringApplicationBuilder().sources(BareConfiguration.class).properties(properties) + .child(BareConfiguration.class).web(WebApplicationType.NONE).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); then(this.context.getParent().getEnvironment()).isNotSameAs(this.context.getEnvironment()); then(this.context.getEnvironment().getPropertySources() @@ -365,27 +539,118 @@ public class BootstrapConfigurationTests { @Test public void includeProfileFromBootstrapPropertySource() { + includeProfileFromBootstrapPropertySource("spring.config.use-legacy-processing=true"); + } + + @Test + public void includeProfileFromBootstrapPropertySourceWithAppContext() { + includeProfileFromBootstrapPropertySource("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void includeProfileFromBootstrapPropertySource(String... properties) { PropertySourceConfiguration.MAP.put("spring.profiles.include", "bar,baz"); - this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").profiles("foo").sources(BareConfiguration.class) - .run(); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .profiles("foo").sources(BareConfiguration.class).run(); then(this.context.getEnvironment().acceptsProfiles("baz")).isTrue(); then(this.context.getEnvironment().acceptsProfiles("bar")).isTrue(); } + @Test + public void activeProfileFromBootstrapPropertySource() { + activeProfileFromBootstrapPropertySource("spring.config.use-legacy-processing=true"); + } + + @Test + public void activeProfileFromBootstrapPropertySourceWithAppContext() { + activeProfileFromBootstrapPropertySource("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + then(this.context.getEnvironment().getActiveProfiles()).doesNotContain("after"); + } + + private void activeProfileFromBootstrapPropertySource(String... properties) { + PropertySourceConfiguration.MAP.put("spring.profiles.active", "bar,baz"); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .profiles("foo").sources(BareConfiguration.class).run(); + then(this.context.getEnvironment().acceptsProfiles("baz", "bar", "foo")).isTrue(); + then(this.context.getEnvironment().getActiveProfiles()).contains("baz", "bar", "foo"); + } + + @Test + public void activeAndIncludeProfileFromBootstrapPropertySource() { + activeAndIncludeProfileFromBootstrapPropertySource("spring.config.use-legacy-processing=true"); + } + + @Test + public void activeAndIncludeProfileFromBootstrapPropertySourceWithAppContext() { + activeAndIncludeProfileFromBootstrapPropertySource("spring.config.use-legacy-processing=true", + "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void activeAndIncludeProfileFromBootstrapPropertySource(String... properties) { + PropertySourceConfiguration.MAP.put("spring.profiles.active", "bar,baz"); + PropertySourceConfiguration.MAP.put("spring.profiles.include", "bar,baz,hello"); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .profiles("foo").sources(BareConfiguration.class).run(); + then(this.context.getEnvironment().acceptsProfiles("baz", "bar", "hello", "foo")).isTrue(); + then(this.context.getEnvironment().getActiveProfiles()).contains("baz", "bar", "foo", "hello"); + } + + @Test + public void activeAndIncludeProfileFromBootstrapPropertySourceWithReplacement() { + activeAndIncludeProfileFromBootstrapPropertySourceWithReplacement("spring.config.use-legacy-processing=true", + "barreplacement=bar"); + } + + @Test + public void activeAndIncludeProfileFromBootstrapPropertySourceWithReplacementWithAppContext() { + activeAndIncludeProfileFromBootstrapPropertySourceWithReplacement("spring.config.use-legacy-processing=true", + "barreplacement=bar", "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void activeAndIncludeProfileFromBootstrapPropertySourceWithReplacement(String... properties) { + PropertySourceConfiguration.MAP.put("spring.profiles.active", "${barreplacement},baz"); + PropertySourceConfiguration.MAP.put("spring.profiles.include", "${barreplacement},baz,hello"); + this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties) + .profiles("foo").sources(BareConfiguration.class).run(); + then(this.context.getEnvironment().acceptsProfiles("baz", "bar", "hello", "foo")).isTrue(); + then(this.context.getEnvironment().getActiveProfiles()).contains("baz", "bar", "foo", "hello"); + } + @Test public void includeProfileFromBootstrapProperties() { + includeProfileFromBootstrapProperties("spring.config.use-legacy-processing=true", + "spring.cloud.bootstrap.name=local"); + } + + @Test + public void includeProfileFromBootstrapPropertiesWithAppContext() { + includeProfileFromBootstrapProperties("spring.config.use-legacy-processing=true", + "spring.cloud.bootstrap.name=local", "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void includeProfileFromBootstrapProperties(String... properties) { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name=local").run(); + .properties(properties).run(); then(this.context.getEnvironment().acceptsProfiles("local")).isTrue(); then(this.context.getEnvironment().getProperty("added")).isEqualTo("Hello added!"); } @Test public void nonEnumerablePropertySourceWorks() { + nonEnumerablePropertySourceWorks("spring.config.use-legacy-processing=true", + "spring.cloud.bootstrap.name=nonenumerable"); + } + + @Test + public void nonEnumerablePropertySourceWorksWithAppContext() { + nonEnumerablePropertySourceWorks("spring.config.use-legacy-processing=true", + "spring.cloud.bootstrap.name=nonenumerable", "spring.cloud.config.initialize-on-context-refresh=true"); + } + + private void nonEnumerablePropertySourceWorks(String... properties) { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name=nonenumerable") - .run(); + .properties(properties).run(); then(this.context.getEnvironment().getProperty("foo")).isEqualTo("bar"); } @@ -425,12 +690,24 @@ public class BootstrapConfigurationTests { @Override public PropertySource locate(Environment environment) { + if (environment instanceof ConfigurableEnvironment) { + if (!((ConfigurableEnvironment) environment).getPropertySources() + .contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) { + if (MAP.containsKey(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME)) { + // This additional profile, after, should not be added when + // initialize-on-context-refresh=true + MAP.put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, + MAP.get(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME) + ",after"); + } + } + } if (this.name != null) { then(this.name).isEqualTo(environment.getProperty("spring.application.name")); } if (this.fail) { throw new RuntimeException("Planned"); } + return new MapPropertySource("testBootstrap", MAP); }