diff --git a/README.md b/README.md
index 1ad23937..b7b0148e 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,15 @@
Spring Platform 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 the
-applications in your origanization, across all environments. The
-concepts on both sides map identically to the Spring `Environment` and
-`PropertySource` abstractions, so they fit very well with Spring
-applications. As an application moves through the deployment pipeline
-from dev to test and into production you can manage the configuration
-that needs to change between those environments and be certain that
-applications have everything they need to run when they migrate.
+applications across all environments. The concepts on both sides map
+identically to the Spring `Environment` and `PropertySource`
+abstractions, so they fit very well with Spring applications. As an
+application moves through the deployment pipeline from dev to test and
+into production you can manage the configuration that needs to change
+between those environments and be certain that applications have
+everything they need to run when they migrate. The default
+implementation of the server repository strategy uses git as a storage
+backend so it easily supports labelled versions of configurations.
## Quick Start
@@ -18,7 +20,9 @@ $ cd spring-platform-config-server
$ mvn spring-boot:run
```
-Then try it:
+The server is a Spring Boot application so you can build the jar file
+and run that (`java -jar ...`) or pull it down from a Maven
+repository. Then try it out as a client:
```
$ curl localhost:8888/foo/development
@@ -28,7 +32,7 @@ $ curl localhost:8888/foo/development
]}
```
-The default strategy for locating property sources is to clone a GIT
+The default strategy for locating property sources is to clone a git
repository (at "spring.platform.config.uri") and use it to initialize
a `SpringApplication`. The application's `Environment` is used to
enumerate property sources. The service has resources in the form:
@@ -38,17 +42,18 @@ enumerate property sources. The service has resources in the form:
```
where the "name" is used as the config name in the `SpringApplication`
-(i.e. what is normally "application"), "profile" is an active profile
-(or comma-separated list of properties), and "label" is an optional
-git label (defaults to "master").
+(i.e. what is normally "application" in a regular Spring Boot app),
+"profile" is an active profile (or comma-separated list of
+properties), and "label" is an optional git label (defaults to
+"master").
### Client Side Usage
Build a client (e.g. see the test cases for the config-client) as a
Spring Boot application that depends on spring-platform-config-client.
When it runs it will pick up the external configuration from the
-default local config server on port 8888. To modify the startup
-behaviour change the location of the server using
+default local config server on port 8888 if it is running. To modify
+the startup behaviour you can change the location of the server using
`bootstrap.properties` (like `application.properties` but for the
bootstrap phase of an application context), e.g.
@@ -56,4 +61,15 @@ bootstrap phase of an application context), e.g.
spring.platform.config.url: http://myconfigserver.com
```
-TODO: add a sample app.
+## Sample Application
+
+There is a sample application
+[here](https://github.com/spring-platform/spring-platform-config/spring-platform-config-sample). It
+is a Spring Boot application so you can run it using the usual
+mechanisms (for instance "mvn spring-boot:run"). When it runs it will
+look for the config server on "http://localhost:8888" by default, so
+you could run the server as well to see it all working together.
+
+The sample has a tets case where the config server is also started in
+the same JVM (with a different port), and the test asserts that an
+environment property from the git configuration repo is present.
diff --git a/pom.xml b/pom.xml
index ec5b4148..c551a6ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,11 @@
1.1.2.BUILD-SNAPSHOT
+
+ spring-platform-config-client
+ spring-platform-config-server
+ spring-platform-config-sample
+
Pivotal Software, Inc.
http://www.spring.io
@@ -67,21 +72,6 @@
-
-
- default
-
- true
-
-
- spring-platform-config-client
- spring-platform-config-server
-
-
-
- full
-
-
@@ -91,7 +81,7 @@
org.springframework.platform
- spring-platform-config-client
+ spring-platform-config-server
1.0.0.BUILD-SNAPSHOT
diff --git a/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/config/ConfigServicePropertySourceLocator.java b/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/config/ConfigServicePropertySourceLocator.java
index d330074c..c99e767b 100644
--- a/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/config/ConfigServicePropertySourceLocator.java
+++ b/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/config/ConfigServicePropertySourceLocator.java
@@ -38,14 +38,14 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator
private String label = "master";
- private String url = "http://localhost:8888";
+ private String uri = "http://localhost:8888";
private RestTemplate restTemplate = new RestTemplate();
@Override
public org.springframework.core.env.PropertySource> locate() {
CompositePropertySource composite = new CompositePropertySource("configService");
- Environment result = restTemplate.exchange(url + "/{name}/{env}/{label}",
+ Environment result = restTemplate.exchange(uri + "/{name}/{env}/{label}",
HttpMethod.GET, new HttpEntity((Void) null), Environment.class,
name, env, label).getBody();
for (PropertySource source : result.getPropertySources()) {
@@ -56,12 +56,12 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator
return composite;
}
- public String getUrl() {
- return url;
+ public String getUri() {
+ return uri;
}
- public void setUrl(String url) {
- this.url = url;
+ public void setUri(String url) {
+ this.uri = url;
}
public String getName() {
diff --git a/spring-platform-config-sample/pom.xml b/spring-platform-config-sample/pom.xml
new file mode 100644
index 00000000..93cf3b0e
--- /dev/null
+++ b/spring-platform-config-sample/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ spring-platform-config-sample
+ jar
+
+ spring-platform-config-sample
+ spring-platform-config-server
+
+
+ org.springframework.platform
+ spring-platform-config
+ 1.0.0.BUILD-SNAPSHOT
+ ..
+
+
+
+
+ org.springframework.platform
+ spring-platform-config-client
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.platform
+ spring-platform-config-server
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ UTF-8
+ org.springframework.platform.config.server.Application
+ 1.7
+
+
+
+
+
+
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-platform-config-sample/src/main/java/sample/Application.java b/spring-platform-config-sample/src/main/java/sample/Application.java
new file mode 100644
index 00000000..4ebda595
--- /dev/null
+++ b/spring-platform-config-sample/src/main/java/sample/Application.java
@@ -0,0 +1,16 @@
+
+package sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan
+@EnableAutoConfiguration
+public class Application {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/spring-platform-config-sample/src/main/resources/application.yml b/spring-platform-config-sample/src/main/resources/application.yml
new file mode 100644
index 00000000..3435b10b
--- /dev/null
+++ b/spring-platform-config-sample/src/main/resources/application.yml
@@ -0,0 +1,2 @@
+info:
+ component: Config Sample
diff --git a/spring-platform-config-sample/src/main/resources/bootstrap.yml b/spring-platform-config-sample/src/main/resources/bootstrap.yml
new file mode 100644
index 00000000..835348b9
--- /dev/null
+++ b/spring-platform-config-sample/src/main/resources/bootstrap.yml
@@ -0,0 +1,6 @@
+spring:
+ application:
+ name: bar
+ platform:
+ config:
+ uri: http://localhost:${config.port:8888}
\ No newline at end of file
diff --git a/spring-platform-config-sample/src/test/java/sample/ApplicationTests.java b/spring-platform-config-sample/src/test/java/sample/ApplicationTests.java
new file mode 100644
index 00000000..e6d3ef3c
--- /dev/null
+++ b/spring-platform-config-sample/src/test/java/sample/ApplicationTests.java
@@ -0,0 +1,42 @@
+package sample;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
+import org.springframework.boot.test.IntegrationTest;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.boot.test.TestRestTemplate;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = Application.class)
+@IntegrationTest("server.port:0")
+@WebAppConfiguration
+public class ApplicationTests {
+
+ private static int configPort;
+
+ @Value("${local.server.port}")
+ private int port;
+
+ @BeforeClass
+ public static void startConfigServer() {
+ ConfigurableApplicationContext context = SpringApplication.run(org.springframework.platform.config.server.Application.class, "--server.port=0");
+ configPort = ((EmbeddedWebApplicationContext)context).getEmbeddedServletContainer().getPort();
+ System.setProperty("config.port", "" + configPort);
+ }
+
+ @Test
+ public void contextLoads() {
+ String foo = new TestRestTemplate().getForObject("http://localhost:" + port + "/env/foo", String.class);
+ assertEquals("bar", foo);
+ }
+
+}
diff --git a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/JGitEnvironmentRepository.java b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/JGitEnvironmentRepository.java
index a27d0900..a016dadb 100644
--- a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/JGitEnvironmentRepository.java
+++ b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/JGitEnvironmentRepository.java
@@ -20,9 +20,13 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.util.FileUtils;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.bootstrap.config.PropertySource;
+import org.springframework.util.Assert;
/**
* @author Dave Syer
@@ -31,6 +35,8 @@ import org.springframework.platform.bootstrap.config.PropertySource;
public class JGitEnvironmentRepository implements EnvironmentRepository {
public static final String DEFAULT_URI = "https://github.com/scratches/config-repo";
+
+ private static Log logger = LogFactory.getLog(JGitEnvironmentRepository.class);
private File basedir;
@@ -38,8 +44,18 @@ public class JGitEnvironmentRepository implements EnvironmentRepository {
public JGitEnvironmentRepository() {
try {
- basedir = Files.createTempDirectory("config-repo-").toFile();
- basedir.deleteOnExit();
+ final File basedir = Files.createTempDirectory("config-repo-").toFile();
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ try {
+ FileUtils.delete(basedir, FileUtils.RECURSIVE);
+ } catch (IOException e) {
+ logger.warn("Failed to delete temporary directory on exit: " + e);
+ }
+ }
+ });
+ this.basedir = basedir;
} catch (IOException e) {
throw new IllegalStateException("Cannot create temp dir", e);
}
@@ -51,6 +67,11 @@ public class JGitEnvironmentRepository implements EnvironmentRepository {
}
this.uri = uri;
}
+
+
+ public void setBasedir(File basedir) {
+ this.basedir = basedir;
+ }
@Override
public Environment findOne(String application, String name, String label) {
@@ -59,6 +80,14 @@ public class JGitEnvironmentRepository implements EnvironmentRepository {
if (new File(basedir, ".git").exists()) {
git = Git.open(basedir);
} else {
+ if (basedir.exists()) {
+ try {
+ FileUtils.delete(basedir, FileUtils.RECURSIVE);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to initialize base directory", e);
+ }
+ }
+ Assert.state(basedir.mkdirs(), "Could not create basedir: " + basedir);
git = Git.cloneRepository().setURI(uri).setDirectory(basedir).call();
}
Environment result;
diff --git a/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/JGitEnvironmentRepositoryTests.java b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/JGitEnvironmentRepositoryTests.java
index bdccd8a5..18ebbb7a 100644
--- a/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/JGitEnvironmentRepositoryTests.java
+++ b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/JGitEnvironmentRepositoryTests.java
@@ -16,8 +16,13 @@
package org.springframework.platform.config.server;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.io.File;
+
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Before;
import org.junit.Test;
import org.springframework.platform.bootstrap.config.Environment;
@@ -29,6 +34,15 @@ public class JGitEnvironmentRepositoryTests {
private JGitEnvironmentRepository repository = new JGitEnvironmentRepository();
+ private File basedir = new File("target/config-repo");
+
+ @Before
+ public void init() throws Exception {
+ if (basedir.exists()) {
+ FileUtils.delete(basedir, FileUtils.RECURSIVE);
+ }
+ }
+
@Test
public void vanilla() {
repository.findOne("bar", "staging", "master");
@@ -38,4 +52,26 @@ public class JGitEnvironmentRepositoryTests {
environment.getPropertySources().get(0).getName());
}
+ @Test
+ public void basedir() {
+ repository.setBasedir(basedir);
+ repository.findOne("bar", "staging", "master");
+ Environment environment = repository.findOne("bar", "staging", "master");
+ assertEquals(1, environment.getPropertySources().size());
+ assertEquals(JGitEnvironmentRepository.DEFAULT_URI + "/bar.properties",
+ environment.getPropertySources().get(0).getName());
+ }
+
+ @Test
+ public void basedirExists() throws Exception {
+ assertTrue(basedir.mkdirs());
+ assertTrue(new File(basedir, ".nothing").createNewFile());
+ repository.setBasedir(basedir);
+ repository.findOne("bar", "staging", "master");
+ Environment environment = repository.findOne("bar", "staging", "master");
+ assertEquals(1, environment.getPropertySources().size());
+ assertEquals(JGitEnvironmentRepository.DEFAULT_URI + "/bar.properties",
+ environment.getPropertySources().get(0).getName());
+ }
+
}