From 5dbf017d99d1310d031255d86f3f38c0d5abf210 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Sat, 14 Jun 2014 14:19:35 +0100 Subject: [PATCH] Add GIT repository support Default to a scratch repository on github: $ curl localhost:8888/foo/development {"name":"development","label":"master","propertySources":[ {"name":"https://github.com/scratches/config-repo/foo-development.properties","source":{"bar":"spam"}}, {"name":"https://github.com/scratches/config-repo/foo.properties","source":{"foo":"bar"}} ]} --- .../BootstrapApplicationListener.java | 3 + .../platform/config/server/Application.java | 11 +++ .../server/JGitEnvironmentRepository.java | 89 +++++++++++++++++++ .../server/NativeEnvironmentRepository.java | 11 ++- ...pringApplicationEnvironmentRepository.java | 79 ++++++++++++++++ .../config/server/ApplicationTests.java | 14 ++- .../JGitEnvironmentRepositoryTests.java | 41 +++++++++ ...ApplicationEnvironmentRepositoryTests.java | 52 +++++++++++ .../test/resources/foo-development.properties | 1 + .../src/test/resources/foo.properties | 1 + .../src/test/resources/test/foo.properties | 1 + 11 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 spring-platform-config-server/src/main/java/org/springframework/platform/config/server/JGitEnvironmentRepository.java create mode 100644 spring-platform-config-server/src/main/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepository.java create mode 100644 spring-platform-config-server/src/test/java/org/springframework/platform/config/server/JGitEnvironmentRepositoryTests.java create mode 100644 spring-platform-config-server/src/test/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepositoryTests.java create mode 100644 spring-platform-config-server/src/test/resources/foo-development.properties create mode 100644 spring-platform-config-server/src/test/resources/foo.properties create mode 100644 spring-platform-config-server/src/test/resources/test/foo.properties diff --git a/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/BootstrapApplicationListener.java b/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/BootstrapApplicationListener.java index 0aff9bad..50d8d709 100644 --- a/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/BootstrapApplicationListener.java +++ b/spring-platform-config-client/src/main/java/org/springframework/platform/bootstrap/BootstrapApplicationListener.java @@ -59,6 +59,9 @@ public class BootstrapApplicationListener implements @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { Environment environment = event.getEnvironment(); + if (!environment.getProperty("spring.platform.bootstrap.enabled", Boolean.class, true)) { + return; + } if (environment instanceof ConfigurableEnvironment) { ConfigurableEnvironment configurable = (ConfigurableEnvironment) environment; // don't listen to events in a bootstrap context diff --git a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/Application.java b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/Application.java index ff15eaa4..8f926b55 100644 --- a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/Application.java +++ b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/Application.java @@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.platform.bootstrap.config.Environment; import org.springframework.web.bind.annotation.PathVariable; @@ -37,6 +38,7 @@ public class Application { } @Configuration + @Profile("native") protected static class NativeRepositoryConfiguration { @Autowired private ConfigurableEnvironment environment; @@ -46,4 +48,13 @@ public class Application { return new NativeEnvironmentRepository(environment); } } + + @Configuration + @Profile("!native") + protected static class GitRepositoryConfiguration { + @Bean + public JGitEnvironmentRepository repository() { + return new JGitEnvironmentRepository(); + } + } } 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 new file mode 100644 index 00000000..a27d0900 --- /dev/null +++ b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/JGitEnvironmentRepository.java @@ -0,0 +1,89 @@ +/* + * 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.platform.config.server; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.eclipse.jgit.api.Git; +import org.springframework.platform.bootstrap.config.Environment; +import org.springframework.platform.bootstrap.config.PropertySource; + +/** + * @author Dave Syer + * + */ +public class JGitEnvironmentRepository implements EnvironmentRepository { + + public static final String DEFAULT_URI = "https://github.com/scratches/config-repo"; + + private File basedir; + + private String uri = DEFAULT_URI; + + public JGitEnvironmentRepository() { + try { + basedir = Files.createTempDirectory("config-repo-").toFile(); + basedir.deleteOnExit(); + } catch (IOException e) { + throw new IllegalStateException("Cannot create temp dir", e); + } + } + + public void setUri(String uri) { + while (uri.endsWith("/")) { + uri = uri.substring(0, uri.length() - 1); + } + this.uri = uri; + } + + @Override + public Environment findOne(String application, String name, String label) { + try { + Git git; + if (new File(basedir, ".git").exists()) { + git = Git.open(basedir); + } else { + git = Git.cloneRepository().setURI(uri).setDirectory(basedir).call(); + } + Environment result; + synchronized (this) { + SpringApplicationEnvironmentRepository environment = new SpringApplicationEnvironmentRepository(); + git.checkout().setName(label).call(); + String search = git.getRepository().getDirectory().getParent(); + environment.setSearchLocations(search); + result = clean(environment.findOne(application, name, label)); + } + return result; + } catch (Exception e) { + throw new IllegalStateException("Cannot clone repository", e); + } + } + + private Environment clean(Environment value) { + Environment result = new Environment(value.getName(), value.getLabel()); + for (PropertySource source : value.getPropertySources()) { + String name = source.getName().replace(basedir.toURI().toString(), ""); + name = name.replace("applicationConfig: [", ""); + name = uri + "/" + + name.substring(0, name.contains("]") ? name.lastIndexOf("]") : name.length()); + result.add(new PropertySource(name, source.getSource())); + } + return result; + } +} diff --git a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/NativeEnvironmentRepository.java b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/NativeEnvironmentRepository.java index 085482db..6085e568 100644 --- a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/NativeEnvironmentRepository.java +++ b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/NativeEnvironmentRepository.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.platform.config.server; import java.util.Arrays; @@ -27,8 +28,10 @@ import org.springframework.platform.bootstrap.config.Environment; import org.springframework.platform.bootstrap.config.PropertySource; import org.springframework.web.context.support.StandardServletEnvironment; - /** + * Simple implementation of {@link EnvironmentRepository} that just reflects an existing + * Spring Environment. + * * @author Dave Syer * */ @@ -46,14 +49,14 @@ public class NativeEnvironmentRepository implements EnvironmentRepository { public NativeEnvironmentRepository(ConfigurableEnvironment environment) { this.environment = environment; } - + @Override public Environment findOne(String application, String env, String label) { Environment result = new Environment(env, label); for (org.springframework.core.env.PropertySource source : environment.getPropertySources()) { String name = source.getName(); - if (!standardSources .contains(name) && source instanceof MapPropertySource) { - result.add(new PropertySource(name, (Map)source.getSource())); + if (!standardSources.contains(name) && source instanceof MapPropertySource) { + result.add(new PropertySource(name, (Map) source.getSource())); } } return result; diff --git a/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepository.java b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepository.java new file mode 100644 index 00000000..a687b5cb --- /dev/null +++ b/spring-platform-config-server/src/main/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepository.java @@ -0,0 +1,79 @@ +/* + * 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.platform.config.server; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.platform.bootstrap.config.Environment; +import org.springframework.util.StringUtils; + +/** + * Simple implementation of {@link EnvironmentRepository} that uses a SpringApplication + * and configuration files located through the normal protocols. The resulting Environment + * is composed of property sources located using the application name as the config file + * stem (spring.config.name) and the environment name as a Spring profile. + * + * @author Dave Syer + * + */ +public class SpringApplicationEnvironmentRepository implements EnvironmentRepository { + + private String[] locations; + + @Override + public Environment findOne(String config, String profile, String label) { + SpringApplicationBuilder builder = new SpringApplicationBuilder( + PropertyPlaceholderAutoConfiguration.class); + builder.profiles(profile.split(",")).web(false).showBanner(false); + String[] args = getArgs(config); + ConfigurableApplicationContext context = builder.run(args); + try { + return new NativeEnvironmentRepository(context.getEnvironment()).findOne( + config, profile, label); + } finally { + context.close(); + } + } + + private String[] getArgs(String config) { + List list = new ArrayList(); + list.add("--spring.config.name=" + config); + list.add("--spring.platform.bootstrap.enabled=false"); + if (locations != null) { + list.add("--spring.config.location=" + + StringUtils.arrayToCommaDelimitedString(locations)); + } + return list.toArray(new String[0]); + } + + public void setSearchLocations(String... locations) { + this.locations = locations; + for (int i = 0; i < locations.length; i++) { + String location = locations[i]; + if (!location.endsWith(".properties") && !location.endsWith(".yml") + && !location.endsWith(".yaml") && !location.endsWith("/")) { + location = location + "/"; + } + locations[i] = location; + } + } + +} diff --git a/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/ApplicationTests.java b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/ApplicationTests.java index ade672e0..6a9f4402 100644 --- a/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/ApplicationTests.java +++ b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/ApplicationTests.java @@ -1,18 +1,30 @@ package org.springframework.platform.config.server; +import static org.junit.Assert.assertFalse; + import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.platform.bootstrap.config.Environment; 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 { + + @Value("${local.server.port}") + private int port; @Test public void contextLoads() { + Environment environment = new TestRestTemplate().getForObject("http://localhost:" + port + "/foo/development/", Environment.class); + assertFalse(environment.getPropertySources().isEmpty()); } } 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 new file mode 100644 index 00000000..bdccd8a5 --- /dev/null +++ b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/JGitEnvironmentRepositoryTests.java @@ -0,0 +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.platform.config.server; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.platform.bootstrap.config.Environment; + +/** + * @author Dave Syer + * + */ +public class JGitEnvironmentRepositoryTests { + + private JGitEnvironmentRepository repository = new JGitEnvironmentRepository(); + + @Test + public void vanilla() { + 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()); + } + +} diff --git a/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepositoryTests.java b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepositoryTests.java new file mode 100644 index 00000000..fd1d12ec --- /dev/null +++ b/spring-platform-config-server/src/test/java/org/springframework/platform/config/server/SpringApplicationEnvironmentRepositoryTests.java @@ -0,0 +1,52 @@ +/* + * 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.platform.config.server; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.platform.bootstrap.config.Environment; + + +/** + * @author Dave Syer + * + */ +public class SpringApplicationEnvironmentRepositoryTests { + + private SpringApplicationEnvironmentRepository repository = new SpringApplicationEnvironmentRepository(); + + @Test + public void vanilla() { + Environment environment = repository.findOne("foo", "development", "master"); + assertEquals(2, environment.getPropertySources().size()); + } + + @Test + public void prefixed() { + repository.setSearchLocations("classpath:/test"); + Environment environment = repository.findOne("foo", "development", "master"); + assertEquals(3, environment.getPropertySources().size()); + } + + @Test + public void prefixedWithFile() { + repository.setSearchLocations("file:./src/test/resources/test"); + Environment environment = repository.findOne("foo", "development", "master"); + assertEquals(3, environment.getPropertySources().size()); + } + +} diff --git a/spring-platform-config-server/src/test/resources/foo-development.properties b/spring-platform-config-server/src/test/resources/foo-development.properties new file mode 100644 index 00000000..15b07370 --- /dev/null +++ b/spring-platform-config-server/src/test/resources/foo-development.properties @@ -0,0 +1 @@ +bar: spam \ No newline at end of file diff --git a/spring-platform-config-server/src/test/resources/foo.properties b/spring-platform-config-server/src/test/resources/foo.properties new file mode 100644 index 00000000..7daacd5d --- /dev/null +++ b/spring-platform-config-server/src/test/resources/foo.properties @@ -0,0 +1 @@ +foo: bar \ No newline at end of file diff --git a/spring-platform-config-server/src/test/resources/test/foo.properties b/spring-platform-config-server/src/test/resources/test/foo.properties new file mode 100644 index 00000000..8d9b461d --- /dev/null +++ b/spring-platform-config-server/src/test/resources/test/foo.properties @@ -0,0 +1 @@ +foo: test_bar \ No newline at end of file