diff --git a/docs/modules/ROOT/pages/server/environment-repository/file-system-backend.adoc b/docs/modules/ROOT/pages/server/environment-repository/file-system-backend.adoc index 56dbe930c..61b8a7c04 100644 --- a/docs/modules/ROOT/pages/server/environment-repository/file-system-backend.adoc +++ b/docs/modules/ROOT/pages/server/environment-repository/file-system-backend.adoc @@ -23,3 +23,8 @@ Thus, the default behaviour with no placeholders is the same as adding a search For example, `file:/tmp/config` is the same as `file:/tmp/config,file:/tmp/config/\{label}`. This behavior can be disabled by setting `spring.cloud.config.server.native.addLabelLocations=false`. +If the request contains several labels (a comma-separated list), the last label takes precedence over the earlier ones, as it does for the Git and SVN backends. +Each label is resolved on its own, and the locations the labels share come last. +Before this behavior was introduced, the first label took precedence. +To restore that behavior, set `spring.cloud.config.server.native.lastLabelWins=false`. + diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentProperties.java index 7926d9ab8..d5b37077e 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentProperties.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentProperties.java @@ -35,6 +35,13 @@ public class NativeEnvironmentProperties implements EnvironmentRepositoryPropert */ private Boolean addLabelLocations = true; + /** + * Flag to determine whether, when several labels are requested, the last label takes + * precedence over the earlier ones (default true). If false, the first label takes + * precedence, as it did before this property was introduced. + */ + private Boolean lastLabelWins = true; + private String defaultLabel = "master"; /** @@ -66,6 +73,14 @@ public void setAddLabelLocations(Boolean addLabelLocations) { this.addLabelLocations = addLabelLocations; } + public Boolean getLastLabelWins() { + return this.lastLabelWins; + } + + public void setLastLabelWins(Boolean lastLabelWins) { + this.lastLabelWins = lastLabelWins; + } + public String getDefaultLabel() { return this.defaultLabel; } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java index 50cd36e64..2e50dfa27 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java @@ -17,10 +17,12 @@ package org.springframework.cloud.config.server.environment; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -85,6 +87,8 @@ public class NativeEnvironmentRepository implements EnvironmentRepository, Searc */ private boolean addLabelLocations; + private boolean lastLabelWins = true; + /** * Version string to be reported for native repository. */ @@ -100,6 +104,7 @@ public NativeEnvironmentRepository(ConfigurableEnvironment environment, NativeEn ObservationRegistry observationRegistry) { this.environment = environment; this.addLabelLocations = properties.getAddLabelLocations(); + this.lastLabelWins = properties.getLastLabelWins(); this.defaultLabel = properties.getDefaultLabel(); this.failOnError = properties.getFailOnError(); this.order = properties.getOrder(); @@ -124,6 +129,14 @@ public void setAddLabelLocations(boolean addLabelLocations) { this.addLabelLocations = addLabelLocations; } + public boolean isLastLabelWins() { + return this.lastLabelWins; + } + + public void setLastLabelWins(boolean lastLabelWins) { + this.lastLabelWins = lastLabelWins; + } + public String getDefaultLabel() { return this.defaultLabel; } @@ -139,7 +152,36 @@ public Environment findOne(String config, String profile, String label) { @Override public Environment findOne(String config, String profile, String label, boolean includeOrigin) { + if (!this.lastLabelWins || label == null || !label.contains(",")) { + return findOneForLabel(config, profile, label, includeOrigin); + } + // Resolve one label at a time, so that the last label takes precedence over the + // earlier ones as in the SCM repositories. Resolving them together would let + // Spring Boot interleave the labels by profile, giving the first label + // precedence. + List labels = new ArrayList<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray(label))); + Collections.reverse(labels); + Environment result = null; + Map sources = new LinkedHashMap<>(); + for (String singleLabel : labels) { + Environment environment = findOneForLabel(config, profile, singleLabel, includeOrigin); + if (result == null) { + result = new Environment(environment.getName(), environment.getProfiles(), label, + environment.getVersion(), environment.getState()); + } + for (PropertySource source : environment.getPropertySources()) { + // A property source found for several labels comes from a location the + // labels share, which has a lower precedence than the label locations, + // so keep it at its last position + sources.remove(source.getName()); + sources.put(source.getName(), source); + } + } + sources.values().forEach(result::add); + return result; + } + private Environment findOneForLabel(String config, String profile, String label, boolean includeOrigin) { try { ConfigurableEnvironment environment = getEnvironment(config, profile, label); DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryTests.java index d592abad9..3cd1d743c 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryTests.java @@ -261,6 +261,19 @@ public void locationAddLabelLocations() { @Test public void locationAddMultipleLabelLocations() { + this.repository.setSearchLocations("classpath:/test/dev/"); + Environment environment = this.repository.findOne("foo", "development", "ignore,applicationxyz"); + // the last label wins, the location shared by the labels comes last + assertThat(environment.getPropertySources()).hasSize(3); + assertThat(environment.getPropertySources().get(0).getSource().get("foo")).isEqualTo("default-app"); + assertThat(environment.getPropertySources().get(1).getSource().get("foo")).isEqualTo("app"); + assertThat(environment.getPropertySources().get(2).getSource().get("foo")).isEqualTo("dev_bar"); + assertThat(environment.getLabel()).isEqualTo("ignore,applicationxyz"); + } + + @Test + public void locationAddMultipleLabelLocationsFirstLabelWins() { + this.repository.setLastLabelWins(false); this.repository.setSearchLocations("classpath:/test/dev/"); Environment environment = this.repository.findOne("foo", "development", "ignore,applicationxyz"); assertThat(environment.getPropertySources()).hasSize(3); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/compliance/NativeEnvironmentRepositoryComplianceTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/compliance/NativeEnvironmentRepositoryComplianceTests.java index 00a5e0071..2676f8620 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/compliance/NativeEnvironmentRepositoryComplianceTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/compliance/NativeEnvironmentRepositoryComplianceTests.java @@ -19,8 +19,6 @@ import java.nio.file.Path; import io.micrometer.observation.ObservationRegistry; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.cloud.config.server.environment.EnvironmentRepository; @@ -57,11 +55,4 @@ public EnvironmentRepository repository() { return this.fixture; } - @Override - @Test - @Disabled("TODO-issue: Label directories are added as search locations, so labels are interleaved by profile and the first label wins") - public void lastLabelTakesPrecedence() throws Exception { - super.lastLabelTakesPrecedence(); - } - }