Support for git labels with slashes ("/")

Spring MVC can't match a pattern with a slash in it so we support
a special character sequence "(_)" which is legal in a URL
but hopefully rare in a git label.

Fixes gh-154
This commit is contained in:
Dave Syer
2015-06-24 14:17:51 +01:00
parent 44fa832d3d
commit 0834917275
3 changed files with 102 additions and 16 deletions

View File

@@ -88,7 +88,12 @@ filesystem repository, so that the server can clone it and use a local
working copy as a cache.
This repository implementation maps the `{label}` parameter of the
HTTP resource to a git label (commit id, branch name or tag).
HTTP resource to a git label (commit id, branch name or tag). If the
git branch or tag name contains a slash ("/") then the label in the
HTTP URL should be specified with the special string "(_)" instead (to
avoid ambiguity with other URL paths). Be careful with the brackets in
the URL if you are using a command line client like curl (e.g. escape
them from the shell with quotes '').
Spring Cloud Config Server supports a single or multiple git
repositories:

View File

@@ -92,18 +92,25 @@ public class EnvironmentController {
@RequestMapping("/{name}/{profiles:.*[^-].*}")
public Environment defaultLabel(@PathVariable String name,
@PathVariable String profiles) {
return labelled(name, profiles, defaultLabel);
return labelled(name, profiles, this.defaultLabel);
}
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
Environment environment = repository.findOne(name, profiles, label);
if (environmentEncryptor != null) {
environment = environmentEncryptor.decrypt(environment);
if (label==null) {
label = this.defaultLabel;
}
if (!overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", overrides));
if (label!=null && label.contains("(_)")) {
// "(_)" is uncommon in a git branch name, but "/" cannot be matched by Spring MVC
label = label.replace("(_)", "/");
}
Environment environment = this.repository.findOne(name, profiles, label);
if (this.environmentEncryptor != null) {
environment = this.environmentEncryptor.decrypt(environment);
}
if (!this.overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", this.overrides));
}
return environment;
}
@@ -111,7 +118,7 @@ public class EnvironmentController {
@RequestMapping("/{name}-{profiles}.properties")
public ResponseEntity<String> properties(@PathVariable String name,
@PathVariable String profiles) throws IOException {
return labelledProperties(name, profiles, defaultLabel);
return labelledProperties(name, profiles, this.defaultLabel);
}
@RequestMapping("/{label}/{name}-{profiles}.properties")
@@ -126,7 +133,7 @@ public class EnvironmentController {
@RequestMapping("{name}-{profiles}.json")
public ResponseEntity<Map<String, Object>> jsonProperties(@PathVariable String name,
@PathVariable String profiles) throws Exception {
return labelledJsonProperties(name, profiles, defaultLabel);
return labelledJsonProperties(name, profiles, this.defaultLabel);
}
@RequestMapping("/{label}/{name}-{profiles}.json")
@@ -153,7 +160,7 @@ public class EnvironmentController {
@RequestMapping({ "/{name}-{profiles}.yml", "/{name}-{profiles}.yaml" })
public ResponseEntity<String> yaml(@PathVariable String name,
@PathVariable String profiles) throws Exception {
return labelledYaml(name, profiles, defaultLabel);
return labelledYaml(name, profiles, this.defaultLabel);
}
@RequestMapping({ "/{label}/{name}-{profiles}.yml", "/{label}/{name}-{profiles}.yaml" })
@@ -251,7 +258,7 @@ public class EnvironmentController {
else {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) current
.get(keys[i]);
.get(keys[i]);
current = map;
}
}

View File

@@ -16,31 +16,48 @@
package org.springframework.cloud.config.server;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.EnvironmentControllerIntegrationTests.ControllerConfiguration;
import org.springframework.cloud.config.server.encryption.CipherEnvironmentEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author Dave Syer
* @author Roy Clarkson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ControllerConfiguration.class)
@WebAppConfiguration
public class EnvironmentControllerIntegrationTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
private EnvironmentRepository repository = Mockito.mock(EnvironmentRepository.class);;
@Autowired
private EnvironmentRepository repository;
@Before
public void init() {
Mockito.when(this.repository.getDefaultLabel()).thenReturn("master");
this.mvc = MockMvcBuilders.standaloneSetup(
new EnvironmentController(this.repository,
new CipherEnvironmentEncryptor(null))).build();
Mockito.reset(this.repository);
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
@@ -49,6 +66,34 @@ public class EnvironmentControllerIntegrationTests {
new Environment("foo", "default"));
this.mvc.perform(MockMvcRequestBuilders.get("/foo/default")).andExpect(
MockMvcResultMatchers.status().isOk());
Mockito.verify(this.repository).findOne("foo", "default", "master");
}
@Test
public void propertiesNoLabel() throws Exception {
Mockito.when(this.repository.findOne("foo", "default", "master")).thenReturn(
new Environment("foo", "default"));
this.mvc.perform(MockMvcRequestBuilders.get("/foo-default.properties")).andExpect(
MockMvcResultMatchers.status().isOk());
Mockito.verify(this.repository).findOne("foo", "default", "master");
}
@Test
public void propertiesLabel() throws Exception {
Mockito.when(this.repository.findOne("foo", "default", "label")).thenReturn(
new Environment("foo", "default"));
this.mvc.perform(MockMvcRequestBuilders.get("/label/foo-default.properties")).andExpect(
MockMvcResultMatchers.status().isOk());
Mockito.verify(this.repository).findOne("foo", "default", "label");
}
@Test
public void propertiesLabelWithSlash() throws Exception {
Mockito.when(this.repository.findOne("foo", "default", "label/spam")).thenReturn(
new Environment("foo", "default"));
this.mvc.perform(MockMvcRequestBuilders.get("/label(_)spam/foo-default.properties")).andExpect(
MockMvcResultMatchers.status().isOk());
Mockito.verify(this.repository).findOne("foo", "default", "label/spam");
}
@Test
@@ -67,4 +112,33 @@ public class EnvironmentControllerIntegrationTests {
MockMvcResultMatchers.status().isOk());
}
@Test
public void environmentWithLabelContainingSlash() throws Exception {
Mockito.when(this.repository.findOne("foo", "default", "feature/puff"))
.thenReturn(new Environment("foo", "default"));
this.mvc.perform(MockMvcRequestBuilders.get("/foo/default/feature(_)puff"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("\"propertySources\":")));
}
@Configuration
@EnableWebMvc
@Import(PropertyPlaceholderAutoConfiguration.class)
public static class ControllerConfiguration {
@Bean
public EnvironmentRepository environmentRepository() {
EnvironmentRepository repository = Mockito.mock(EnvironmentRepository.class);
Mockito.when(repository.getDefaultLabel()).thenReturn("master");
return repository;
}
@Bean
public EnvironmentController controller() {
return new EnvironmentController(environmentRepository(),
new CipherEnvironmentEncryptor(null));
}
}
}