From 62846bda84793307ca7f5edd927e7d37d2b6f1ef Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 30 Sep 2015 13:50:37 +0100 Subject: [PATCH] Add ResourceController for serving plain text config files Can be adapted to serve content in any format (e.g. nginx config file, XML configuration for logger, etc.) - basically anything that can be stored in plain text and doesn't require streaming. Fixes gh-147, see also gh-198 --- .../main/asciidoc/spring-cloud-config.adoc | 74 +++++++++++++ .../monitor/FileMonitorConfiguration.java | 4 +- .../AbstractScmEnvironmentRepository.java | 46 ++++++++ .../config/server/EnableConfigServer.java | 3 +- .../config/server/EnvironmentController.java | 4 + .../config/server/EnvironmentRepository.java | 1 - .../server/GenericResourceRepository.java | 94 ++++++++++++++++ .../server/JGitEnvironmentRepository.java | 62 +++++------ .../MultipleJGitEnvironmentRepository.java | 11 ++ .../server/NativeEnvironmentRepository.java | 66 +++++------ .../server/NoSuchResourceException.java | 29 +++++ .../config/server/ResourceController.java | 103 ++++++++++++++++++ .../server/ResourceLocationService.java | 27 +++++ .../config/server/ResourceRepository.java | 29 +++++ .../server/SvnKitEnvironmentRepository.java | 18 +-- .../config/ConfigServerMvcConfiguration.java | 23 +++- .../EnvironmentRepositoryConfiguration.java | 18 +-- .../ResourceRepositoryConfiguration.java | 40 +++++++ .../ConfigClientOffIntegrationTests.java | 29 +++-- .../ConfigClientOnIntegrationTests.java | 29 +++-- .../GenericResourceRepositoryTests.java | 68 ++++++++++++ .../NativeEnvironmentRepositoryTests.java | 2 +- .../server/ResourceControllerTests.java | 72 ++++++++++++ .../src/test/resources/configserver-test.yml | 5 +- .../src/test/resources/foo-local.txt | 1 + .../test/resources/test/dev/placeholder.txt | 1 + .../src/test/resources/test/dev/template.json | 3 + 27 files changed, 738 insertions(+), 124 deletions(-) create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/AbstractScmEnvironmentRepository.java create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/GenericResourceRepository.java create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NoSuchResourceException.java create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceController.java create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceLocationService.java create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceRepository.java create mode 100644 spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java create mode 100644 spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/GenericResourceRepositoryTests.java create mode 100644 spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ResourceControllerTests.java create mode 100644 spring-cloud-config-server/src/test/resources/foo-local.txt create mode 100644 spring-cloud-config-server/src/test/resources/test/dev/placeholder.txt create mode 100644 spring-cloud-config-server/src/test/resources/test/dev/template.json diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 2c30d903..3a0cd9ea 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -526,6 +526,80 @@ TIP: the `{name:value}` prefixes can also be added to plaintext posted to the `/encrypt` endpoint, if you want to let the Config Server handle all encryption as well as decryption. +== Serving Plain Text + +Instead of using the `Environment` abstraction (or one of the +alternative representations of it in YAML or properties format) your +applications might need generic plain text configuration files, +tailored to their environment. The Config Server provides these +through an additional endpoint at `/{name}/{profile}/{label}/{path}` +where "name", "profile" and "label" have the same meaning as the +regular environment endpoint, but "path" is a file name +(e.g. `log.xml`). The source files for this endpoint are located in +the same way as for the environment endpoints: the same search path is +used as for properties or YAML files, but instead of aggregating all +matching resources, only the first one to match is returned. + +After a resource is located, placeholders in the normal format +(`${...}`) are resolved using the effective `Environment` for the +application name, profile and label supplied. In this way the resource +endpoint is tightly integrated with the environment +endpoints. Example, if you have this layout for a GIT (or SVN) +repository: + +---- +application.yml +nginx.conf +---- + +where `nginx.conf` looks like this: + +---- +server { + listen 80; + server_name ${nginx.server.name}; +} +---- + +and `application.yml` like this: + +[source,yaml] +---- +nginx: + server: + name: example.com +--- +spring: + profiles: development +nginx: + server: + name: develop.com +---- + +then the `/foo/default/master/nginx.conf` resource looks like this: + +---- +server { + listen 80; + server_name example.com; +} +---- + +and `/foo/development/master/nginx.conf` like this: + +---- +server { + listen 80; + server_name develop.com; +} +---- + +NOTE: just like the source files for environment configuration, the +"profile" is used to resolve the file name, so if you want a +profile-specific file you can use a file called +`logback-development.xml` to be resolved by +`/\*/development/*/logback.xml`. + == Embedding the Config Server The Config Server runs best as a standalone application, but if you 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 dcc78f6f..cb1501b5 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 @@ -34,7 +34,7 @@ import java.util.LinkedHashSet; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.config.server.AbstractScmAccessor; +import org.springframework.cloud.config.server.AbstractScmEnvironmentRepository; import org.springframework.cloud.config.server.NativeEnvironmentRepository; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.SmartLifecycle; @@ -66,7 +66,7 @@ public class FileMonitorConfiguration implements SmartLifecycle, ResourceLoaderA PropertyPathEndpoint endpoint; @Autowired(required = false) - AbstractScmAccessor scmRepository; + AbstractScmEnvironmentRepository scmRepository; @Autowired(required = false) NativeEnvironmentRepository nativeEnvironmentRepository; diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/AbstractScmEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/AbstractScmEnvironmentRepository.java new file mode 100644 index 00000000..67c677f0 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/AbstractScmEnvironmentRepository.java @@ -0,0 +1,46 @@ +/* + * Copyright 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; + +import org.springframework.cloud.config.environment.Environment; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * @author Dave Syer + * + */ +public abstract class AbstractScmEnvironmentRepository extends AbstractScmAccessor + implements EnvironmentRepository, ResourceLocationService { + + private EnvironmentCleaner cleaner = new EnvironmentCleaner(); + + public AbstractScmEnvironmentRepository(ConfigurableEnvironment environment) { + super(environment); + } + + @Override + public synchronized Environment findOne(String application, String profile, String label) { + NativeEnvironmentRepository delegate = new NativeEnvironmentRepository( + getEnvironment()); + delegate.setSearchLocations(getLocations(application, profile, label)); + Environment result = delegate.findOne(application, profile, ""); + result.setLabel(label); + return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(), + getUri()); + } + +} 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 48685392..829ec23d 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 @@ -24,6 +24,7 @@ import java.lang.annotation.Target; import org.springframework.cloud.config.server.config.ConfigServerEncryptionConfiguration; import org.springframework.cloud.config.server.config.ConfigServerMvcConfiguration; import org.springframework.cloud.config.server.config.EnvironmentRepositoryConfiguration; +import org.springframework.cloud.config.server.config.ResourceRepositoryConfiguration; import org.springframework.context.annotation.Import; /** @@ -33,7 +34,7 @@ import org.springframework.context.annotation.Import; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import({ EnvironmentRepositoryConfiguration.class, +@Import({ EnvironmentRepositoryConfiguration.class, ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class }) public @interface EnableConfigServer { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java index edf244d5..c0aaa46f 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentController.java @@ -74,6 +74,10 @@ public class EnvironmentController { private boolean stripDocument = true; + public EnvironmentController(EnvironmentRepository repository) { + this(repository, null); + } + public EnvironmentController(EnvironmentRepository repository, EnvironmentEncryptor environmentEncryptor) { this.repository = repository; diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepository.java index a179890f..29589696 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/EnvironmentRepository.java @@ -17,7 +17,6 @@ package org.springframework.cloud.config.server; import org.springframework.cloud.config.environment.Environment; - /** * @author Dave Syer * @author Roy Clarkson diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/GenericResourceRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/GenericResourceRepository.java new file mode 100644 index 00000000..25ac8c4d --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/GenericResourceRepository.java @@ -0,0 +1,94 @@ +/* + * 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; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; + +/** + * An {@link ResourceRepository} backed by a {@link ResourceLocationService}. + * + * @author Dave Syer + */ +public class GenericResourceRepository + implements ResourceRepository, ResourceLoaderAware { + + private ResourceLoader resourceLoader; + + private ResourceLocationService service; + + public GenericResourceRepository(ResourceLocationService service) { + this.service = service; + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Override + public synchronized Resource findOne(String application, String profile, String label, + String path) { + String[] locations = this.service.getLocations(application, "default", label); + try { + for (int i = locations.length; i-- > 0;) { + String location = locations[i]; + for (String local : getProfilePaths(profile, path)) { + Resource file = this.resourceLoader.getResource(location) + .createRelative(local); + if (file.exists() && file.isReadable()) { + return file; + } + } + } + } + catch (IOException e) { + throw new NoSuchResourceException( + "Error : " + path + ". (" + e.getMessage() + ")"); + } + throw new NoSuchResourceException("Not found: " + path); + } + + private Collection getProfilePaths(String profiles, String path) { + Set paths = new LinkedHashSet<>(); + for (String profile : StringUtils.commaDelimitedListToSet(profiles)) { + if (!StringUtils.hasText(profile) || "default".equals(profile)) { + paths.add(path); + } + else { + String ext = StringUtils.getFilenameExtension(path); + if (ext != null) { + ext = "." + ext; + path = StringUtils.stripFilenameExtension(path); + } + else { + ext = ""; + } + paths.add(path + "-" + profile + ext); + } + } + return paths; + } + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/JGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/JGitEnvironmentRepository.java index 4c4b8399..8e72989e 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/JGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/JGitEnvironmentRepository.java @@ -39,7 +39,6 @@ import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.util.FileUtils; import org.springframework.beans.factory.InitializingBean; -import org.springframework.cloud.config.environment.Environment; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.UrlResource; import org.springframework.util.Assert; @@ -53,13 +52,15 @@ import com.jcraft.jsch.Session; * @author Dave Syer * @author Roy Clarkson */ -public class JGitEnvironmentRepository extends AbstractScmAccessor implements EnvironmentRepository, InitializingBean { +public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository + implements EnvironmentRepository, ResourceLocationService, InitializingBean { private static final String DEFAULT_LABEL = "master"; private static final String FILE_URI_PREFIX = "file:"; /** - * Timeout (in seconds) for obtaining HTTP or SSH connection (if applicable). Default 5 seconds. + * Timeout (in seconds) for obtaining HTTP or SSH connection (if applicable). Default + * 5 seconds. */ private int timeout = 5; @@ -71,8 +72,6 @@ public class JGitEnvironmentRepository extends AbstractScmAccessor implements En */ private boolean cloneOnStart = false; - private EnvironmentCleaner cleaner = new EnvironmentCleaner(); - private JGitEnvironmentRepository.JGitFactory gitFactory = new JGitEnvironmentRepository.JGitFactory(); public JGitEnvironmentRepository(ConfigurableEnvironment environment) { @@ -109,12 +108,33 @@ public class JGitEnvironmentRepository extends AbstractScmAccessor implements En } @Override - public Environment findOne(String application, String profile, String label) { + public String[] getLocations(String application, String profile, String label) { + refresh(application, label); + return getSearchLocations(getWorkingDirectory()); + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.state(getUri() != null, + "You need to configure a uri for the git repository"); + if (this.cloneOnStart) { + initClonedRepository(); + } + } + + /** + * Get the working directory ready. + */ + private void refresh(String application, String label) { initialize(); Git git = null; try { git = createGitClient(); - return loadEnvironment(git, application, profile, label); + git.getRepository().getConfig().setString("branch", label, "merge", label); + Ref ref = checkout(git, label); + if (shouldPull(git, ref)) { + pull(git, label, ref); + } } catch (RefNotFoundException e) { throw new NoSuchLabelException("No such label: " + label); @@ -137,15 +157,6 @@ public class JGitEnvironmentRepository extends AbstractScmAccessor implements En } } - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(getUri() != null, - "You need to configure a uri for the git repository"); - if (this.cloneOnStart) { - initClonedRepository(); - } - } - /** * Clones the remote repository and then opens a connection to it. * @throws GitAPIException @@ -160,21 +171,6 @@ public class JGitEnvironmentRepository extends AbstractScmAccessor implements En } - private synchronized Environment loadEnvironment(Git git, String application, - String profile, String label) throws GitAPIException { - NativeEnvironmentRepository environment = new NativeEnvironmentRepository( - getEnvironment()); - git.getRepository().getConfig().setString("branch", label, "merge", label); - Ref ref = checkout(git, label); - if (shouldPull(git, ref)) { - pull(git, label, ref); - } - environment.setSearchLocations(getSearchLocations(getWorkingDirectory())); - Environment result = environment.findOne(application, profile, ""); - result.setLabel(label); - return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(), getUri()); - } - private Ref checkout(Git git, String label) throws GitAPIException { CheckoutCommand checkout = git.checkout(); if (shouldTrack(git, label)) { @@ -299,8 +295,8 @@ public class JGitEnvironmentRepository extends AbstractScmAccessor implements En private void trackBranch(Git git, CheckoutCommand checkout, String label) { checkout.setCreateBranch(true).setName(label) - .setUpstreamMode(SetupUpstreamMode.TRACK) - .setStartPoint("origin/" + label); + .setUpstreamMode(SetupUpstreamMode.TRACK) + .setStartPoint("origin/" + label); } private boolean isBranch(Git git, String label) throws GitAPIException { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java index f262f097..5a09acea 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java @@ -79,6 +79,17 @@ public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository return this.repos; } + @Override + public String[] getLocations(String application, String profile, String label) { + for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) { + Environment source = repository.findOne(application, profile, label); + if (source != null) { + return repository.getLocations(application, profile, label); + } + } + return super.getLocations(application, profile, label); + } + @Override public Environment findOne(String application, String profile, String label) { for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NativeEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NativeEnvironmentRepository.java index 90e7183d..23f67c82 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NativeEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NativeEnvironmentRepository.java @@ -47,7 +47,8 @@ import org.springframework.util.StringUtils; * @author Roy Clarkson */ @ConfigurationProperties("spring.cloud.config.server.native") -public class NativeEnvironmentRepository implements EnvironmentRepository { +public class NativeEnvironmentRepository + implements EnvironmentRepository, ResourceLocationService { private static Log logger = LogFactory.getLog(NativeEnvironmentRepository.class); @@ -96,8 +97,8 @@ public class NativeEnvironmentRepository implements EnvironmentRepository { String[] args = getArgs(config, label); // Explicitly set the listeners (to exclude logging listener which would change // log levels in the caller) - builder.application().setListeners( - Arrays.asList(new ConfigFileEnvironmentPostProcessor(), + builder.application() + .setListeners(Arrays.asList(new ConfigFileEnvironmentPostProcessor(), new EnvironmentPostProcessingApplicationListener())); ConfigurableApplicationContext context = builder.run(args); environment.getPropertySources().remove("profiles"); @@ -110,13 +111,30 @@ public class NativeEnvironmentRepository implements EnvironmentRepository { } } + @Override + public String[] getLocations(String application, String profile, String label) { + String[] locations = this.searchLocations; + if (this.searchLocations == null) { + locations = DEFAULT_LOCATIONS; + } + List output = new ArrayList(); + for (String location : locations) { + output.add(location); + } + for (String location : locations) { + if (isDirectory(location) && StringUtils.hasText(label)) { + output.add(location + label.trim() + "/"); + } + } + return output.toArray(new String[0]); + } + private ConfigurableEnvironment getEnvironment(String profile) { ConfigurableEnvironment environment = new StandardEnvironment(); environment.getPropertySources() - .addFirst( - new MapPropertySource("profiles", Collections - . singletonMap("spring.profiles.active", - profile))); + .addFirst(new MapPropertySource("profiles", + Collections. singletonMap( + "spring.profiles.active", profile))); return environment; } @@ -134,18 +152,19 @@ public class NativeEnvironmentRepository implements EnvironmentRepository { boolean matches = false; String normal = name; if (normal.startsWith("file:")) { - normal = StringUtils.cleanPath(new File(normal.substring("file:" - .length())).getAbsolutePath()); + normal = StringUtils + .cleanPath(new File(normal.substring("file:".length())) + .getAbsolutePath()); } - for (String pattern : StringUtils - .commaDelimitedListToStringArray(getLocations( - this.searchLocations, result.getLabel()))) { + for (String pattern : getLocations(null, null, result.getLabel())) { if (!pattern.contains(":")) { pattern = "file:" + pattern; } if (pattern.startsWith("file:")) { - pattern = StringUtils.cleanPath(new File(pattern - .substring("file:".length())).getAbsolutePath()) + "/"; + pattern = StringUtils + .cleanPath(new File(pattern.substring("file:".length())) + .getAbsolutePath()) + + "/"; } if (logger.isTraceEnabled()) { logger.trace("Testing pattern: " + pattern @@ -179,27 +198,10 @@ public class NativeEnvironmentRepository implements EnvironmentRepository { list.add("--spring.config.name=" + config); list.add("--spring.cloud.bootstrap.enabled=false"); list.add("--encrypt.failOnError=" + this.failOnError); - String[] locations = this.searchLocations; - if (this.searchLocations == null) { - locations = DEFAULT_LOCATIONS; - } - list.add("--spring.config.location=" + getLocations(locations, label)); + list.add("--spring.config.location=" + StringUtils.arrayToCommaDelimitedString(getLocations(null, null, label))); return list.toArray(new String[0]); } - private String getLocations(String[] locations, String label) { - List output = new ArrayList(); - for (String location : locations) { - output.add(location); - } - for (String location : locations) { - if (isDirectory(location) && StringUtils.hasText(label)) { - output.add(location + label.trim() + "/"); - } - } - return StringUtils.collectionToCommaDelimitedString(output); - } - public String[] getSearchLocations() { return this.searchLocations; } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NoSuchResourceException.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NoSuchResourceException.java new file mode 100644 index 00000000..a70bb785 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/NoSuchResourceException.java @@ -0,0 +1,29 @@ +/* + * Copyright 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; + +/** + * @author Dave Syer + * + */ +public class NoSuchResourceException extends RuntimeException { + + public NoSuchResourceException(String string) { + super(string); + } + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceController.java new file mode 100644 index 00000000..adb0878b --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceController.java @@ -0,0 +1,103 @@ +/* + * Copyright 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; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Map; + +import org.springframework.cloud.config.environment.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +/** + * An HTTP endpoint for serving up templated plain text resources from an underlying + * repository. Can be used to supply config files for consumption by a wide variety of + * applications and services. A {@link ResourceRepository} is used to locate a + * {@link Resource}, specific to an application, and the contents are transformed to text. + * Then an {@link EnvironmentController} is used to supply key-value pairs which are used + * to replace placeholders in the resource text. + * + * @author Dave Syer + * + */ +@RestController +@RequestMapping(method = RequestMethod.GET, value = "${spring.cloud.config.server.prefix:}") +public class ResourceController { + + private ResourceRepository resourceRepository; + + private EnvironmentController environmentController; + + public ResourceController(ResourceRepository resourceRepository, + EnvironmentController environmentController) { + this.resourceRepository = resourceRepository; + this.environmentController = environmentController; + } + + @RequestMapping("/{name}/{profile}/{label}/{path:.*}") + public synchronized String resolve(@PathVariable String name, + @PathVariable String profile, @PathVariable String label, + @PathVariable String path) throws IOException { + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addAfter( + StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, + new EnvironmentPropertySource( + this.environmentController.labelled(name, profile, label))); + String text = StreamUtils.copyToString( + this.resourceRepository.findOne(name, profile, label, path).getInputStream(), + Charset.forName("UTF-8")); + // Mask out escaped placeholders + text = text.replace("\\${", "$_{"); + return environment.resolvePlaceholders(text).replace("$_{", "${"); + } + + @ExceptionHandler(NoSuchResourceException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public void notFound(NoSuchResourceException e) { + } + + private static class EnvironmentPropertySource extends PropertySource { + + public EnvironmentPropertySource(Environment sources) { + super("cloudEnvironment", sources); + } + + @Override + public Object getProperty(String name) { + for (org.springframework.cloud.config.environment.PropertySource source : getSource() + .getPropertySources()) { + Map map = source.getSource(); + if (map.containsKey(name)) { + return map.get(name); + } + } + return null; + } + + } + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceLocationService.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceLocationService.java new file mode 100644 index 00000000..306682db --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceLocationService.java @@ -0,0 +1,27 @@ +/* + * Copyright 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; + +/** + * @author Dave Syer + * + */ +public interface ResourceLocationService { + + String[] getLocations(String application, String profile, String label); + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceRepository.java new file mode 100644 index 00000000..dccb4c3d --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ResourceRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 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; + +import org.springframework.core.io.Resource; + +/** + * @author Dave Syer + * + */ +public interface ResourceRepository { + + Resource findOne(String name, String profile, String label, String path); + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/SvnKitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/SvnKitEnvironmentRepository.java index 6902191d..cdeac598 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/SvnKitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/SvnKitEnvironmentRepository.java @@ -23,7 +23,6 @@ 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.cloud.config.environment.Environment; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -42,22 +41,20 @@ import org.tmatesoft.svn.core.wc2.SvnUpdate; * @author Roy Clarkson */ @ConfigurationProperties("spring.cloud.config.server.svn") -public class SvnKitEnvironmentRepository extends AbstractScmAccessor +public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepository implements EnvironmentRepository, InitializingBean { private static Log logger = LogFactory.getLog(SvnKitEnvironmentRepository.class); private static final String DEFAULT_LABEL = "trunk"; - private EnvironmentCleaner cleaner = new EnvironmentCleaner(); - @Override public String getDefaultLabel() { return DEFAULT_LABEL; } @Override - public Environment findOne(String application, String profile, String label) { + public String[] getLocations(String application, String profile, String label) { SvnOperationFactory svnOperationFactory = new SvnOperationFactory(); if (hasText(getUsername())) { svnOperationFactory @@ -71,8 +68,7 @@ public class SvnKitEnvironmentRepository extends AbstractScmAccessor else { checkout(svnOperationFactory); } - return this.cleaner.clean(loadEnvironment(application, profile, label), - getWorkingDirectory().toURI().toString(), getUri()); + return getLocations(label); } catch (SVNException e) { throw new IllegalStateException("Cannot checkout repository", e); @@ -82,10 +78,7 @@ public class SvnKitEnvironmentRepository extends AbstractScmAccessor } } - private synchronized Environment loadEnvironment(String application, String profile, - String label) { - final NativeEnvironmentRepository environmentRepository = new NativeEnvironmentRepository( - getEnvironment()); + private String[] getLocations(String label) { String[] locations = getSearchLocations(getSvnPath(getWorkingDirectory(), label)); boolean exists = false; for (String location : locations) { @@ -100,8 +93,7 @@ public class SvnKitEnvironmentRepository extends AbstractScmAccessor if (!exists) { throw new NoSuchLabelException("No label found for: " + label); } - environmentRepository.setSearchLocations(locations); - return environmentRepository.findOne(application, profile, label); + return locations; } private void checkout(SvnOperationFactory svnOperationFactory) throws SVNException { 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 5b942bc4..0a48d936 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 @@ -20,6 +20,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.cloud.config.server.ConfigServerProperties; import org.springframework.cloud.config.server.EnvironmentController; import org.springframework.cloud.config.server.EnvironmentRepository; +import org.springframework.cloud.config.server.ResourceController; +import org.springframework.cloud.config.server.ResourceRepository; import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,6 +37,9 @@ public class ConfigServerMvcConfiguration { @Autowired private EnvironmentRepository repository; + @Autowired + private ResourceRepository resources; + @Autowired private ConfigServerProperties server; @@ -43,19 +48,25 @@ public class ConfigServerMvcConfiguration { @Bean public EnvironmentController environmentController() { - EnvironmentController controller = new EnvironmentController(repository, environmentEncryptor); + EnvironmentController controller = new EnvironmentController(this.repository, this.environmentEncryptor); controller.setDefaultLabel(getDefaultLabel()); - controller.setOverrides(server.getOverrides()); - controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml()); + controller.setOverrides(this.server.getOverrides()); + controller.setStripDocumentFromYaml(this.server.isStripDocumentFromYaml()); + return controller; + } + + @Bean + public ResourceController resourceController() { + ResourceController controller = new ResourceController(this.resources, environmentController()); return controller; } private String getDefaultLabel() { - if (StringUtils.hasText(server.getDefaultLabel())) { - return server.getDefaultLabel(); + if (StringUtils.hasText(this.server.getDefaultLabel())) { + return this.server.getDefaultLabel(); } else { - return repository.getDefaultLabel(); + return this.repository.getDefaultLabel(); } } } \ 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 13163754..a56203a0 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 @@ -50,11 +50,11 @@ public class EnvironmentRepositoryConfiguration { protected static class NativeRepositoryConfiguration { @Autowired - private ConfigurableEnvironment environment; - + private ConfigurableEnvironment environment; + @Bean public EnvironmentRepository environmentRepository() { - return new NativeEnvironmentRepository(environment); + return new NativeEnvironmentRepository(this.environment); } } @@ -62,16 +62,16 @@ public class EnvironmentRepositoryConfiguration { @Configuration @ConditionalOnMissingBean(EnvironmentRepository.class) protected static class GitRepositoryConfiguration { - + @Autowired - private ConfigurableEnvironment environment; - + private ConfigurableEnvironment environment; + @Bean public EnvironmentRepository environmentRepository() { - return new MultipleJGitEnvironmentRepository(environment); + return new MultipleJGitEnvironmentRepository(this.environment); } } - + @Configuration @Profile("subversion") protected static class SvnRepositoryConfiguration { @@ -80,7 +80,7 @@ public class EnvironmentRepositoryConfiguration { @Bean public EnvironmentRepository environmentRepository() { - return new SvnKitEnvironmentRepository(environment); + return new SvnKitEnvironmentRepository(this.environment); } } 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 new file mode 100644 index 00000000..ff8fa062 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ResourceRepositoryConfiguration.java @@ -0,0 +1,40 @@ +/* + * 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.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.config.server.ConfigServerProperties; +import org.springframework.cloud.config.server.GenericResourceRepository; +import org.springframework.cloud.config.server.ResourceLocationService; +import org.springframework.cloud.config.server.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 + public ResourceRepository resourceRepository(ResourceLocationService service) { + return new GenericResourceRepository(service); + } +} \ No newline at end of file diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOffIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOffIntegrationTests.java index 664937ca..3f1a8a1d 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOffIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOffIntegrationTests.java @@ -1,12 +1,16 @@ package org.springframework.cloud.config.server; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyString; + import java.io.IOException; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; - import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -20,15 +24,11 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.io.ByteArrayResource; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.anyString; - @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = TestConfiguration.class) @IntegrationTest("server.port:0") @@ -50,27 +50,34 @@ public class ConfigClientOffIntegrationTests { @Test public void contextLoads() { Environment environment = new TestRestTemplate().getForObject("http://localhost:" - + port + "/foo/development/", Environment.class); + + this.port + "/foo/development/", Environment.class); assertTrue(environment.getPropertySources().isEmpty()); } @Test public void configClientDisabled() throws Exception { - assertEquals(0, BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, + assertEquals(0, BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.context, ConfigServicePropertySourceLocator.class).length); } - + @Configuration @Import(ConfigServerApplication.class) protected static class TestConfiguration { - + @Bean public EnvironmentRepository environmentRepository() { EnvironmentRepository repository = Mockito.mock(EnvironmentRepository.class); given(repository.findOne(anyString(), anyString(), anyString())).willReturn(new Environment("", "")); return repository; } - + + @Bean + public ResourceRepository resourceRepository() { + ResourceRepository repository = Mockito.mock(ResourceRepository.class); + given(repository.findOne(anyString(), anyString(), anyString(), anyString())).willReturn(new ByteArrayResource("".getBytes())); + return repository; + } + } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOnIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOnIntegrationTests.java index 0787f4a2..44bdb07b 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOnIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ConfigClientOnIntegrationTests.java @@ -1,12 +1,16 @@ package org.springframework.cloud.config.server; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyString; + import java.io.IOException; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; - import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -20,15 +24,11 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.io.ByteArrayResource; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.anyString; - @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = TestConfiguration.class) @IntegrationTest({ "server.port:0", "spring.cloud.config.enabled:true" }) @@ -50,27 +50,34 @@ public class ConfigClientOnIntegrationTests { @Test public void contextLoads() { Environment environment = new TestRestTemplate().getForObject("http://localhost:" - + port + "/foo/development/", Environment.class); + + this.port + "/foo/development/", Environment.class); assertTrue(environment.getPropertySources().isEmpty()); } @Test public void configClientEnabled() throws Exception { - assertEquals(1, BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, + assertEquals(1, BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.context, ConfigServicePropertySourceLocator.class).length); } - + @Configuration @Import(ConfigServerApplication.class) protected static class TestConfiguration { - + @Bean public EnvironmentRepository environmentRepository() { EnvironmentRepository repository = Mockito.mock(EnvironmentRepository.class); given(repository.findOne(anyString(), anyString(), anyString())).willReturn(new Environment("", "")); return repository; } - + + @Bean + public ResourceRepository resourceRepository() { + ResourceRepository repository = Mockito.mock(ResourceRepository.class); + given(repository.findOne(anyString(), anyString(), anyString(), anyString())).willReturn(new ByteArrayResource("".getBytes())); + return repository; + } + } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/GenericResourceRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/GenericResourceRepositoryTests.java new file mode 100644 index 00000000..d8c1299b --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/GenericResourceRepositoryTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 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; + +import static org.junit.Assert.assertNotNull; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * @author Dave Syer + * + */ +public class GenericResourceRepositoryTests { + + private GenericResourceRepository repository; + private ConfigurableApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Before + public void init() { + this.context = new SpringApplicationBuilder( + NativeEnvironmentRepositoryTests.class).web(false).run(); + this.repository = new GenericResourceRepository( + new NativeEnvironmentRepository(this.context.getEnvironment())); + this.repository.setResourceLoader(this.context); + this.context.close(); + } + + @Test + public void locateResource() { + assertNotNull(this.repository.findOne("blah", "default", "master", "foo.properties")); + } + + @Test + public void locateProfiledResource() { + assertNotNull(this.repository.findOne("blah", "local", "master", "foo.txt")); + } + + @Test(expected=NoSuchResourceException.class) + public void locateMissingResource() { + assertNotNull(this.repository.findOne("blah", "default", "master", "foo.txt")); + } + +} diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeEnvironmentRepositoryTests.java index c93b9726..6f431d21 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/NativeEnvironmentRepositoryTests.java @@ -71,7 +71,7 @@ public class NativeEnvironmentRepositoryTests { this.repository.setSearchLocations("classpath:/test"); Environment environment = this.repository.findOne("foo", "development", "dev"); assertEquals(3, environment.getPropertySources().size()); - // position 1 because it has higher precendence than anything except the + // position 1 because it has higher precedence than anything except the // foo-development.properties assertEquals("dev_bar", environment.getPropertySources().get(1).getSource().get("foo")); diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ResourceControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ResourceControllerTests.java new file mode 100644 index 00000000..59b5b4ed --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ResourceControllerTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 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; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * @author Dave Syer + * + */ +public class ResourceControllerTests { + + private ResourceController controller; + private GenericResourceRepository repository; + private ConfigurableApplicationContext context; + private NativeEnvironmentRepository environmentRepository; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Before + public void init() { + this.context = new SpringApplicationBuilder( + NativeEnvironmentRepositoryTests.class).web(false).run(); + this.environmentRepository = new NativeEnvironmentRepository( + this.context.getEnvironment()); + this.repository = new GenericResourceRepository(this.environmentRepository); + this.repository.setResourceLoader(this.context); + this.controller = new ResourceController(this.repository, + new EnvironmentController(this.environmentRepository)); + this.context.close(); + } + + @Test + public void templateReplacement() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test"); + String resource = this.controller.resolve("foo", "bar", "dev", "template.json"); + assertEquals("{\n \"foo\": \"dev_bar\"\n}", resource); + } + + @Test + public void escapedPlaceholder() throws Exception { + this.environmentRepository.setSearchLocations("classpath:/test"); + String resource = this.controller.resolve("foo", "bar", "dev", "placeholder.txt"); + assertEquals("foo: ${foo}", resource); + } + +} diff --git a/spring-cloud-config-server/src/test/resources/configserver-test.yml b/spring-cloud-config-server/src/test/resources/configserver-test.yml index 7446b648..d4f1e947 100644 --- a/spring-cloud-config-server/src/test/resources/configserver-test.yml +++ b/spring-cloud-config-server/src/test/resources/configserver-test.yml @@ -5,7 +5,4 @@ spring: git: basedir: target/config overrides: - spring: - cloud: - config: - enabled: true \ No newline at end of file + spring.cloud.config.enabled: true \ No newline at end of file diff --git a/spring-cloud-config-server/src/test/resources/foo-local.txt b/spring-cloud-config-server/src/test/resources/foo-local.txt new file mode 100644 index 00000000..b3694701 --- /dev/null +++ b/spring-cloud-config-server/src/test/resources/foo-local.txt @@ -0,0 +1 @@ +foo: local \ No newline at end of file diff --git a/spring-cloud-config-server/src/test/resources/test/dev/placeholder.txt b/spring-cloud-config-server/src/test/resources/test/dev/placeholder.txt new file mode 100644 index 00000000..6f8a2fe9 --- /dev/null +++ b/spring-cloud-config-server/src/test/resources/test/dev/placeholder.txt @@ -0,0 +1 @@ +foo: \${foo} \ No newline at end of file diff --git a/spring-cloud-config-server/src/test/resources/test/dev/template.json b/spring-cloud-config-server/src/test/resources/test/dev/template.json new file mode 100644 index 00000000..356a5539 --- /dev/null +++ b/spring-cloud-config-server/src/test/resources/test/dev/template.json @@ -0,0 +1,3 @@ +{ + "foo": "${foo}" +} \ No newline at end of file