diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 8ec76eb2..3b3c5412 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -1164,12 +1164,14 @@ server { } ---- -NOTE: just like the source files for environment configuration, the +NOTE: Just like the source files for environment configuration, the "profile" is used to resolve the file name, so if you want a profile-specific file then `/\*/development/*/logback.xml` will be resolved by a file called `logback-development.xml` (in preference to `logback.xml`). +NOTE: If you do not want to supply the `label` and let the server use the default label, you can supply a `useDefaultLabel` request parameter. So, the above example for the `default` profile could look like `/foo/default/nginx.conf?useDefaultLabel`. + == Embedding the Config Server The Config Server runs best as a standalone application, but if you diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/bootstrap/ConfigServerBootstrapConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/bootstrap/ConfigServerBootstrapConfiguration.java index dc8b611a..388144c0 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/bootstrap/ConfigServerBootstrapConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/bootstrap/ConfigServerBootstrapConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.cloud.config.client.ConfigClientProperties; import org.springframework.cloud.config.server.config.ConfigServerProperties; import org.springframework.cloud.config.server.config.EnvironmentRepositoryConfiguration; +import org.springframework.cloud.config.server.config.TransportConfiguration; import org.springframework.cloud.config.server.environment.EnvironmentRepository; import org.springframework.cloud.config.server.environment.EnvironmentRepositoryPropertySourceLocator; import org.springframework.context.annotation.Bean; @@ -44,7 +45,7 @@ import org.springframework.util.StringUtils; public class ConfigServerBootstrapConfiguration { @EnableConfigurationProperties(ConfigServerProperties.class) - @Import(EnvironmentRepositoryConfiguration.class) + @Import({ EnvironmentRepositoryConfiguration.class, TransportConfiguration.class }) protected static class LocalPropertySourceLocatorConfiguration { @Autowired diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelper.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelper.java index 05a621b5..dca6c40c 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelper.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelper.java @@ -117,7 +117,7 @@ class EnvironmentPrefixHelper { if (value.contains(ESCAPE)) { return value.substring(value.indexOf(ESCAPE) + ESCAPE.length()); } - return value.substring(value.lastIndexOf("}") + 1); + return value.replaceFirst("^(\\{.*?:.*?\\})+", ""); } private String removeEnvironmentPrefix(String input) { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java index feea79ae..4c9786a5 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java @@ -49,6 +49,7 @@ import org.springframework.web.util.UrlPathHelper; * to replace placeholders in the resource text. * * @author Dave Syer + * @author Daniel Lavoie * */ @RestController @@ -77,9 +78,23 @@ public class ResourceController { return retrieve(name, profile, label, path, resolvePlaceholders); } + @RequestMapping(value = "/{name}/{profile}/**", params = "useDefaultLabel") + public String retrieve(@PathVariable String name, @PathVariable String profile, + HttpServletRequest request, + @RequestParam(defaultValue = "true") boolean resolvePlaceholders) + throws IOException { + String path = getFilePath(request, name, profile, null); + return retrieve(name, profile, null, path, resolvePlaceholders); + } + private String getFilePath(HttpServletRequest request, String name, String profile, String label) { - String stem = String.format("/%s/%s/%s/", name, profile, label); + String stem; + if(label != null ) { + stem = String.format("/%s/%s/%s/", name, profile, label); + }else { + stem = String.format("/%s/%s/", name, profile); + } String path = this.helper.getPathWithinApplication(request); path = path.substring(path.indexOf(stem) + stem.length()); return path; @@ -87,6 +102,11 @@ public class ResourceController { synchronized String retrieve(String name, String profile, String label, String path, boolean resolvePlaceholders) throws IOException { + if (name != null && name.contains("(_)")) { + // "(_)" is uncommon in a git repo name, but "/" cannot be matched + // by Spring MVC + name = name.replace("(_)", "/"); + } if (label != null && label.contains("(_)")) { // "(_)" is uncommon in a git branch name, but "/" cannot be matched // by Spring MVC @@ -116,6 +136,11 @@ public class ResourceController { synchronized byte[] binary(String name, String profile, String label, String path) throws IOException { + if (name != null && name.contains("(_)")) { + // "(_)" is uncommon in a git repo name, but "/" cannot be matched + // by Spring MVC + name = name.replace("(_)", "/"); + } if (label != null && label.contains("(_)")) { // "(_)" is uncommon in a git branch name, but "/" cannot be matched // by Spring MVC diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EncryptionControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EncryptionControllerTests.java index 2ff4e23c..84a08821 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EncryptionControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EncryptionControllerTests.java @@ -135,6 +135,20 @@ public class EncryptionControllerTests { assertThat("Prefix must be stripped prior to encrypt", captor.getValue(), not(containsString("{key:test}"))); } + @Test + public void encryptDecyptTextWithCurlyBrace() { + this.controller = new EncryptionController( + new SingleTextEncryptorLocator(new RsaSecretEncryptor())); + + String plain = "textwith}brace"; + + String cipher = this.controller.encrypt(plain, + MediaType.APPLICATION_FORM_URLENCODED); + String decrypt = this.controller.decrypt(cipher, + MediaType.APPLICATION_FORM_URLENCODED); + assertEquals(plain, decrypt); + } + @Test public void addEnvironment() { TextEncryptorLocator locator = new TextEncryptorLocator() { diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelperTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelperTests.java index c9b810aa..b1b997e9 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelperTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/encryption/EnvironmentPrefixHelperTests.java @@ -81,4 +81,14 @@ public class EnvironmentPrefixHelperTests { assertEquals("mykey", keys.get("key")); } + @Test + public void testTextWithCurlyBracesNoPrefix() { + assertEquals("textwith}brac{es", this.helper.stripPrefix("textwith}brac{es")); + } + + @Test + public void testTextWithCurlyBracesPrefix() { + assertEquals("textwith}brac{es{and}prefix", this.helper + .stripPrefix("{key:foo}{name:bar}textwith}brac{es{and}prefix")); + } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerIntegrationTests.java index 685bd385..9a5b9df3 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerIntegrationTests.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.config.environment.Environment; +import org.springframework.cloud.config.server.environment.EnvironmentController; import org.springframework.cloud.config.server.environment.EnvironmentRepository; import org.springframework.cloud.config.server.resource.ResourceControllerIntegrationTests.ControllerConfiguration; import org.springframework.context.annotation.Bean; @@ -41,10 +42,11 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; /** * @author Dave Syer + * @author Daniel Lavoie * */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = ControllerConfiguration.class) +@SpringBootTest(classes = ControllerConfiguration.class, properties = "trace") @DirtiesContext public class ResourceControllerIntegrationTests { @@ -74,6 +76,19 @@ public class ResourceControllerIntegrationTests { Mockito.verify(this.resources).findOne("foo", "default", "master", "foo.txt"); } + @Test + public void resourceNoLabel() throws Exception { + Mockito.when(this.repository.findOne("foo", "default", null)) + .thenReturn(new Environment("foo", "default", "master")); + Mockito.when(this.resources.findOne("foo", "default", null, "foo.txt")) + .thenReturn(new ByteArrayResource("hello".getBytes())); + this.mvc.perform(MockMvcRequestBuilders.get("/foo/default/foo.txt") + .param("useDefaultLabel", "")) + .andExpect(MockMvcResultMatchers.status().isOk()); + Mockito.verify(this.repository).findOne("foo", "default", null); + Mockito.verify(this.resources).findOne("foo", "default", null, "foo.txt"); + } + @Configuration @EnableWebMvc @Import(PropertyPlaceholderAutoConfiguration.class) @@ -92,7 +107,12 @@ public class ResourceControllerIntegrationTests { } @Bean - public ResourceController controller() { + public EnvironmentController environmentController() { + return new EnvironmentController(environmentRepository()); + } + + @Bean + public ResourceController resourceController() { return new ResourceController(resourceRepository(), environmentRepository()); } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerTests.java index 87b1cba6..a135cb2a 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerTests.java @@ -28,6 +28,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** @@ -89,6 +90,52 @@ public class ResourceControllerTests { assertEquals("foo: ${foo}", resource); } + @Test + public void applicationAndLabelPlaceholdersWithoutSlash() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{application}/{label}"); + String resource = this.controller.retrieve("dev", "bar", "spam", "foo.txt", true); + assertEquals("foo: dev_bar/spam", resource); + } + + @Test + public void applicationPlaceholderWithSlash() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{application}"); + String resource = this.controller.retrieve("dev(_)spam", "bar", "", "foo.txt", true); + assertEquals("foo: dev_bar/spam", resource); + } + + @Test + public void applicationPlaceholderWithSlashNullLabel() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{application}"); + String resource = this.controller.retrieve("dev(_)spam", "bar", null, "foo.txt", true); + assertEquals("foo: dev_bar/spam", resource); + } + + @Test + public void labelPlaceholderWithSlash() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{label}"); + String resource = this.controller.retrieve("dev", "bar", "dev(_)spam", "foo.txt", true); + assertEquals("foo: dev_bar/spam", resource); + } + + @Test + public void profilePlaceholderNullLabel() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{profile}"); + String resource = this.controller.retrieve("bar", "dev", null, "spam/foo.txt", true); + assertEquals("foo: dev_bar/spam", resource); + } + + @Test + public void nullNameAndLabel() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test"); + try { + this.controller.retrieve(null, "foo", "bar", "spam/foo.txt", true); + } + catch (Exception e) { + assertNotNull(e); + } + } + @Test public void labelWithSlash() throws Exception { this.environmentRepository.setSearchLocations("classpath:/test"); @@ -145,6 +192,52 @@ public class ResourceControllerTests { assertEquals("foo: dev_bar/spam", resource); } + @Test + public void applicationAndLabelPlaceholdersWithoutSlashForBinary() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{application}/{label}"); + byte[] resource = this.controller.binary("dev", "bar", "spam", "foo.txt"); + assertEquals("foo: dev_bar/spam", new String(resource)); + } + + @Test + public void applicationPlaceholderWithSlashForBinary() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{application}"); + byte[] resource = this.controller.binary("dev(_)spam", "bar", "", "foo.txt"); + assertEquals("foo: dev_bar/spam", new String(resource)); + } + + @Test + public void applicationPlaceholderWithSlashForBinaryNullLabel() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{application}"); + byte[] resource = this.controller.binary("dev(_)spam", "bar", null, "foo.txt"); + assertEquals("foo: dev_bar/spam", new String(resource)); + } + + @Test + public void labelPlaceholderWithSlashForBinary() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{label}"); + byte[] resource = this.controller.binary("dev", "bar", "dev(_)spam", "foo.txt"); + assertEquals("foo: dev_bar/spam", new String(resource)); + } + + @Test + public void profilePlaceholderForBinaryNullLabel() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test/{profile}"); + byte[] resource = this.controller.binary("bar", "dev", null, "spam/foo.txt"); + assertEquals("foo: dev_bar/spam", new String(resource)); + } + + @Test + public void forBinaryNullName() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test"); + try { + this.controller.binary(null, "foo", "bar", "spam/foo.txt"); + } + catch (Exception e) { + assertNotNull(e); + } + } + @Test public void labelWithSlashForBinary() throws Exception { this.environmentRepository.setSearchLocations("classpath:/test");