Merge remote-tracking branch 'upstream/master'

updating fork from master
This commit is contained in:
rlynch2
2016-10-24 17:27:28 -07:00
31 changed files with 859 additions and 493 deletions

View File

@@ -1,5 +1,9 @@
// Do not edit this file (e.g. go instead to src/main/asciidoc)
image::https://circleci.com/gh/spring-cloud/spring-cloud-config/tree/master.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-config/tree/master"]
image::https://codecov.io/gh/spring-cloud/spring-cloud-config/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-config/branch/master"]
image::https://api.codacy.com/project/badge/Grade/f064024a072c477e97dca6ed5a70fccd?branch=master["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-config?branch=master&utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-config&utm_campaign=Badge_Grade"]
Spring Cloud Config provides server and client-side support for externalized configuration in a distributed system. With the Config Server you have a central place to manage external properties for applications across all environments. The concepts on both client and server map identically to the Spring `Environment` and `PropertySource` abstractions, so they fit very well with Spring applications, but can be used with any application running in any language. As an application moves through the deployment pipeline from dev to test and into production you can manage the configuration between those environments and be certain that applications have everything they need to run when they migrate. The default implementation of the server storage backend uses git so it easily supports labelled versions of configuration environments, as well as being accessible to a wide range of tooling for managing the content. It is easy to add alternative implementations and plug them in with Spring configuration.

26
circle.yml Normal file
View File

