From 5bea0ccc0f331e9c4c9695824804cd92c2137a9e Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Sat, 14 Jun 2014 19:53:25 +0100 Subject: [PATCH] Add sample app --- README.md | 46 ++++++++----- pom.xml | 22 ++----- .../ConfigServicePropertySourceLocator.java | 12 ++-- spring-platform-config-sample/pom.xml | 66 +++++++++++++++++++ .../src/main/java/sample/Application.java | 16 +++++ .../src/main/resources/application.yml | 2 + .../src/main/resources/bootstrap.yml | 6 ++ .../test/java/sample/ApplicationTests.java | 42 ++++++++++++ .../server/JGitEnvironmentRepository.java | 33 +++++++++- .../JGitEnvironmentRepositoryTests.java | 38 ++++++++++- 10 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 spring-platform-config-sample/pom.xml create mode 100644 spring-platform-config-sample/src/main/java/sample/Application.java create mode 100644 spring-platform-config-sample/src/main/resources/application.yml create mode 100644 spring-platform-config-sample/src/main/resources/bootstrap.yml create mode 100644 spring-platform-config-sample/src/test/java/sample/ApplicationTests.java 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()); + } + }