diff --git a/README.adoc b/README.adoc index 5cab0c5d..1286672a 100644 --- a/README.adoc +++ b/README.adoc @@ -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. diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..d2f65f74 --- /dev/null +++ b/circle.yml @@ -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 \ No newline at end of file diff --git a/docs/pom.xml b/docs/pom.xml index 332db6b4..708b34fc 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT pom Spring Cloud Config Docs diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 5db5a965..d143627a 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -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). diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 063f1925..9bf6682f 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -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 <> 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 diff --git a/mvnw b/mvnw index fe1957e3..0a7dac22 100755 --- a/mvnw +++ b/mvnw @@ -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 '\!//' -e 's!.*$!!' ) +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" \ diff --git a/pom.xml b/pom.xml index b517a342..798ecb33 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT pom Spring Cloud Config Spring Cloud Config @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-build - 1.2.0.RELEASE + 1.2.2.BUILD-SNAPSHOT @@ -22,6 +22,7 @@ config + 1.1.5.BUILD-SNAPSHOT spring-cloud-config-dependencies @@ -41,6 +42,13 @@ pom import + + org.springframework.cloud + spring-cloud-commons-dependencies + ${spring-cloud-commons.version} + pom + import + @@ -105,5 +113,47 @@ + + sonar + + + + org.jacoco + jacoco-maven-plugin + + + pre-unit-test + + prepare-agent + + + surefireArgLine + ${project.build.directory}/jacoco.exec + + + + post-unit-test + test + + report + + + + ${project.build.directory}/jacoco.exec + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${surefireArgLine} + + + + + diff --git a/spring-cloud-config-client/pom.xml b/spring-cloud-config-client/pom.xml index c91d7a2e..7323d870 100644 --- a/spring-cloud-config-client/pom.xml +++ b/spring-cloud-config-client/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java index 7e6f7e9b..ed75bac1 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java @@ -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); } } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientHealthProperties.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientHealthProperties.java new file mode 100644 index 00000000..717b963e --- /dev/null +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientHealthProperties.java @@ -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; + } +} diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java index 31932cba..b77b1a18 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java @@ -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 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(); + } + } diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java index 321d9891..3593e3fd 100644 --- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java +++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java @@ -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.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.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)); + } + + } diff --git a/spring-cloud-config-dependencies/pom.xml b/spring-cloud-config-dependencies/pom.xml index e2f10172..f07f6a90 100644 --- a/spring-cloud-config-dependencies/pom.xml +++ b/spring-cloud-config-dependencies/pom.xml @@ -5,45 +5,35 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT spring-cloud-config-dependencies - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT pom spring-cloud-config-dependencies Spring Cloud Config Dependencies - - 1.1.2.BUILD-SNAPSHOT - - - org.springframework.cloud - spring-cloud-commons-dependencies - ${spring-cloud-commons.version} - pom - import - org.springframework.cloud spring-cloud-starter-config - 1.2.0.BUILD-SNAPSHOT + ${project.version} org.springframework.cloud spring-cloud-config-client - 1.2.0.BUILD-SNAPSHOT + ${project.version} org.springframework.cloud spring-cloud-config-server - 1.2.0.BUILD-SNAPSHOT + ${project.version} org.springframework.cloud spring-cloud-config-monitor - 1.2.0.BUILD-SNAPSHOT + ${project.version} org.eclipse.jgit diff --git a/spring-cloud-config-monitor/pom.xml b/spring-cloud-config-monitor/pom.xml index c102e099..f7466b8f 100644 --- a/spring-cloud-config-monitor/pom.xml +++ b/spring-cloud-config-monitor/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT .. spring-cloud-config-monitor @@ -13,7 +13,7 @@ Spring Cloud Config Monitor ${basedir}/../.. - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT diff --git a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractor.java b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractor.java index d571a40a..a21b6929 100644 --- a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractor.java +++ b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractor.java @@ -36,7 +36,8 @@ public class BitbucketPropertyPathNotificationExtractor @Override public PropertyPathNotification extract(MultiValueMap headers, Map 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) { diff --git a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfiguration.java b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfiguration.java index d9238c7d..510621f6 100644 --- a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfiguration.java +++ b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfiguration.java @@ -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(); } } diff --git a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java index 9a6e7c9e..33afb6a2 100644 --- a/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java +++ b/spring-cloud-config-monitor/src/main/java/org/springframework/cloud/config/monitor/FileMonitorConfiguration.java @@ -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); + } } } diff --git a/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractorTests.java b/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractorTests.java index 2ff35dc6..3cc6c6be 100644 --- a/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractorTests.java +++ b/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/BitbucketPropertyPathNotificationExtractorTests.java @@ -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 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"); } diff --git a/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfigurationTests.java b/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfigurationTests.java index 28e2b22a..f9e76597 100644 --- a/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfigurationTests.java +++ b/spring-cloud-config-monitor/src/test/java/org/springframework/cloud/config/monitor/EnvironmentMonitorAutoConfigurationTests.java @@ -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 headers, Map payload) { + throw new UnsupportedOperationException("doesn't do anything"); + } + }; + } + } } diff --git a/spring-cloud-config-sample/pom.xml b/spring-cloud-config-sample/pom.xml index fd8f2e69..79f773fa 100644 --- a/spring-cloud-config-sample/pom.xml +++ b/spring-cloud-config-sample/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-config-server/pom.xml b/spring-cloud-config-server/pom.xml index 666e3f81..4c399c6e 100644 --- a/spring-cloud-config-server/pom.xml +++ b/spring-cloud-config-server/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT .. diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java index 829ec23d..9443e6b9 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnableConfigServer.java @@ -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 { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java index 19c27209..8f7743e8 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerMvcConfiguration.java @@ -80,4 +80,4 @@ public class ConfigServerMvcConfiguration extends WebMvcConfigurerAdapter { encrypted.setOverrides(this.server.getOverrides()); return encrypted; } -} \ No newline at end of file +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java index 3638f0b7..7590f1a8 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java @@ -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(); + } + } } \ No newline at end of file diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java index 182694d7..4498c772 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java @@ -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); + } } \ No newline at end of file diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentCleaner.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentCleaner.java index 9cb144f6..192077cd 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentCleaner.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentCleaner.java @@ -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; + } + } \ No newline at end of file diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java index 1f86c61b..2e27743c 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java @@ -152,7 +152,7 @@ public class EnvironmentController { throws Exception { validateProfiles(profiles); Environment environment = labelled(name, profiles, label); - Map properties = convertToMap(environment); + Map 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 result = convertToMap(environment); + Map 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 convertToMap(Environment input) throws BindException { + private Map convertToMap(Environment input, boolean resolvePlaceholders) throws BindException { Map target = new LinkedHashMap<>(); PropertiesConfigurationFactory> factory = new PropertiesConfigurationFactory<>( target); + if (!resolvePlaceholders) { + factory.setResolvePlaceholders(false); + } Map data = convertToProperties(input); LinkedHashMap properties = new LinkedHashMap<>(); for (String key : data.keySet()) { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java index 41d66dd0..63707ba0 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java @@ -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; + } + +} diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/CustomEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/CustomEnvironmentRepositoryTests.java new file mode 100644 index 00000000..a5b5b89f --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/CustomEnvironmentRepositoryTests.java @@ -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); + } + + } + +} diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java index d429e7c9..98fabb77 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java @@ -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 diff --git a/spring-cloud-starter-config/pom.xml b/spring-cloud-starter-config/pom.xml index f2f6447a..e24d77f6 100644 --- a/spring-cloud-starter-config/pom.xml +++ b/spring-cloud-starter-config/pom.xml @@ -5,10 +5,10 @@ org.springframework.cloud spring-cloud-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT spring-cloud-starter-config - 1.2.0.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT spring-cloud-starter-config Spring Cloud Starter https://projects.spring.io/spring-cloud