@@ -0,0 +1,26 @@
general:
branches:
ignore:
- gh-pages # list of branches to ignore
machine:
java:
version: openjdk8 #Open JDK has the JCE extentions installed by default
environment:
_JAVA_OPTIONS: "-Xms1024m -Xmx2048m"
dependencies:
override:
- ./mvnw -s .settings.xml -U --fail-never dependency:go-offline || true
test:
override:
- ./mvnw -s .settings.xml clean install org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
post:
- find . -type f -regex ".*/spring-cloud-*.*/target/*.*" | cpio -pdm $CIRCLE_ARTIFACTS
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
- find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
- bash <(curl -s https://codecov.io/bash)
notify:
webhooks:
# A list of hook hashes, containing the url field
# gitter hook
- url: https://webhooks.gitter.im/e/5de9034d65b40fc39d61

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
<name>Spring Cloud Config Docs</name>

View File

@@ -1,3 +1,6 @@
image::https://circleci.com/gh/spring-cloud/spring-cloud-config/tree/master.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-config/tree/master"]
image::https://codecov.io/gh/spring-cloud/spring-cloud-config/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-config/branch/master"]
image::https://api.codacy.com/project/badge/Grade/f064024a072c477e97dca6ed5a70fccd?branch=master["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-config?branch=master&utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-config&utm_campaign=Badge_Grade"]
include::intro.adoc[]
@@ -76,7 +79,7 @@ http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.ht
http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[Java 7 JCE]
http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[Java 8 JCE]
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[Java 8 JCE]
Extract files into JDK/jre/lib/security folder (whichever version of JRE/JDK x64/x86 you are using).

View File

@@ -5,6 +5,8 @@
:toc:
:nofooter:
*{spring-cloud-version}*
include::intro.adoc[]
== Quick Start
@@ -1160,10 +1162,58 @@ spring:
----
If you use another form of security you might need to provide a
`RestTemplate` to the `ConfigServicePropertySourceLocator` (e.g. by
If you use another form of security you might need to <<custom-rest-template,provide a
`RestTemplate`>> to the `ConfigServicePropertySourceLocator` (e.g. by
grabbing it in the bootstrap context and injecting one).
==== Health Indicator
The Config Client supplies a Spring Boot Health Indicator that attempts to load configuration from Config Server. The health indicator can be disabled by setting `health.config.enabled=false`. The response is also cached for performance reasons. The default cache time to live is 5 minutes. To change that value set the `health.config.time-to-live` property (in milliseconds).
[[custom-rest-template]]
==== Providing A Custom RestTemplate
In some cases you might need to customize the requests made to the config server from
the client. Typically this involves passing special `Authorization` headers to
authenticate requests to the server. To provide a custom `RestTemplate` follow the
steps below.
1. Set `spring.cloud.config.enabled=false` to disable the existing config server
property source.
2. Create a new configuration bean with an implementation of `PropertySourceLocator`.
.CustomConfigServiceBootstrapConfiguration.java
[source,java]
----
@Configuration
public class CustomConfigServiceBootstrapConfiguration {
@Bean
public ConfigClientProperties configClientProperties() {
ConfigClientProperties client = new ConfigClientProperties(this.environment);
client.setEnabled(false);
return client;
}
@Bean
public ConfigServicePropertySourceLocator configServicePropertySourceLocator() {
ConfigClientProperties clientProperties = configClientProperties();
ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(clientProperties);
configServicePropertySourceLocator.setRestTemplate(customRestTemplate(clientProperties));
return configServicePropertySourceLocator;
}
}
----
3. In `resources/META-INF` create a file called
`spring.factories` and specify your custom configuration.
.spring.factorties
[source,properties]
----
org.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfiguration
----
==== Vault
When using Vault as a backend to your config server the client will need to

9
mvnw vendored
View File

@@ -226,11 +226,9 @@ export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
VERSION=$(exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
-Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec )
echo "Running version check"
VERSION=$( sed '\!<parent!,\!</parent!d' `dirname $0`/pom.xml | grep '<version' | head -1 | sed -e 's/.*<version>//' -e 's!</version>.*$!!' )
echo "The found version is [${VERSION}]"
if echo $VERSION | egrep -q 'M|RC'; then
echo Activating \"milestone\" profile for version=\"$VERSION\"
@@ -240,7 +238,6 @@ else
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
fi
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \

54
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Config</name>
<description>Spring Cloud Config</description>
@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.2.0.RELEASE</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<scm>
@@ -22,6 +22,7 @@
</scm>
<properties>
<bintray.package>config</bintray.package>
<spring-cloud-commons.version>1.1.5.BUILD-SNAPSHOT</spring-cloud-commons.version>
</properties>
<modules>
<module>spring-cloud-config-dependencies</module>
@@ -41,6 +42,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons-dependencies</artifactId>
<version>${spring-cloud-commons.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
@@ -105,5 +113,47 @@
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>sonar</id>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>surefireArgLine</propertyName>
<destFile>${project.build.directory}/jacoco.exec</destFile>
</configuration>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Sets the VM argument line used when unit tests are run. -->
<argLine>${surefireArgLine}</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -53,6 +53,11 @@ public class ConfigClientAutoConfiguration {
return client;
}
@Bean
public ConfigClientHealthProperties configClientHealthProperties() {
return new ConfigClientHealthProperties();
}
@Configuration
@ConditionalOnClass(HealthIndicator.class)
@ConditionalOnBean(ConfigServicePropertySourceLocator.class)
@@ -61,24 +66,9 @@ public class ConfigClientAutoConfiguration {
@Bean
public ConfigServerHealthIndicator configServerHealthIndicator(
ConfigServicePropertySourceLocator locator, Environment environment) {
return new ConfigServerHealthIndicator(locator, environment);
}
}
@ConfigurationProperties("health.config")
public static class Health {
/**
* Flag to indicate that the config server health indicator should be installed.
*/
boolean enabled;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
ConfigServicePropertySourceLocator locator,
ConfigClientHealthProperties properties, Environment environment) {
return new ConfigServerHealthIndicator(locator, environment, properties);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2013-2016 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.client;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("health.config")
public class ConfigClientHealthProperties {
/**
* Flag to indicate that the config server health indicator should be installed.
*/
boolean enabled;
/**
* Time to live for cached result, in milliseconds. Default 300000 (5 min).
*/
private long timeToLive = 60 * 5 * 1000;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public long getTimeToLive() {
return timeToLive;
}
public void setTimeToLive(long timeToLive) {
this.timeToLive = timeToLive;
}
}

View File

@@ -16,17 +16,23 @@ import org.springframework.core.env.PropertySource;
public class ConfigServerHealthIndicator extends AbstractHealthIndicator {
private ConfigServicePropertySourceLocator locator;
private ConfigClientHealthProperties properties;
private Environment environment;
private long lastAccess = 0;
private PropertySource<?> cached;
public ConfigServerHealthIndicator(ConfigServicePropertySourceLocator locator,
Environment environment) {
Environment environment, ConfigClientHealthProperties properties) {
this.environment = environment;
this.locator = locator;
this.properties = properties;
}
@Override
protected void doHealthCheck(Builder builder) throws Exception {
PropertySource<?> propertySource = locator.locate(this.environment);
PropertySource<?> propertySource = getPropertySource();
builder.up();
if (propertySource instanceof CompositePropertySource) {
List<String> sources = new ArrayList<>();
@@ -40,4 +46,21 @@ public class ConfigServerHealthIndicator extends AbstractHealthIndicator {
builder.unknown().withDetail("error", "no property sources located");
}
}
private PropertySource<?> getPropertySource() {
long accessTime = System.currentTimeMillis();
if (isCacheStale(accessTime)) {
this.lastAccess = accessTime;
this.cached = locator.locate(this.environment);
}
return this.cached;
}
private boolean isCacheStale(long accessTime) {
if (this.cached == null) {
return true;
}
return (accessTime - this.lastAccess) >= this.properties.getTimeToLive();
}
}

View File

@@ -17,11 +17,16 @@
package org.springframework.cloud.config.client;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.Collections;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.actuate.health.Status;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
@@ -34,11 +39,11 @@ import org.springframework.core.env.PropertySource;
*/
public class ConfigServerHealthIndicatorTests {
private ConfigServicePropertySourceLocator locator = Mockito
.mock(ConfigServicePropertySourceLocator.class);
private Environment environment = Mockito.mock(Environment.class);
private ConfigServicePropertySourceLocator locator =
mock(ConfigServicePropertySourceLocator.class);
private Environment environment = mock(Environment.class);
private ConfigServerHealthIndicator indicator = new ConfigServerHealthIndicator(
locator, environment);
locator, environment, new ConfigClientHealthProperties());
@Test
public void testDefaultStatus() {
@@ -48,15 +53,32 @@ public class ConfigServerHealthIndicatorTests {
@Test
public void testExceptionStatus() {
Mockito.doThrow(new IllegalStateException()).when(locator).locate(Mockito.any(Environment.class));
doThrow(new IllegalStateException()).when(locator).locate(any(Environment.class));
assertEquals(Status.DOWN, indicator.health().getStatus());
verify(locator, times(1)).locate(any(Environment.class));
}
@Test
public void testServerUp() {
PropertySource<?> source = new MapPropertySource("foo", Collections.<String,Object>emptyMap());
Mockito.doReturn(source).when(locator).locate(Mockito.any(Environment.class));
doReturn(source).when(locator).locate(any(Environment.class));
assertEquals(Status.UP, indicator.health().getStatus());
verify(locator, times(1)).locate(any(Environment.class));
}
@Test
public void healthIsCached() {
PropertySource<?> source = new MapPropertySource("foo", Collections.<String,Object>emptyMap());
doReturn(source).when(locator).locate(any(Environment.class));
// not cached
assertEquals(Status.UP, indicator.health().getStatus());
// cached
assertEquals(Status.UP, indicator.health().getStatus());
verify(locator, times(1)).locate(any(Environment.class));
}
}

View File

@@ -5,45 +5,35 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-config-dependencies</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-cloud-config-dependencies</name>
<description>Spring Cloud Config Dependencies</description>
<properties>
<spring-cloud-commons.version>1.1.2.BUILD-SNAPSHOT</spring-cloud-commons.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons-dependencies</artifactId>
<version>${spring-cloud-commons.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-config-monitor</artifactId>
@@ -13,7 +13,7 @@
<description>Spring Cloud Config Monitor</description>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<spring-cloud-bus.version>1.2.0.BUILD-SNAPSHOT</spring-cloud-bus.version>
<spring-cloud-bus.version>1.2.2.BUILD-SNAPSHOT</spring-cloud-bus.version>
</properties>
<dependencyManagement>
<dependencies>

View File

@@ -36,7 +36,8 @@ public class BitbucketPropertyPathNotificationExtractor
@Override
public PropertyPathNotification extract(MultiValueMap<String, String> headers,
Map<String, Object> request) {
if ("repo:push".equals(headers.getFirst("X-Event-Key")) &&
if (("repo:push".equals(headers.getFirst("X-Event-Key")) ||
"pullrequest:fulfilled".equals(headers.getFirst("X-Event-Key"))) &&
StringUtils.hasText(headers.getFirst("X-Hook-UUID"))) {
Object push = request.get("push");
if (push instanceof Map && ((Map<?,?>)push).get("changes") instanceof Collection) {

View File

@@ -27,6 +27,7 @@ import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
* @author Will Boyd
*
*/
@Configuration
@@ -42,22 +43,27 @@ public class EnvironmentMonitorAutoConfiguration {
return new PropertyPathEndpoint(new CompositePropertyPathNotificationExtractor(this.extractors));
}
@Bean
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.github.enabled", havingValue="true", matchIfMissing=true)
public GithubPropertyPathNotificationExtractor githubPropertyPathNotificationExtractor() {
return new GithubPropertyPathNotificationExtractor();
}
@Configuration
protected static class PropertyPathNotificationExtractorConfiguration {
@Bean
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.gitlab.enabled", havingValue="true", matchIfMissing=true)
public GitlabPropertyPathNotificationExtractor gitlabPropertyPathNotificationExtractor() {
return new GitlabPropertyPathNotificationExtractor();
}
@Bean
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.github.enabled", havingValue="true", matchIfMissing=true)
public GithubPropertyPathNotificationExtractor githubPropertyPathNotificationExtractor() {
return new GithubPropertyPathNotificationExtractor();
}
@Bean
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.gitlab.enabled", havingValue="true", matchIfMissing=true)
public GitlabPropertyPathNotificationExtractor gitlabPropertyPathNotificationExtractor() {
return new GitlabPropertyPathNotificationExtractor();
}
@Bean
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.bitbucket.enabled", havingValue="true", matchIfMissing=true)
public BitbucketPropertyPathNotificationExtractor bitbucketPropertyPathNotificationExtractor() {
return new BitbucketPropertyPathNotificationExtractor();
}
@Bean
@ConditionalOnProperty(value="spring.cloud.config.server.monitor.bitbucket.enabled", havingValue="true", matchIfMissing=true)
public BitbucketPropertyPathNotificationExtractor bitbucketPropertyPathNotificationExtractor() {
return new BitbucketPropertyPathNotificationExtractor();
}
}

View File

@@ -200,7 +200,7 @@ public class FileMonitorConfiguration implements SmartLifecycle, ResourceLoaderA
try {
paths.add(Paths.get(resource.getURI()));
}
catch (IOException e) {
catch (Exception e) {
log.error("Cannot resolve URI for path: " + path);
}
}
@@ -301,8 +301,14 @@ public class FileMonitorConfiguration implements SmartLifecycle, ResourceLoaderA
if (log.isDebugEnabled()) {
log.debug("registering: " + dir + " for file creation events");
}
try {
dir.register(this.watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Cannot register watcher for " + dir, e);
}
}
}

View File

@@ -50,6 +50,16 @@ public class BitbucketPropertyPathNotificationExtractorTests {
assertNotNull(extracted);
assertEquals("application.yml", extracted.getPaths()[0]);
}
@Test
public void bitbucketPullRequestFulfillmentDetected() throws Exception {
// https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html#EventPayloads-Merged
Map<String, Object> value = readPayload("bitbucket.json");
setHeaders("pullrequest:fulfilled");
PropertyPathNotification extracted = this.extractor.extract(this.headers, value);
assertNotNull(extracted);
assertEquals("application.yml", extracted.getPaths()[0]);
}
private void setHeaders(String eventKey) {
this.headers.set("X-Event-Key", eventKey);
@@ -57,7 +67,7 @@ public class BitbucketPropertyPathNotificationExtractorTests {
}
@Test
public void notAPushNotDetected() throws Exception {
public void notAPushOrPullRequestNotDetected() throws Exception {
assertNotExtracted("bitbucket.json", "issue:created");
}

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.config.monitor;
import static org.junit.Assert.assertEquals;
import java.util.Collection;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
@@ -26,7 +27,10 @@ import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoCo
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.MultiValueMap;
/**
* @author Dave Syer
@@ -48,5 +52,34 @@ public class EnvironmentMonitorAutoConfigurationTests {
"extractors")).size());
context.close();
}
@Test
public void testCanAddCustomPropertyPathNotificationExtractor() {
ConfigurableApplicationContext context = new SpringApplicationBuilder(
CustomPropertyPathNotificationExtractorConfig.class,
EnvironmentMonitorAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, ServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class).properties("server.port=-1")
.run();
PropertyPathEndpoint endpoint = context.getBean(PropertyPathEndpoint.class);
assertEquals(5,
((Collection<?>) ReflectionTestUtils.getField(
ReflectionTestUtils.getField(endpoint, "extractor"),
"extractors")).size());
context.close();
}
@Configuration
static class CustomPropertyPathNotificationExtractorConfig {
@Bean
public PropertyPathNotificationExtractor customNotificationExtractor() {
return new PropertyPathNotificationExtractor() {
@Override
public PropertyPathNotification extract(MultiValueMap<String, String> headers, Map<String, Object> payload) {
throw new UnsupportedOperationException("doesn't do anything");
}
};
}
}
}

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -34,7 +34,7 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EnvironmentRepositoryConfiguration.class, ResourceRepositoryConfiguration.class,
@Import({ ResourceRepositoryConfiguration.class, EnvironmentRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public @interface EnableConfigServer {

View File

@@ -80,4 +80,4 @@ public class ConfigServerMvcConfiguration extends WebMvcConfigurerAdapter {
encrypted.setOverrides(this.server.getOverrides());
return encrypted;
}
}
}

View File

@@ -1,133 +1,133 @@
/*
* 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.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.environment.ConsulEnvironmentWatch;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentWatch;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.SvnKitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.client.RestTemplate;
/**
* @author Dave Syer
*
*/
@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
public class EnvironmentRepositoryConfiguration {
@Bean
@ConditionalOnProperty(value = "spring.cloud.config.server.health.enabled", matchIfMissing = true)
public ConfigServerHealthIndicator configServerHealthIndicator(EnvironmentRepository repository) {
return new ConfigServerHealthIndicator(repository);
}
@Configuration
@Profile("native")
protected static class NativeRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public EnvironmentRepository environmentRepository() {
return new NativeEnvironmentRepository(this.environment);
}
}
@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
protected static class GitRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Bean
public EnvironmentRepository environmentRepository() {
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
@Configuration
@Profile("subversion")
protected static class SvnRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Bean
public EnvironmentRepository environmentRepository() {
SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
@Configuration
@Profile("vault")
protected static class VaultConfiguration {
@Bean
public EnvironmentRepository environmentRepository(HttpServletRequest request, EnvironmentWatch watch) {
return new VaultEnvironmentRepository(request, watch, new RestTemplate());
}
}
@Configuration
@ConditionalOnProperty(value = "spring.cloud.config.server.consul.watch.enabled")
protected static class ConsulEnvironmentWatchConfiguration {
@Bean
public EnvironmentWatch environmentWatch() {
return new ConsulEnvironmentWatch();
}
}
@Configuration
@ConditionalOnMissingBean(EnvironmentWatch.class)
protected static class DefaultEnvironmentWatch {
@Bean
public EnvironmentWatch environmentWatch() {
return new EnvironmentWatch.Default();
}
}
/*
* 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.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.environment.ConsulEnvironmentWatch;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentWatch;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.SvnKitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.client.RestTemplate;
/**
* @author Dave Syer
*
*/
@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
public class EnvironmentRepositoryConfiguration {
@Bean
@ConditionalOnProperty(value = "spring.cloud.config.server.health.enabled", matchIfMissing = true)
public ConfigServerHealthIndicator configServerHealthIndicator(EnvironmentRepository repository) {
return new ConfigServerHealthIndicator(repository);
}
@Configuration
@Profile("native")
protected static class NativeRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public NativeEnvironmentRepository environmentRepository() {
return new NativeEnvironmentRepository(this.environment);
}
}
@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
protected static class GitRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Bean
public MultipleJGitEnvironmentRepository environmentRepository() {
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
@Configuration
@Profile("subversion")
protected static class SvnRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Autowired
private ConfigServerProperties server;
@Bean
public SvnKitEnvironmentRepository environmentRepository() {
SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
@Configuration
@Profile("vault")
protected static class VaultConfiguration {
@Bean
public EnvironmentRepository environmentRepository(HttpServletRequest request, EnvironmentWatch watch) {
return new VaultEnvironmentRepository(request, watch, new RestTemplate());
}
}
@Configuration
@ConditionalOnProperty(value = "spring.cloud.config.server.consul.watch.enabled")
protected static class ConsulEnvironmentWatchConfiguration {
@Bean
public EnvironmentWatch environmentWatch() {
return new ConsulEnvironmentWatch();
}
}
@Configuration
@ConditionalOnMissingBean(EnvironmentWatch.class)
protected static class DefaultEnvironmentWatch {
@Bean
public EnvironmentWatch environmentWatch() {
return new EnvironmentWatch.Default();
}
}
}

View File

@@ -1,41 +1,41 @@
/*
* 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.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.environment.SearchPathLocator;
import org.springframework.cloud.config.server.resource.GenericResourceRepository;
import org.springframework.cloud.config.server.resource.ResourceRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*
*/
@Configuration
@ConditionalOnMissingBean(ResourceRepository.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
public class ResourceRepositoryConfiguration {
@Bean
@ConditionalOnBean(SearchPathLocator.class)
public ResourceRepository resourceRepository(SearchPathLocator service) {
return new GenericResourceRepository(service);
}
/*
* 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.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.environment.SearchPathLocator;
import org.springframework.cloud.config.server.resource.GenericResourceRepository;
import org.springframework.cloud.config.server.resource.ResourceRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*
*/
@Configuration
@EnableConfigurationProperties(ConfigServerProperties.class)
@ConditionalOnMissingBean(ResourceRepository.class)
public class ResourceRepositoryConfiguration {
@Bean
@ConditionalOnBean(SearchPathLocator.class)
public ResourceRepository resourceRepository(SearchPathLocator service) {
return new GenericResourceRepository(service);
}
}

View File

@@ -1,38 +1,38 @@
/*
* 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.environment;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
/**
* @author Dave Syer
* @author Michael Prankl
*/
public class EnvironmentCleaner {
public Environment clean(Environment value, String workingDir, String uri) {
Environment result = new Environment(value);
for (PropertySource source : value.getPropertySources()) {
String name = source.getName().replace(workingDir, "");
name = name.replace("applicationConfig: [", "");
name = uri + "/" + name.replace("]", "");
result.add(new PropertySource(name, source.getSource()));
}
return result;
}
/*
* 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.environment;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
/**
* @author Dave Syer
* @author Michael Prankl
*/
public class EnvironmentCleaner {
public Environment clean(Environment value, String workingDir, String uri) {
Environment result = new Environment(value);
for (PropertySource source : value.getPropertySources()) {
String name = source.getName().replace(workingDir, "");
name = name.replace("applicationConfig: [", "");
name = uri + "/" + name.replace("]", "");
result.add(new PropertySource(name, source.getSource()));
}
return result;
}
}

View File

@@ -152,7 +152,7 @@ public class EnvironmentController {
throws Exception {
validateProfiles(profiles);
Environment environment = labelled(name, profiles, label);
Map<String, Object> properties = convertToMap(environment);
Map<String, Object> properties = convertToMap(environment, resolvePlaceholders);
String json = this.objectMapper.writeValueAsString(properties);
if (resolvePlaceholders) {
json = resolvePlaceholders(prepareEnvironment(environment), json);
@@ -188,7 +188,7 @@ public class EnvironmentController {
throws Exception {
validateProfiles(profiles);
Environment environment = labelled(name, profiles, label);
Map<String, Object> result = convertToMap(environment);
Map<String, Object> result = convertToMap(environment, resolvePlaceholders);
if (this.stripDocument && result.size() == 1
&& result.keySet().iterator().next().equals("document")) {
Object value = result.get("document");
@@ -208,10 +208,13 @@ public class EnvironmentController {
return getSuccess(yaml);
}
private Map<String, Object> convertToMap(Environment input) throws BindException {
private Map<String, Object> convertToMap(Environment input, boolean resolvePlaceholders) throws BindException {
Map<String, Object> target = new LinkedHashMap<>();
PropertiesConfigurationFactory<Map<String, Object>> factory = new PropertiesConfigurationFactory<>(
target);
if (!resolvePlaceholders) {
factory.setResolvePlaceholders(false);
}
Map<String, Object> data = convertToProperties(input);
LinkedHashMap<String, Object> properties = new LinkedHashMap<>();
for (String key : data.keySet()) {

View File

@@ -1,196 +1,196 @@
/*
* Copyright 2013-2015 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.environment;
import java.io.File;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNAuthenticationManager;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc2.SvnCheckout;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.core.wc2.SvnUpdate;
import static org.springframework.util.StringUtils.hasText;
/**
* Subversion-backed {@link EnvironmentRepository}.
*
* @author Michael Prankl
* @author Roy Clarkson
*/
@ConfigurationProperties("spring.cloud.config.server.svn")
public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepository
implements EnvironmentRepository, InitializingBean {
private static Log logger = LogFactory.getLog(SvnKitEnvironmentRepository.class);
private static final String DEFAULT_LABEL = "trunk";
/**
* The default label for environment properties requests.
*/
private String defaultLabel = DEFAULT_LABEL;
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
@Override
public synchronized Locations getLocations(String application, String profile,
String label) {
if (label == null) {
label = this.defaultLabel;
}
SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
if (hasText(getUsername())) {
svnOperationFactory
.setAuthenticationManager(new DefaultSVNAuthenticationManager(null,
false, getUsername(), getPassword()));
}
try {
String version;
if (new File(getWorkingDirectory(), ".svn").exists()) {
version = update(svnOperationFactory, label);
}
else {
version = checkout(svnOperationFactory);
}
return new Locations(application, profile, label, version,
getPaths(application, profile, label));
}
catch (SVNException e) {
throw new IllegalStateException("Cannot checkout repository", e);
}
finally {
svnOperationFactory.dispose();
}
}
private String[] getPaths(String application, String profile, String label) {
String[] locations = getSearchLocations(getSvnPath(getWorkingDirectory(), label), application, profile, label);
boolean exists = false;
for (String location : locations) {
location = StringUtils.cleanPath(location);
URI locationUri = URI.create(location);
if (new File(locationUri).exists()) {
exists = true;
break;
}
}
if (!exists) {
throw new NoSuchLabelException("No label found for: " + label);
}
return locations;
}
private String checkout(SvnOperationFactory svnOperationFactory) throws SVNException {
logger.debug("Checking out " + getUri() + " to: "
+ getWorkingDirectory().getAbsolutePath());
final SvnCheckout checkout = svnOperationFactory.createCheckout();
checkout.setSource(SvnTarget.fromURL(SVNURL.parseURIEncoded(getUri())));
checkout.setSingleTarget(SvnTarget.fromFile(getWorkingDirectory()));
Long id = checkout.run();
if (id == null) {
return null;
}
return id.toString();
}
private String update(SvnOperationFactory svnOperationFactory, String label) throws SVNException {
logger.debug("Repo already checked out - updating instead.");
try {
final SvnUpdate update = svnOperationFactory.createUpdate();
update.setSingleTarget(SvnTarget.fromFile(getWorkingDirectory()));
long[] ids = update.run();
StringBuilder version = new StringBuilder();
for (long id : ids) {
if (version.length() > 0) {
version.append(",");
}
version.append(id);
}
return version.toString();
}
catch (Exception e) {
this.logger.warn("Could not update remote for " + label + " (current local="
+ getWorkingDirectory().getPath() + "), remote: " + this.getUri()
+ ")");
}
final SVNStatus status = SVNClientManager.newInstance().getStatusClient()
.doStatus(getWorkingDirectory(), false);
return status != null ? status.getRevision().toString() : null;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(getUri() != null,
"You need to configure a uri for the subversion repository (e.g. 'http://example.com/svn/')");
resolveRelativeFileUri();
}
private void resolveRelativeFileUri() {
if (getUri().startsWith("file:///./")) {
String path = getUri().substring(8);
String absolutePath = new File(path).getAbsolutePath();
setUri("file:///" + StringUtils.cleanPath(absolutePath));
}
}
public SvnKitEnvironmentRepository(ConfigurableEnvironment environment) {
super(environment);
}
@Override
protected File getWorkingDirectory() {
return this.getBasedir();
}
private File getSvnPath(File workingDirectory, String label) {
// use label as path relative to repository root
// if it doesn't exists check branches and then tags folders
File svnPath = new File(workingDirectory, label);
if(!svnPath.exists()) {
svnPath = new File(workingDirectory, "branches" + File.separator + label);
if(!svnPath.exists()) {
svnPath = new File(workingDirectory, "tags" + File.separator + label);
if(!svnPath.exists()) {
throw new NoSuchLabelException("No label found for: " + label);
}
}
}
return svnPath;
}
}
/*
* Copyright 2013-2015 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.environment;
import java.io.File;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNAuthenticationManager;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc2.SvnCheckout;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.core.wc2.SvnUpdate;
import static org.springframework.util.StringUtils.hasText;
/**
* Subversion-backed {@link EnvironmentRepository}.
*
* @author Michael Prankl
* @author Roy Clarkson
*/
@ConfigurationProperties("spring.cloud.config.server.svn")
public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepository
implements EnvironmentRepository, InitializingBean {
private static Log logger = LogFactory.getLog(SvnKitEnvironmentRepository.class);
private static final String DEFAULT_LABEL = "trunk";
/**
* The default label for environment properties requests.
*/
private String defaultLabel = DEFAULT_LABEL;
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
@Override
public synchronized Locations getLocations(String application, String profile,
String label) {
if (label == null) {
label = this.defaultLabel;
}
SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
if (hasText(getUsername())) {
svnOperationFactory
.setAuthenticationManager(new DefaultSVNAuthenticationManager(null,
false, getUsername(), getPassword()));
}
try {
String version;
if (new File(getWorkingDirectory(), ".svn").exists()) {
version = update(svnOperationFactory, label);
}
else {
version = checkout(svnOperationFactory);
}
return new Locations(application, profile, label, version,
getPaths(application, profile, label));
}
catch (SVNException e) {
throw new IllegalStateException("Cannot checkout repository", e);
}
finally {
svnOperationFactory.dispose();
}
}
private String[] getPaths(String application, String profile, String label) {
String[] locations = getSearchLocations(getSvnPath(getWorkingDirectory(), label), application, profile, label);
boolean exists = false;
for (String location : locations) {
location = StringUtils.cleanPath(location);
URI locationUri = URI.create(location);
if (new File(locationUri).exists()) {
exists = true;
break;
}
}
if (!exists) {
throw new NoSuchLabelException("No label found for: " + label);
}
return locations;
}
private String checkout(SvnOperationFactory svnOperationFactory) throws SVNException {
logger.debug("Checking out " + getUri() + " to: "
+ getWorkingDirectory().getAbsolutePath());
final SvnCheckout checkout = svnOperationFactory.createCheckout();
checkout.setSource(SvnTarget.fromURL(SVNURL.parseURIEncoded(getUri())));
checkout.setSingleTarget(SvnTarget.fromFile(getWorkingDirectory()));
Long id = checkout.run();
if (id == null) {
return null;
}
return id.toString();
}
private String update(SvnOperationFactory svnOperationFactory, String label) throws SVNException {
logger.debug("Repo already checked out - updating instead.");
try {
final SvnUpdate update = svnOperationFactory.createUpdate();
update.setSingleTarget(SvnTarget.fromFile(getWorkingDirectory()));
long[] ids = update.run();
StringBuilder version = new StringBuilder();
for (long id : ids) {
if (version.length() > 0) {
version.append(",");
}
version.append(id);
}
return version.toString();
}
catch (Exception e) {
this.logger.warn("Could not update remote for " + label + " (current local="
+ getWorkingDirectory().getPath() + "), remote: " + this.getUri()
+ ")");
}
final SVNStatus status = SVNClientManager.newInstance().getStatusClient()
.doStatus(getWorkingDirectory(), false);
return status != null ? status.getRevision().toString() : null;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(getUri() != null,
"You need to configure a uri for the subversion repository (e.g. 'http://example.com/svn/')");
resolveRelativeFileUri();
}
private void resolveRelativeFileUri() {
if (getUri().startsWith("file:///./")) {
String path = getUri().substring(8);
String absolutePath = new File(path).getAbsolutePath();
setUri("file:///" + StringUtils.cleanPath(absolutePath));
}
}
public SvnKitEnvironmentRepository(ConfigurableEnvironment environment) {
super(environment);
}
@Override
protected File getWorkingDirectory() {
return this.getBasedir();
}
private File getSvnPath(File workingDirectory, String label) {
// use label as path relative to repository root
// if it doesn't exists check branches and then tags folders
File svnPath = new File(workingDirectory, label);
if(!svnPath.exists()) {
svnPath = new File(workingDirectory, "branches" + File.separator + label);
if(!svnPath.exists()) {
svnPath = new File(workingDirectory, "tags" + File.separator + label);
if(!svnPath.exists()) {
throw new NoSuchLabelException("No label found for: " + label);
}
}
}
return svnPath;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2015 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.config;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.config.server.config.CustomEnvironmentRepositoryTests.TestApplication;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertFalse;
/**
* @author Dave Syer
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class, properties = { "server.port:0",
"spring.config.name:configserver" }, webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@DirtiesContext
public class CustomEnvironmentRepositoryTests {
@LocalServerPort
private int port;
@Test
public void contextLoads() {
Environment environment = new TestRestTemplate().getForObject(
"http://localhost:" + port + "/foo/development/", Environment.class);
assertFalse(environment.getPropertySources().isEmpty());
}
@Configuration
@EnableAutoConfiguration
@EnableConfigServer
protected static class TestApplication {
@Bean
public EnvironmentRepository environmentRepository() {
return new EnvironmentRepository() {
@Override
public Environment findOne(String application, String profile,
String label) {
return new Environment("test", new String[0], "label", "version",
"state");
}
};
}
public static void main(String[] args) throws Exception {
SpringApplication.run(CustomEnvironmentRepositoryTests.TestApplication.class,
args);
}
}
}

View File

@@ -138,7 +138,15 @@ public class EnvironmentControllerTests {
public void placeholdersNotResolvedInYamlFromSystemPropertiesWhenNotFlaggedWithDefault() throws Exception {
whenPlaceholdersSystemPropsWithDefault();
String yaml = this.controller.yaml("foo", "bar", false).getBody();
// If there is a default value we can't prevent the placeholder being resolved
// If there is a default value we prevent the placeholder being resolved
assertEquals("a:\n b:\n c: ${foo:spam}\n", yaml);
}
@Test
public void placeholdersResolvedInYamlFromSystemPropertiesWhenFlagged() throws Exception {
whenPlaceholdersSystemPropsWithDefault();
String yaml = this.controller.yaml("foo", "bar", true).getBody();
// If there is a default value we do not prevent the placeholder being resolved
assertEquals("a:\n b:\n c: spam\n", yaml);
}
@@ -335,10 +343,18 @@ public class EnvironmentControllerTests {
}
@Test
public void placeholdersResolvedInJsonFromSystemPropertiesWhenNotFlaggedWithDefault() throws Exception {
public void placeholdersNotResolvedInJsonFromSystemPropertiesWhenNotFlaggedWithDefault() throws Exception {
whenPlaceholdersSystemPropsWithDefault();
String json = this.controller.jsonProperties("foo", "bar", false).getBody();
// If there is a default value we can't prevent the placeholder being resolved
// If there is a default value we prevent the placeholder being resolved
assertEquals("{\"a\":{\"b\":{\"c\":\"${foo:spam}\"}}}", json);
}
@Test
public void placeholdersResolvedInJsonFromSystemPropertiesWhenFlagged() throws Exception {
whenPlaceholdersSystemPropsWithDefault();
String json = this.controller.jsonProperties("foo", "bar", true).getBody();
// If there is a default value we do not prevent the placeholder being resolved
assertEquals("{\"a\":{\"b\":{\"c\":\"spam\"}}}", json);
}
@@ -392,7 +408,6 @@ public class EnvironmentControllerTests {
mvc.perform(MockMvcRequestBuilders.get("/foo-bar.json"))
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.content().string("{}"));
;
}
@Test

View File

@@ -5,10 +5,10 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-starter-config</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
<version>1.2.2.BUILD-SNAPSHOT</version>
<name>spring-cloud-starter-config</name>
<description>Spring Cloud Starter</description>
<url>https://projects.spring.io/spring-cloud</url>