From a89b2ae46ea6cb40846cd547085f636e31232272 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 15 Jun 2018 16:29:40 +0200 Subject: [PATCH] Support profile expression in yml profile matching Closes gh-12469 --- .../main/asciidoc/spring-boot-features.adoc | 18 +++++++-- .../config/ConfigFileApplicationListener.java | 8 ++-- .../org/springframework/boot/ReproTests.java | 7 +++- .../boot/SpringApplicationTests.java | 3 +- .../SpringApplicationBuilderTests.java | 39 ++++++++++--------- .../ConfigFileApplicationListenerTests.java | 34 +++++++++++++--- .../test/resources/testprofileexpression.yml | 14 +++++++ 7 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/resources/testprofileexpression.yml diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 5f27d29a55..e6be4dd596 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -756,15 +756,25 @@ example: address: 127.0.0.1 --- spring: - profiles: production + profiles: production & eu-central server: address: 192.168.1.120 ---- In the preceding example, if the `development` profile is active, the `server.address` -property is `127.0.0.1`. Similarly, if the `production` profile is active, the -`server.address` property is `192.168.1.120`. If the `development` and `production` -profiles are *not* enabled, then the value for the property is `192.168.1.100`. +property is `127.0.0.1`. Similarly, if the `production` *and* `eu-central` profiles are +active, the `server.address` property is `192.168.1.120`. If the `development` and +`production` and `eu-central` profiles are *not* enabled, then the value for the property +is `192.168.1.100`. + +[NOTE] +==== +`spring.profiles` can therefore contains a simple profile name (for example `production`) +or a profile expression. A profile expression allows for more complicated profile logic +to be expressed, for example `production & (eu-central | eu-west)`. Check the +{spring-reference}core.html#beans-definition-profiles-java[reference guide] for more +details. +==== If none are explicitly active when the application context starts, the default profiles are activated. So, in the following YAML, we set a value for `spring.security.user.password` diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 62a61f77d4..b4e95385ce 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -57,6 +57,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.Profiles; import org.springframework.core.env.PropertySource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; @@ -411,14 +412,15 @@ public class ConfigFileApplicationListener } return ObjectUtils.containsElement(document.getProfiles(), profile.getName()) - && this.environment.acceptsProfiles(document.getProfiles()); + && this.environment + .acceptsProfiles(Profiles.of(document.getProfiles())); }; } private DocumentFilter getNegativeProfileFilter(Profile profile) { return (Document document) -> (profile == null - && !ObjectUtils.isEmpty(document.getProfiles()) - && this.environment.acceptsProfiles(document.getProfiles())); + && !ObjectUtils.isEmpty(document.getProfiles()) && this.environment + .acceptsProfiles(Profiles.of(document.getProfiles()))); } private DocumentConsumer addToLoaded( diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ReproTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ReproTests.java index 8493091f12..e077b1a5ef 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ReproTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ReproTests.java @@ -21,6 +21,7 @@ import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Profiles; import static org.assertj.core.api.Assertions.assertThat; @@ -50,8 +51,10 @@ public class ReproTests { this.context = application.run( "--spring.config.name=enableprofileviaapplicationproperties", "--spring.profiles.active=dev"); - assertThat(this.context.getEnvironment().acceptsProfiles("dev")).isTrue(); - assertThat(this.context.getEnvironment().acceptsProfiles("a")).isTrue(); + assertThat(this.context.getEnvironment().acceptsProfiles(Profiles.of("dev"))) + .isTrue(); + assertThat(this.context.getEnvironment().acceptsProfiles(Profiles.of("a"))) + .isTrue(); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 7740fb0c33..e31a5b6a67 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -83,6 +83,7 @@ import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.Profiles; import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ClassPathResource; @@ -525,7 +526,7 @@ public class SpringApplicationTests { ConfigurableEnvironment environment = new StandardEnvironment(); application.setEnvironment(environment); this.context = application.run(); - assertThat(environment.acceptsProfiles("foo")).isTrue(); + assertThat(environment.acceptsProfiles(Profiles.of("foo"))).isTrue(); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java index ea6eff78e4..2ee328983e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java @@ -31,6 +31,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Configuration; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Profiles; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; @@ -72,7 +73,8 @@ public class SpringApplicationBuilderTests { this.context = application.run(); assertThat(this.context).isInstanceOf(StaticApplicationContext.class); assertThat(this.context.getEnvironment().getProperty("foo")).isEqualTo("bucket"); - assertThat(this.context.getEnvironment().acceptsProfiles("foo")).isTrue(); + assertThat(this.context.getEnvironment().acceptsProfiles(Profiles.of("foo"))) + .isTrue(); } @Test @@ -205,11 +207,12 @@ public class SpringApplicationBuilderTests { ExampleConfig.class).profiles("node").properties("transport=redis") .child(ChildConfig.class).web(WebApplicationType.NONE); this.context = application.run(); - assertThat(this.context.getEnvironment().acceptsProfiles("node")).isTrue(); + assertThat(this.context.getEnvironment().acceptsProfiles(Profiles.of("node"))) + .isTrue(); assertThat(this.context.getEnvironment().getProperty("transport")) .isEqualTo("redis"); - assertThat(this.context.getParent().getEnvironment().acceptsProfiles("node")) - .isTrue(); + assertThat(this.context.getParent().getEnvironment() + .acceptsProfiles(Profiles.of("node"))).isTrue(); assertThat(this.context.getParent().getEnvironment().getProperty("transport")) .isEqualTo("redis"); // only defined in node profile @@ -223,10 +226,10 @@ public class SpringApplicationBuilderTests { .child(ChildConfig.class).profiles("admin") .web(WebApplicationType.NONE); this.context = application.run(); - assertThat(this.context.getEnvironment().acceptsProfiles("node", "admin")) - .isTrue(); - assertThat(this.context.getParent().getEnvironment().acceptsProfiles("admin")) - .isFalse(); + assertThat(this.context.getEnvironment() + .acceptsProfiles(Profiles.of("node", "admin"))).isTrue(); + assertThat(this.context.getParent().getEnvironment() + .acceptsProfiles(Profiles.of("admin"))).isFalse(); } @Test @@ -237,12 +240,12 @@ public class SpringApplicationBuilderTests { .profiles("admin").web(WebApplicationType.NONE); shared.profiles("parent"); this.context = application.run(); - assertThat(this.context.getEnvironment().acceptsProfiles("node", "admin")) - .isTrue(); - assertThat(this.context.getParent().getEnvironment().acceptsProfiles("node", - "parent")).isTrue(); - assertThat(this.context.getParent().getEnvironment().acceptsProfiles("admin")) - .isFalse(); + assertThat(this.context.getEnvironment() + .acceptsProfiles(Profiles.of("node", "admin"))).isTrue(); + assertThat(this.context.getParent().getEnvironment() + .acceptsProfiles(Profiles.of("node", "parent"))).isTrue(); + assertThat(this.context.getParent().getEnvironment() + .acceptsProfiles(Profiles.of("admin"))).isFalse(); } @Test @@ -253,12 +256,12 @@ public class SpringApplicationBuilderTests { .child(ChildConfig.class).profiles("admin") .web(WebApplicationType.NONE); this.context = application.run(); - assertThat(this.context.getEnvironment().acceptsProfiles("node", "admin")) - .isTrue(); + assertThat(this.context.getEnvironment() + .acceptsProfiles(Profiles.of("node", "admin"))).isTrue(); // Now they share an Environment explicitly so there's no way to keep the profiles // separate - assertThat(this.context.getParent().getEnvironment().acceptsProfiles("admin")) - .isTrue(); + assertThat(this.context.getParent().getEnvironment() + .acceptsProfiles(Profiles.of("admin"))).isTrue(); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java index 1d82d42e11..48b80669a5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java @@ -54,6 +54,7 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.Profiles; import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ByteArrayResource; @@ -504,6 +505,29 @@ public class ConfigFileApplicationListenerTests { assertThat(property).isEqualTo("notempty"); } + @Test + public void yamlProfileExpressionsAnd() { + assertProfileExpression("devandother", "dev", "other"); + } + + @Test + public void yamlProfileExpressionsComplex() { + assertProfileExpression("devorotherandanother", "dev", "another"); + } + + @Test + public void yamlProfileExpressionsNoMatch() { + assertProfileExpression("fromyamlfile", "dev"); + } + + private void assertProfileExpression(String value, String... activeProfiles) { + this.environment.setActiveProfiles(activeProfiles); + this.initializer.setSearchNames("testprofileexpression"); + this.initializer.postProcessEnvironment(this.environment, this.application); + String property = this.environment.getProperty("my.property"); + assertThat(property).isEqualTo(value); + } + @Test public void yamlNegatedProfiles() { // gh-8011 @@ -827,7 +851,7 @@ public class ConfigFileApplicationListenerTests { assertThat(environment.containsProperty("customprofile")).isTrue(); assertThat(environment.containsProperty("customprofile-specific")).isTrue(); assertThat(environment.containsProperty("customprofile-customdefault")).isTrue(); - assertThat(environment.acceptsProfiles("customdefault")).isTrue(); + assertThat(environment.acceptsProfiles(Profiles.of("customdefault"))).isTrue(); } @Test @@ -899,7 +923,7 @@ public class ConfigFileApplicationListenerTests { application.setWebApplicationType(WebApplicationType.NONE); this.context = application.run("--spring.config.name=applicationloop"); ConfigurableEnvironment environment = this.context.getEnvironment(); - assertThat(environment.acceptsProfiles("loop")).isTrue(); + assertThat(environment.acceptsProfiles(Profiles.of("loop"))).isTrue(); } @Test @@ -909,8 +933,8 @@ public class ConfigFileApplicationListenerTests { application.setWebApplicationType(WebApplicationType.NONE); this.context = application.run("--spring.config.name=applicationmultiprofiles"); ConfigurableEnvironment environment = this.context.getEnvironment(); - assertThat(environment.acceptsProfiles("test")).isTrue(); - assertThat(environment.acceptsProfiles("another-test")).isTrue(); + assertThat(environment.acceptsProfiles(Profiles.of("test"))).isTrue(); + assertThat(environment.acceptsProfiles(Profiles.of("another-test"))).isTrue(); assertThat(environment.getProperty("message")).isEqualTo("multiprofile"); } @@ -932,7 +956,7 @@ public class ConfigFileApplicationListenerTests { @Override public boolean matches(ConfigurableEnvironment value) { - return value.acceptsProfiles(profile); + return value.acceptsProfiles(Profiles.of(profile)); } }; diff --git a/spring-boot-project/spring-boot/src/test/resources/testprofileexpression.yml b/spring-boot-project/spring-boot/src/test/resources/testprofileexpression.yml new file mode 100644 index 0000000000..78ddcfe64e --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/testprofileexpression.yml @@ -0,0 +1,14 @@ +--- +my: + property: fromyamlfile +--- +spring: + profiles: dev & other +my: + property: devandother +--- +spring: + profiles: (dev | other) & another +my: + property: devorotherandanother +--- \ No newline at end of file