From 8478a5263f8fb56027dc828d6a0636277709401a Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 9 Mar 2023 10:59:41 -0500 Subject: [PATCH] Add property to reverse location order. Fixes #1910 (#2239) Co-authored-by: Ryan Baxter <524254+ryanjbaxter@users.noreply.github.com> --- .../server/config/ConfigServerProperties.java | 19 ++++++- .../ResourceRepositoryConfiguration.java | 4 +- .../resource/GenericResourceRepository.java | 14 +++++ .../server/CompositeIntegrationTests.java | 53 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerProperties.java index 8e8b1fdc..118ddf58 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerProperties.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerProperties.java @@ -95,6 +95,14 @@ public class ConfigServerProperties { */ private boolean failOnCompositeError = true; + /** + * By default the location order we use in GenericResourceRepository is the order in + * which they are listed. Prior to Hoxton.SR11 the order used to be reverse. If this + * property is set to true then we will reverse ther order like it used to be prior to + * Hoxton.SR11. + */ + private boolean reverseLocationOrder = false; + /** * Decryption configuration for when server handles encrypted properties before * sending them to clients. @@ -185,13 +193,22 @@ public class ConfigServerProperties { this.failOnCompositeError = failOnCompositeError; } + public boolean isReverseLocationOrder() { + return reverseLocationOrder; + } + + public void setReverseLocationOrder(boolean reverseLocationOrder) { + this.reverseLocationOrder = reverseLocationOrder; + } + @Override public String toString() { return new ToStringCreator(this).append("enabled", enabled).append("bootstrap", bootstrap) .append("prefix", prefix).append("defaultLabel", defaultLabel).append("overrides", overrides) .append("stripDocumentFromYaml", stripDocumentFromYaml).append("acceptEmpty", acceptEmpty) .append("defaultApplicationName", defaultApplicationName).append("defaultProfile", defaultProfile) - .append("failOnCompositeError", failOnCompositeError).append("encrypt", encrypt).toString(); + .append("failOnCompositeError", failOnCompositeError).append("encrypt", encrypt) + .append("reverseLocationOrder", reverseLocationOrder).toString(); } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java index 75c04227..d8fbd185 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java @@ -34,8 +34,8 @@ public class ResourceRepositoryConfiguration { @Bean @ConditionalOnBean(SearchPathLocator.class) - public ResourceRepository resourceRepository(SearchPathLocator service) { - return new GenericResourceRepository(service); + public ResourceRepository resourceRepository(SearchPathLocator service, ConfigServerProperties properties) { + return new GenericResourceRepository(service, properties); } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java index ef11507f..80797b88 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java @@ -18,15 +18,19 @@ package org.springframework.cloud.config.server.resource; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import org.springframework.cloud.config.server.config.ConfigServerProperties; import org.springframework.cloud.config.server.environment.SearchPathLocator; import org.springframework.cloud.config.server.support.PathUtils; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -40,10 +44,17 @@ public class GenericResourceRepository implements ResourceRepository, ResourceLo private SearchPathLocator service; + private ConfigServerProperties properties; + public GenericResourceRepository(SearchPathLocator service) { this.service = service; } + public GenericResourceRepository(SearchPathLocator service, ConfigServerProperties properties) { + this(service); + this.properties = properties; + } + @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; @@ -54,6 +65,9 @@ public class GenericResourceRepository implements ResourceRepository, ResourceLo if (StringUtils.hasText(path)) { String[] locations = this.service.getLocations(application, profile, label).getLocations(); + if (!ObjectUtils.isEmpty(properties) && properties.isReverseLocationOrder()) { + Collections.reverse(Arrays.asList(locations)); + } ArrayList locationResources = new ArrayList<>(); for (String location : locations) { if (!PathUtils.isInvalidEncodedLocation(location)) { diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeIntegrationTests.java index f2ec4f89..cceb5b2a 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeIntegrationTests.java @@ -94,6 +94,59 @@ public class CompositeIntegrationTests { } + @RunWith(SpringRunner.class) + @SpringBootTest(classes = TestConfigServerApplication.class, + properties = { "spring.config.name:compositeconfigserver", + "spring.cloud.config.server.svn.uri:file:///./target/repos/svn-config-repo", + "spring.cloud.config.server.svn.order:2", + "spring.cloud.config.server.git.uri:file:./target/repos/config-repo", + "spring.cloud.config.server.git.order:1", "spring.cloud.config.server.reverseLocationOrder:true" }, + webEnvironment = RANDOM_PORT) + @ActiveProfiles({ "test", "git", "subversion" }) + public static class ReverseLocationOrderTest { + + @LocalServerPort + private int port; + + @BeforeClass + public static void init() throws Exception { + // mock Git configuration to make tests independent of local Git configuration + SystemReader.setInstance(new MockSystemReader()); + + ConfigServerTestUtils.prepareLocalRepo(); + ConfigServerTestUtils.prepareLocalSvnRepo("src/test/resources/svn-config-repo", + "target/repos/svn-config-repo"); + } + + @Test + public void contextLoads() { + ResponseEntity response = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/foo/development/", HttpMethod.GET, getV2AcceptEntity(), + Environment.class); + Environment environment = response.getBody(); + assertThat(3).isEqualTo(environment.getPropertySources().size()); + assertThat("overrides").isEqualTo(environment.getPropertySources().get(0).getName()); + assertThat(environment.getPropertySources().get(1).getName().contains("config-repo") + && !environment.getPropertySources().get(1).getName().contains("svn-config-repo")).isTrue(); + assertThat(environment.getPropertySources().get(2).getName().contains("svn-config-repo")).isTrue(); + ConfigServerTestUtils.assertConfigEnabled(environment); + } + + @Test + public void resourceEndpointsWork() { + // This request will get the file from the Git Repo because its order is first + // However since spring.cloud.config.server.reverseLocationOrder is true, the + // SVN repo + // will be searched first and return the file from that repo + String text = new TestRestTemplate().getForObject( + "http://localhost:" + this.port + "/foo/development/composite/bar.properties", String.class); + + String expected = "foo: bar"; + assertThat(expected).isEqualTo(text).as("invalid content"); + } + + } + @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfigServerApplication.class, properties = { "spring.config.name:compositeconfigserver",