diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index afe3fdfa9ac62..96d6d9328553f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -325,7 +325,7 @@ static final class GenerateOperation implements AutoCloseable { clinit = cc.getMethodCreator(MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "", void.class)); clinit.setModifiers(Opcodes.ACC_STATIC); - clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); + clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getProfileConfiguration())); clinitNameBuilder = clinit.newInstance(SB_NEW); clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load("quarkus")); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java index 0ccaf7bf98704..17e580d734f70 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java @@ -161,7 +161,7 @@ public static void run(Application application, Class + * Multiple profiles may be set in any of these sources, by separating each profile with a comma @{code ","}. + * + * @see #QUARKUS_PROFILE_ENV + * @see #QUARKUS_PROFILE_PROP + * @see #QUARKUS_TEST_PROFILE_PROP + * @see #BACKWARD_COMPATIBLE_QUARKUS_PROFILE_PROP + * @return the profile configuration. + */ + public static String getProfileConfiguration() { if (launchMode == LaunchMode.TEST) { String profile = System.getProperty(QUARKUS_TEST_PROFILE_PROP); if (profile != null) { @@ -69,4 +106,21 @@ public static String getActiveProfile() { return launchMode.getDefaultProfile(); } + /** + * Returns the active list of profiles. + * + * @return a list of names with all the active profiles. + */ + public static List getActiveProfiles() { + List profiles = PROFILE_CONVERTER.convert(getProfileConfiguration()); + Collections.reverse(profiles); + return profiles; + } + + public static boolean isProfileActive(final String profile) { + return getActiveProfiles().contains(profile); + } + + private static final Converter> PROFILE_CONVERTER = newCollectionConverter( + newEmptyValueConverter(value -> value), ArrayList::new); } diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java index 6b9be41d66154..da2f2f5e0bb91 100644 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java +++ b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java @@ -46,7 +46,7 @@ private SmallRyeConfigBuilder configBuilder(String... keyValues) { return new SmallRyeConfigBuilder() .addDefaultInterceptors() .withSources(new PropertiesConfigSource(maps(keyValues), "test input", 500)) - .withProfile(ProfileManager.getActiveProfile()); + .withProfile(ProfileManager.getProfileConfiguration()); } private SmallRyeConfig buildConfig(String... keyValues) { diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index 663af725017d9..033cef8229cf3 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -483,9 +483,17 @@ quarkus.http.port=9090 And then set the `QUARKUS_PROFILE` environment variable to `staging` to activate my profile. +==== Multiple Profiles + +Multiple Profiles may be active at the same time. The configuration `quarkus.profile` or `QUARKUS_PROFILE` accepts a +comma-separated list of profile names. + +When multiple profiles are active, the rules for profile configuration are exactly the same. If two profiles define the +same configuration, then the last listed profile has priority. + [NOTE] ==== -The proper way to check the active profile programmatically is to use the `getActiveProfile` method of `io.quarkus.runtime.configuration.ProfileManager`. +The proper way to check the active profile programmatically is to use the `isProfileActive` method of `io.quarkus.runtime.configuration.ProfileManager`. Using `@ConfigProperty("quarkus.profile")` will *not* work properly. ==== @@ -816,7 +824,7 @@ quarkus: In general, `null` YAML keys are not included in assembly of the configuration property name, allowing them to be used to any level for disambiguating configuration keys. -== More info on how to configure +== Additional Configuration Features Quarkus relies on SmallRye Config and inherits its features. @@ -829,6 +837,8 @@ SmallRye Config provides: * Fallback Configuration Properties * Logging * Hide Secrets +* Config Mappings +* Multiple Profiles For more information, please check the link:https://smallrye.io/docs/smallrye-config/index.html[SmallRye Config documentation]. diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BuildTimeEnabledProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BuildTimeEnabledProcessor.java index 596f1437b7a9c..979051b009687 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BuildTimeEnabledProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BuildTimeEnabledProcessor.java @@ -43,7 +43,7 @@ void ifBuildProfile(CombinedIndexBuildItem index, BuildProducer annotationInstances = index.getIndex().getAnnotations(IF_BUILD_PROFILE); for (AnnotationInstance instance : annotationInstances) { String profileOnInstance = instance.value().asString(); - boolean enabled = profileOnInstance.equals(ProfileManager.getActiveProfile()); + boolean enabled = ProfileManager.isProfileActive(profileOnInstance); if (enabled) { LOGGER.debug("Enabling " + instance + " since the profile value matches the active profile."); } else { @@ -58,7 +58,7 @@ void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer annotationInstances = index.getIndex().getAnnotations(UNLESS_BUILD_PROFILE); for (AnnotationInstance instance : annotationInstances) { String profileOnInstance = instance.value().asString(); - boolean enabled = !profileOnInstance.equals(ProfileManager.getActiveProfile()); + boolean enabled = !ProfileManager.isProfileActive(profileOnInstance); if (enabled) { LOGGER.debug("Enabling " + instance + " since the profile value does not match the active profile."); } else { diff --git a/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java index faec3b6ddcc51..968e8a9d4b83d 100644 --- a/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java +++ b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java @@ -82,7 +82,7 @@ public Map createUserTransaction() { @Transactional @Path("/import") public Map get() { - boolean isProdMode = ProfileManager.getActiveProfile().equals(LaunchMode.NORMAL.getDefaultProfile()); + boolean isProdMode = ProfileManager.isProfileActive(LaunchMode.NORMAL.getDefaultProfile()); Gift gift = em.find(Gift.class, 100000L); Map map = new HashMap<>(); // Native tests are run under the 'prod' profile for now. In NORMAL mode, Quarkus doesn't execute the SQL import file diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/ProfileManagerTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ProfileManagerTestCase.java index 8bcf86c206b21..71d53c35baae6 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/ProfileManagerTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ProfileManagerTestCase.java @@ -35,14 +35,14 @@ private void resetProfileManagerState() { @Test public void testDefaultTestProfile() { - Assertions.assertEquals(LaunchMode.TEST.getDefaultProfile(), ProfileManager.getActiveProfile()); + Assertions.assertTrue(ProfileManager.isProfileActive(LaunchMode.TEST.getDefaultProfile())); } @Test public void testCustomTestProfile() { String customProfile = "foo"; System.setProperty(ProfileManager.QUARKUS_TEST_PROFILE_PROP, customProfile); - Assertions.assertEquals(customProfile, ProfileManager.getActiveProfile()); + Assertions.assertTrue(ProfileManager.isProfileActive(customProfile)); } @Test @@ -60,7 +60,7 @@ private void testCustomProfile(LaunchMode launchMode) { ProfileManager.setRuntimeDefaultProfile("foo"); String customProfile = "bar"; System.setProperty(ProfileManager.QUARKUS_PROFILE_PROP, customProfile); - Assertions.assertEquals(customProfile, ProfileManager.getActiveProfile()); + Assertions.assertTrue(ProfileManager.isProfileActive(customProfile)); } @Test @@ -78,7 +78,7 @@ private void testBackwardCompatibleCustomProfile(LaunchMode launchMode) { ProfileManager.setRuntimeDefaultProfile("foo"); String customProfile = "bar"; System.setProperty(BACKWARD_COMPATIBLE_QUARKUS_PROFILE_PROP, customProfile); - Assertions.assertEquals(customProfile, ProfileManager.getActiveProfile()); + Assertions.assertTrue(ProfileManager.isProfileActive(customProfile)); } @Test @@ -95,7 +95,7 @@ private void testCustomRuntimeProfile(LaunchMode launchMode) { ProfileManager.setLaunchMode(launchMode); String customProfile = "foo"; ProfileManager.setRuntimeDefaultProfile(customProfile); - Assertions.assertEquals(customProfile, ProfileManager.getActiveProfile()); + Assertions.assertTrue(ProfileManager.isProfileActive(customProfile)); } @Test @@ -110,6 +110,6 @@ public void testDefaultDevProfile() { private void testDefaultProfile(LaunchMode launchMode) { ProfileManager.setLaunchMode(launchMode); - Assertions.assertEquals(launchMode.getDefaultProfile(), ProfileManager.getActiveProfile()); + Assertions.assertTrue(ProfileManager.isProfileActive(launchMode.getDefaultProfile())); } } diff --git a/integration-tests/smallrye-config/pom.xml b/integration-tests/smallrye-config/pom.xml index c056d31ba2b41..1b4c0e380e8c3 100644 --- a/integration-tests/smallrye-config/pom.xml +++ b/integration-tests/smallrye-config/pom.xml @@ -36,7 +36,6 @@ test - io.quarkus @@ -83,6 +82,15 @@ maven-compiler-plugin ${compiler-plugin.version} + + org.apache.maven.plugins + maven-surefire-plugin + + + common,test + + + @@ -96,6 +104,24 @@ + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + + set-system-properties + + + + common,prod + + + + + org.apache.maven.plugins maven-surefire-plugin @@ -112,6 +138,9 @@ verify + + common,prod + ${project.build.directory}/${project.build.finalName}-runner diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigResource.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigResource.java new file mode 100644 index 0000000000000..31ff23547b94b --- /dev/null +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigResource.java @@ -0,0 +1,48 @@ +package io.quarkus.it.smallrye.config; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.config.Config; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SmallRyeConfig; + +@Path("/config") +@Produces(MediaType.APPLICATION_JSON) +public class ConfigResource { + @Inject + Config config; + + @GET + @Path("/{name}") + public Response configValue(@PathParam("name") final String name) { + final io.smallrye.config.ConfigValue configValue = ((SmallRyeConfig) config).getConfigValue(name); + return Response.ok(new ConfigValue(configValue.getName(), configValue.getValue())).build(); + } + + @RegisterForReflection + public static class ConfigValue { + final String name; + final String value; + + public ConfigValue(final String name, final String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + } +} diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/MultipleProfilesResource.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/MultipleProfilesResource.java new file mode 100644 index 0000000000000..9f1533844f012 --- /dev/null +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/MultipleProfilesResource.java @@ -0,0 +1,70 @@ +package io.quarkus.it.smallrye.config; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.quarkus.arc.profile.IfBuildProfile; +import io.quarkus.arc.profile.UnlessBuildProfile; + +@Path("/profiles") +@Consumes(MediaType.TEXT_PLAIN) +@Produces(MediaType.TEXT_PLAIN) +public class MultipleProfilesResource { + @Inject + Instance activeProfiles; + + @GET + public Response getActiveProfiles() { + return Response.ok(activeProfiles.stream() + .map(ActiveProfile::value) + .reduce((integer, integer2) -> integer | integer2) + .orElse(0)).build(); + } + + public interface ActiveProfile { + int value(); + } + + @ApplicationScoped + @IfBuildProfile("common") + public static class CommonProfile implements ActiveProfile { + @Override + public int value() { + return 1; + } + } + + @ApplicationScoped + @IfBuildProfile("test") + public static class TestProfile implements ActiveProfile { + @Override + public int value() { + return 2; + } + } + + @ApplicationScoped + @IfBuildProfile("prod") + public static class ProdProfile implements ActiveProfile { + @Override + public int value() { + return 4; + } + } + + @ApplicationScoped + @UnlessBuildProfile("main") + public static class MainProfile implements ActiveProfile { + @Override + public int value() { + return 8; + } + } +} diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java index 13c05e224f415..6e07efcc331cf 100644 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java @@ -3,9 +3,12 @@ import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("/server") +@Produces(MediaType.APPLICATION_JSON) public class ServerResource { @Inject Server server; diff --git a/integration-tests/smallrye-config/src/main/resources/application.properties b/integration-tests/smallrye-config/src/main/resources/application.properties index c9fcd2857cc9d..8d744f73842f2 100644 --- a/integration-tests/smallrye-config/src/main/resources/application.properties +++ b/integration-tests/smallrye-config/src/main/resources/application.properties @@ -1,3 +1,17 @@ +quarkus.log.level=INFO +quarkus.log.category."io.smallrye".level=DEBUG + +# Multiple Profiles +%common.profile.property.shared=common +%test.profile.property.shared=main +%prod.profile.property.shared=main + +%common.profile.property.common=common +%test.profile.property.main=main +%prod.profile.property.main=main +no.profile=any + +# Mapping server.host=localhost server.port=8080 server.timeout=60s diff --git a/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/MultipleProfilesIT.java b/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/MultipleProfilesIT.java new file mode 100644 index 0000000000000..00100c1440501 --- /dev/null +++ b/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/MultipleProfilesIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.smallrye.config; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class MultipleProfilesIT extends MultipleProflesTest { +} diff --git a/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/MultipleProflesTest.java b/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/MultipleProflesTest.java new file mode 100644 index 0000000000000..092fcf6327d34 --- /dev/null +++ b/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/MultipleProflesTest.java @@ -0,0 +1,65 @@ +package io.quarkus.it.smallrye.config; + +import static io.restassured.RestAssured.given; +import static javax.ws.rs.core.Response.Status.OK; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.runtime.configuration.ProfileManager; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +public class MultipleProflesTest { + @Test + void properties() { + given() + .get("/config/{name}", "profile.property.shared") + .then() + .statusCode(OK.getStatusCode()) + .body("value", equalTo("main")); + + given() + .get("/config/{name}", "profile.property.common") + .then() + .statusCode(OK.getStatusCode()) + .body("value", equalTo("common")); + + given() + .get("/config/{name}", "profile.property.main") + .then() + .statusCode(OK.getStatusCode()) + .body("value", equalTo("main")); + + given() + .get("/config/{name}", "no.profile") + .then() + .statusCode(OK.getStatusCode()) + .body("value", equalTo("any")); + } + + @Test + void activeProfiles() { + given().contentType(ContentType.TEXT).get("/profiles") + .then() + .statusCode(OK.getStatusCode()) + .body(is((ProfileManager.getActiveProfiles() + .stream() + .map(profile -> { + switch (profile) { + case "common": + return 1; + case "test": + return 2; + case "prod": + return 4; + default: + return 0; + } + }) + .reduce((integer, integer2) -> integer | integer2) + .orElse(0) | 8) + "")); + } +}