Add sample app

This commit is contained in:
Dave Syer
2014-06-14 19:53:25 +01:00
parent c4e71d8eea
commit 5bea0ccc0f
10 changed files with 243 additions and 40 deletions

View File

@@ -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.

22
pom.xml
View File

@@ -14,6 +14,11 @@
<version>1.1.2.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modules>
<module>spring-platform-config-client</module>
<module>spring-platform-config-server</module>
<module>spring-platform-config-sample</module>
</modules>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
@@ -67,21 +72,6 @@
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>spring-platform-config-client</module>
<module>spring-platform-config-server</module>
</modules>
</profile>
<profile>
<id>full</id>
</profile>
</profiles>
<dependencyManagement>
<dependencies>
<dependency>
@@ -91,7 +81,7 @@
</dependency>
<dependency>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config-client</artifactId>
<artifactId>spring-platform-config-server</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
</dependencies>

View File

@@ -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>((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() {

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-platform-config-sample</artifactId>
<packaging>jar</packaging>
<name>spring-platform-config-sample</name>
<description>spring-platform-config-server</description>
<parent>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.platform</groupId>
<artifactId>spring-platform-config-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>org.springframework.platform.config.server.Application</start-class>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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);
}
}

View File

@@ -0,0 +1,2 @@
info:
component: Config Sample

View File

@@ -0,0 +1,6 @@
spring:
application:
name: bar
platform:
config:
uri: http://localhost:${config.port:8888}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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());
}
}