Fix native profile so it behaves as described in docs

The SCM repositories have a "clean()" method to strip out the server's
own configuration files from the property sources shipped to remote
clients. The "native" one didn't have that method so this change adds
it with a similar implementation to the SCM case, except slightly
more complicated because of the extra search locations added by the
"label" parameter.

Also changes the name SpringApplicationEnvironmentRepository to
NativeEnvironmentRepository to match the profile and the config properties.

Fixes gh-109
This commit is contained in:
Dave Syer
2015-03-20 14:42:42 +00:00
parent 5a80ce9e3a
commit b1ba8df066
9 changed files with 292 additions and 222 deletions

View File

@@ -40,11 +40,11 @@ public abstract class AbstractScmEnvironmentRepository implements EnvironmentRep
InitializingBean {
private static Log logger = LogFactory.getLog(AbstractScmEnvironmentRepository.class);
protected File basedir;
protected String uri;
protected ConfigurableEnvironment environment;
protected String username;
protected String password;
private File basedir;
private String uri;
private ConfigurableEnvironment environment;
private String username;
private String password;
private String[] searchPaths = new String[0];
public AbstractScmEnvironmentRepository(ConfigurableEnvironment environment) {
@@ -72,6 +72,14 @@ public abstract class AbstractScmEnvironmentRepository implements EnvironmentRep
throw new IllegalStateException("Cannot create temp dir", e);
}
}
protected ConfigurableEnvironment getEnvironment() {
return environment;
}
protected void setEnvironment(ConfigurableEnvironment environment) {
this.environment = environment;
}
public void setUri(String uri) {
while (uri.endsWith("/")) {
@@ -148,12 +156,6 @@ public abstract class AbstractScmEnvironmentRepository implements EnvironmentRep
for (PropertySource source : value.getPropertySources()) {
String name = source.getName().replace(
getWorkingDirectory().toURI().toString(), "");
if (name.contains(("classpath:/"))) {
continue;
}
if (environment.getPropertySources().contains(name)) {
continue;
}
name = name.replace("applicationConfig: [", "");
name = uri + "/" + name.replace("]", "");
result.add(new PropertySource(name, source.getSource()));

View File

@@ -35,10 +35,15 @@ public class ConfigServerConfiguration {
@Configuration
@Profile("native")
protected static class NativeRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public EnvironmentRepository environmentRepository() {
return new SpringApplicationEnvironmentRepository();
return new NativeEnvironmentRepository(environment);
}
}
@Configuration

View File

@@ -64,14 +64,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
super(environment);
}
protected ConfigurableEnvironment getEnvironment() {
return environment;
}
protected void setEnvironment(ConfigurableEnvironment environment) {
this.environment = environment;
}
@Override
public Environment findOne(String application, String profile, String label) {
initialize();
@@ -100,12 +92,13 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(uri != null, "You need to configure a uri for the git repository");
Assert.state(getUri() != null,
"You need to configure a uri for the git repository");
}
private synchronized Environment loadEnvironment(Git git, String application,
String profile, String label) throws GitAPIException {
SpringApplicationEnvironmentRepository environment = new SpringApplicationEnvironmentRepository();
NativeEnvironmentRepository environment = new NativeEnvironmentRepository(getEnvironment());
git.getRepository().getConfig().setString("branch", label, "merge", label);
Ref ref = checkout(git, label);
if (shouldPull(git, ref)) {
@@ -145,7 +138,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
private void pull(Git git, String label, Ref ref) {
PullCommand pull = git.pull();
try {
if (hasText(username)) {
if (hasText(getUsername())) {
setCredentialsProvider(pull);
}
pull.call();
@@ -162,7 +155,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
}
private Git createGitClient() throws IOException, GitAPIException {
if (new File(basedir, ".git").exists()) {
if (new File(getBasedir(), ".git").exists()) {
return openGitRepository();
}
else {
@@ -172,8 +165,8 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
private Git copyRepository() throws IOException, GitAPIException {
deleteBaseDirIfExists();
Assert.state(basedir.mkdirs(), "Could not create basedir: " + basedir);
if (uri.startsWith("file:")) {
Assert.state(getBasedir().mkdirs(), "Could not create basedir: " + getBasedir());
if (getUri().startsWith("file:")) {
return copyFromLocalRepository();
}
else {
@@ -189,18 +182,19 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
private Git copyFromLocalRepository() throws IOException {
Git git;
File remote = new UrlResource(StringUtils.cleanPath(uri)).getFile();
Assert.state(remote.isDirectory(), "No directory at " + uri);
File remote = new UrlResource(StringUtils.cleanPath(getUri())).getFile();
Assert.state(remote.isDirectory(), "No directory at " + getUri());
File gitDir = new File(remote, ".git");
Assert.state(gitDir.exists(), "No .git at " + uri);
Assert.state(gitDir.isDirectory(), "No .git directory at " + uri);
Assert.state(gitDir.exists(), "No .git at " + getUri());
Assert.state(gitDir.isDirectory(), "No .git directory at " + getUri());
git = Git.open(remote);
return git;
}
private Git cloneToBasedir() throws GitAPIException {
CloneCommand clone = Git.cloneRepository().setURI(uri).setDirectory(basedir);
if (hasText(username)) {
CloneCommand clone = Git.cloneRepository().setURI(getUri())
.setDirectory(getBasedir());
if (hasText(getUsername())) {
setCredentialsProvider(clone);
}
return clone.call();
@@ -209,7 +203,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
private void tryFetch(Git git) {
try {
FetchCommand fetch = git.fetch();
if (hasText(username)) {
if (hasText(getUsername())) {
setCredentialsProvider(fetch);
}
fetch.call();
@@ -220,9 +214,9 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
}
private void deleteBaseDirIfExists() {
if (basedir.exists()) {
if (getBasedir().exists()) {
try {
FileUtils.delete(basedir, FileUtils.RECURSIVE);
FileUtils.delete(getBasedir(), FileUtils.RECURSIVE);
}
catch (IOException e) {
throw new IllegalStateException("Failed to initialize base directory", e);
@@ -231,7 +225,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
}
private void initialize() {
if (uri.startsWith("file:") && !initialized) {
if (getUri().startsWith("file:") && !initialized) {
SshSessionFactory.setInstance(new JschConfigSessionFactory() {
@Override
protected void configure(Host hc, Session session) {
@@ -243,8 +237,8 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
}
private void setCredentialsProvider(TransportCommand<?, ?> cmd) {
cmd.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username,
password));
cmd.setCredentialsProvider(new UsernamePasswordCredentialsProvider(getUsername(),
getPassword()));
}
private void trackBranch(Git git, CheckoutCommand checkout, String label) {

View File

@@ -16,35 +16,53 @@
package org.springframework.cloud.config.server;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
/**
* Simple implementation of {@link EnvironmentRepository} that just reflects an existing
* Spring Environment.
* Simple implementation of {@link EnvironmentRepository} that uses a SpringApplication
* and configuration files located through the normal protocols. The resulting Environment
* is composed of property sources located using the application name as the config file
* stem (spring.config.name) and the environment name as a Spring profile.
*
* @author Dave Syer
*
*/
@ConfigurationProperties("spring.cloud.config.server.native")
public class NativeEnvironmentRepository implements EnvironmentRepository {
private Set<String> standardSources = new HashSet<String>(Arrays.asList(
"vcap",
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
private static Log logger = LogFactory
.getLog(NativeEnvironmentRepository.class);
/**
* Locations to search for configuration files. Defaults to the same as a Spring Boot
* app so [classpath:/,classpath:/config/,file:./,file:./config/].
*/
private String[] searchLocations;
/**
* Flag to determine how to handle exceptions during decryption (default false).
*/
private boolean failOnError = false;
private static final String[] DEFAULT_LOCATIONS = new String[] { "classpath:/",
"classpath:/config/", "file:./", "file:./config/" };
private ConfigurableEnvironment environment;
@@ -52,17 +70,146 @@ public class NativeEnvironmentRepository implements EnvironmentRepository {
this.environment = environment;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public boolean isFailOnError() {
return failOnError;
}
@Override
public Environment findOne(String application, String env, String label) {
Environment result = new Environment(application, StringUtils.commaDelimitedListToStringArray(env), label);
for (org.springframework.core.env.PropertySource<?> source : environment.getPropertySources()) {
public Environment findOne(String config, String profile, String label) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(
PropertyPlaceholderAutoConfiguration.class);
ConfigurableEnvironment environment = getEnvironment(profile);
builder.environment(environment);
builder.web(false).showBanner(false);
String[] args = getArgs(config, label);
// Explicitly set the listeners (to exclude logging listener which would change
// log
// levels in the caller)
builder.application().setListeners(
Collections.singletonList(new ConfigFileApplicationListener()));
ConfigurableApplicationContext context = builder.run(args);
environment.getPropertySources().remove("profiles");
try {
return clean(new PassthruEnvironmentRepository(environment).findOne(config,
profile, label));
}
finally {
context.close();
}
}
private ConfigurableEnvironment getEnvironment(String profile) {
ConfigurableEnvironment environment = new StandardEnvironment();
environment.getPropertySources()
.addFirst(
new MapPropertySource("profiles", Collections
.<String, Object> singletonMap("spring.profiles.active",
profile)));
return environment;
}
protected Environment clean(Environment value) {
Environment result = new Environment(value.getName(), value.getProfiles(),
value.getLabel());
for (PropertySource source : value.getPropertySources()) {
String name = source.getName();
if (!standardSources.contains(name) && source instanceof MapPropertySource) {
result.add(new PropertySource(name, (Map<?, ?>) source.getSource()));
if (environment.getPropertySources().contains(name)) {
continue;
}
name = name.replace("applicationConfig: [", "");
name = name.replace("]", "");
if (searchLocations != null) {
boolean matches = false;
String normal = name;
if (normal.startsWith("file:")) {
normal = new File(normal.substring("file:".length()))
.getAbsolutePath();
}
for (String pattern : StringUtils
.commaDelimitedListToStringArray(getLocations(searchLocations,
result.getLabel()))) {
if (!pattern.contains(":")) {
pattern = "file:" + pattern;
}
if (pattern.startsWith("file:")) {
pattern = StringUtils.cleanPath(new File(pattern
.substring("file:".length())).getAbsolutePath()) + "/";
}
if (logger.isTraceEnabled()) {
logger.trace("Testing pattern: " + pattern
+ " with property source: " + name);
}
if (normal.startsWith(pattern)
&& !normal.substring(pattern.length()).contains("/")) {
matches = true;
break;
}
}
if (!matches) {
// Don't include this one: it wasn't matched by our search locations
if (logger.isDebugEnabled()) {
logger.debug("Not adding property source: " + name);
}
continue;
}
}
logger.info("Adding property source: " + name);
result.add(new PropertySource(name, source.getSource()));
}
return result;
}
private String[] getArgs(String config, String label) {
List<String> list = new ArrayList<String>();
if (!config.startsWith("application")) {
config = "application," + config;
}
list.add("--spring.config.name=" + config);
list.add("--spring.cloud.bootstrap.enabled=false");
list.add("--encrypt.failOnError=" + failOnError);
String[] locations = this.searchLocations;
if (searchLocations == null) {
locations = DEFAULT_LOCATIONS;
}
list.add("--spring.config.location=" + getLocations(locations, label));
return list.toArray(new String[0]);
}
private String getLocations(String[] locations, String label) {
List<String> output = new ArrayList<String>();
for (String location : locations) {
output.add(location);
}
for (String location : locations) {
if (isDirectory(location) && StringUtils.hasText(label)) {
output.add(location + label.trim() + "/");
}
}
return StringUtils.collectionToCommaDelimitedString(output);
}
public String[] getSearchLocations() {
return searchLocations;
}
public void setSearchLocations(String... locations) {
this.searchLocations = locations;
for (int i = 0; i < locations.length; i++) {
String location = locations[i];
if (isDirectory(location) && !location.endsWith("/")) {
location = location + "/";
}
locations[i] = location;
}
}
private boolean isDirectory(String location) {
return !location.endsWith(".properties") && !location.endsWith(".yml")
&& !location.endsWith(".yaml");
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.config.server;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
/**
* Simple implementation of {@link EnvironmentRepository} that just reflects an existing
* Spring Environment.
*
* @author Dave Syer
*
*/
public class PassthruEnvironmentRepository implements EnvironmentRepository {
private Set<String> standardSources = new HashSet<String>(Arrays.asList(
"vcap",
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
private ConfigurableEnvironment environment;
public PassthruEnvironmentRepository(ConfigurableEnvironment environment) {
this.environment = environment;
}
@Override
public Environment findOne(String application, String env, String label) {
Environment result = new Environment(application, StringUtils.commaDelimitedListToStringArray(env), label);
for (org.springframework.core.env.PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (!standardSources.contains(name) && source instanceof MapPropertySource) {
result.add(new PropertySource(name, (Map<?, ?>) source.getSource()));
}
}
return result;
}
}

View File

@@ -1,151 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.config.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.StringUtils;
/**
* Simple implementation of {@link EnvironmentRepository} that uses a SpringApplication
* and configuration files located through the normal protocols. The resulting Environment
* is composed of property sources located using the application name as the config file
* stem (spring.config.name) and the environment name as a Spring profile.
*
* @author Dave Syer
*
*/
@ConfigurationProperties("spring.cloud.config.server.native")
public class SpringApplicationEnvironmentRepository implements EnvironmentRepository {
/**
* Locations to search for configuration files. Defaults to the same as a Spring Boot
* app so [classpath:/,classpath:/config/,file:./,file:./config/].
*/
private String[] searchLocations;
/**
* Flag to determine how to handle exceptions during decryption (default false).
*/
private boolean failOnError = false;
private static final String[] DEFAULT_LOCATIONS = new String[] { "classpath:/",
"classpath:/config/", "file:./", "file:./config/" };
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public boolean isFailOnError() {
return failOnError;
}
@Override
public Environment findOne(String config, String profile, String label) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(
PropertyPlaceholderAutoConfiguration.class);
ConfigurableEnvironment environment = getEnvironment(profile);
builder.environment(environment);
builder.web(false).showBanner(false);
String[] args = getArgs(config, label);
// Explicitly set the listeners (to exclude logging listener which would change log
// levels in the caller)
builder.application().setListeners(
Collections.singletonList(new ConfigFileApplicationListener()));
ConfigurableApplicationContext context = builder.run(args);
environment.getPropertySources().remove("profiles");
try {
return new NativeEnvironmentRepository(environment).findOne(config, profile,
label);
}
finally {
context.close();
}
}
private ConfigurableEnvironment getEnvironment(String profile) {
ConfigurableEnvironment environment = new StandardEnvironment();
environment.getPropertySources()
.addFirst(
new MapPropertySource("profiles", Collections
.<String, Object> singletonMap("spring.profiles.active",
profile)));
return environment;
}
private String[] getArgs(String config, String label) {
List<String> list = new ArrayList<String>();
if (!config.startsWith("application")) {
config = "application," + config;
}
list.add("--spring.config.name=" + config);
list.add("--spring.cloud.bootstrap.enabled=false");
list.add("--encrypt.failOnError=" + failOnError);
if (searchLocations != null) {
list.add("--spring.config.location=" + getLocations(this.searchLocations, label));
}
else {
list.add("--spring.config.location=" + getLocations(DEFAULT_LOCATIONS, label));
}
return list.toArray(new String[0]);
}
private String getLocations(String[] locations, String label) {
List<String> output = new ArrayList<String>();
for (String location : locations) {
output.add(location);
}
for (String location : locations) {
if (isDirectory(location) && StringUtils.hasText(label)) {
output.add(location + label.trim() + "/");
}
}
return StringUtils.collectionToCommaDelimitedString(output);
}
public String[] getSearchLocations() {
return searchLocations;
}
public void setSearchLocations(String... locations) {
this.searchLocations = locations;
for (int i = 0; i < locations.length; i++) {
String location = locations[i];
if (isDirectory(location) && !location.endsWith("/")) {
location = location + "/";
}
locations[i] = location;
}
}
private boolean isDirectory(String location) {
return !location.endsWith(".properties") && !location.endsWith(".yml")
&& !location.endsWith(".yaml");
}
}

View File

@@ -71,7 +71,8 @@ public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepositor
}
private Environment loadEnvironment(String application, String profile, String label) {
final SpringApplicationEnvironmentRepository environmentRepository = new SpringApplicationEnvironmentRepository();
final NativeEnvironmentRepository environmentRepository = new NativeEnvironmentRepository(
getEnvironment());
environmentRepository.setSearchLocations(getSearchLocations(getSvnPath(
getWorkingDirectory(), label)));
return environmentRepository.findOne(application, profile, label);
@@ -116,7 +117,7 @@ public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepositor
@Override
protected File getWorkingDirectory() {
return this.basedir;
return this.getBasedir();
}
private File getSvnPath(File workingDirectory, String label) {

View File

@@ -19,16 +19,17 @@ import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.SpringApplicationEnvironmentRepository;
import org.springframework.cloud.config.server.NativeEnvironmentRepository;
import org.springframework.core.env.StandardEnvironment;
/**
* @author Dave Syer
*
*/
public class SpringApplicationEnvironmentRepositoryTests {
private SpringApplicationEnvironmentRepository repository = new SpringApplicationEnvironmentRepository();
public class NativeEnvironmentRepositoryTests {
private NativeEnvironmentRepository repository = new NativeEnvironmentRepository(
new StandardEnvironment());
@Test
public void vanilla() {
@@ -47,23 +48,25 @@ public class SpringApplicationEnvironmentRepositoryTests {
public void prefixed() {
repository.setSearchLocations("classpath:/test");
Environment environment = repository.findOne("foo", "development", "master");
assertEquals(3, environment.getPropertySources().size());
assertEquals(2, environment.getPropertySources().size());
}
@Test
public void prefixedWithFile() {
repository.setSearchLocations("file:./src/test/resources/test");
Environment environment = repository.findOne("foo", "development", "master");
assertEquals(3, environment.getPropertySources().size());
assertEquals(2, environment.getPropertySources().size());
}
@Test
public void labelled() {
repository.setSearchLocations("classpath:/test");
Environment environment = repository.findOne("foo", "development", "dev");
assertEquals(4, environment.getPropertySources().size());
// position 1 because it has higher precendence than anything except the foo-development.properties
assertEquals("dev_bar", environment.getPropertySources().get(1).getSource().get("foo"));
assertEquals(3, environment.getPropertySources().size());
// position 1 because it has higher precendence than anything except the
// foo-development.properties
assertEquals("dev_bar",
environment.getPropertySources().get(1).getSource().get("foo"));
}
}