From 8478a5263f8fb56027dc828d6a0636277709401a Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 9 Mar 2023 10:59:41 -0500 Subject: [PATCH 1/2] 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", From 599c0603fe8f34e3c43c59b10e08ceaebac629f4 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Wed, 22 Mar 2023 17:17:59 -0400 Subject: [PATCH 2/2] Document how to override properties locally (#2242) Fixes #2022 Co-authored-by: Ryan Baxter <524254+ryanjbaxter@users.noreply.github.com> --- .../main/asciidoc/spring-cloud-config.adoc | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index db5ac73b..3c4d5194 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -1316,6 +1316,61 @@ However, in properties files, you do need to escape the backslash, when you conf You can change the priority of all overrides in the client to be more like default values, letting applications supply their own values in environment variables or System properties, by setting the `spring.cloud.config.overrideNone=true` flag (the default is false) in the remote repository. +==== Using Bootstrap To Override Properties + +If you enable <>, you can allow client applications to override configuration from the config server by placing two properties within +the applications configuration coming from the config server. + +[source,properties] +---- +spring.cloud.config.allowOverride=true +spring.cloud.config.overrideNone=true +---- + +With Bootstrap enabled and these two properties set to true you will be able to override configuration from the config server +within the clients application configuration. + +==== Overriding Properties Using Placeholders + +A cleaner way to override properties without enabling config first bootstrap is to use property placeholders in the configuration coming from the config server. + +For example if the configuration coming from the config server contains the following property + +[source,properties] +---- +hello=${app.hello:Hello From Config Server!} +---- + +You can override the value of `hello` coming from the config server by setting `app.hello` in your local application configuration + +[source,properties] +---- +app.hello=Hello From Application! +---- + +==== Overriding Properties Using Profiles + +The final way to override properties coming from the config server is to specify them in profile specific configuration file within the client +application. + +For example, if you have the following configuration from the config server + +[source,properties] +---- +hello="Hello From Config Server!" +---- + +You can override the value of `hello` in the client application by setting `hello` in a profile specific configuration file and +then enabling that profile. + +.application-overrides.properties +[source,properties] +---- +hello="Hello From Application!" +---- + +In the above example you would have to enable the `overrides` profile. + === Health Indicator Config Server comes with a Health Indicator that checks whether the configured `EnvironmentRepository` is working.