diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 5ba5a368..d5982942 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -757,6 +757,52 @@ The database needs to have a table called `PROPERTIES` with columns called `APPL All fields are of type String in Java, so you can make them `VARCHAR` of whatever length you need. Property values behave in the same way as they would if they came from Spring Boot properties files named `{application}-{profile}.properties`, including all the encryption and decryption, which will be applied as post-processing steps (that is, not in the repository implementation directly). +==== Redis Backend + +Spring Cloud Config Server supports Redis as a backend for configuration properties. +You can enable this feature by adding a dependency to link:https://spring.io/projects/spring-data-redis[Spring Data Redis]. + +[source,xml,indent=0] +.pom.xml +---- + + + org.springframework.boot + spring-boot-starter-data-redis + + +---- + +The following configuration uses Spring Data `RedisTemplate` to access a Redis. We can use `spring.redis.*` properties to override default connection settings. + +[source,yaml] +---- +spring: + profiles: + active: redis + redis: + host: redis + port: 16379 +---- + +The properties should be stored as fields in a hash. The name of hash should be the same as `spring.application.name` property or conjunction of `spring.application.name` and `spring.profiles.active[n]`. +[source,sh] +---- +HMSET sample-app server.port "8100" sample.topic.name "test" test.property1 "property1" +---- + +After executing the command visible above a hash should contain the following keys with values: +---- +HGETALL sample-app +{ + "server.port": "8100", + "sample.topic.name": "test", + "test.property1": "property1" +} +---- + +NOTE: When no profile is specified `default` will be used. + ==== CredHub Backend Spring Cloud Config Server supports link:https://docs.cloudfoundry.org/credhub[CredHub] as a backend for configuration properties. diff --git a/spring-cloud-config-server/pom.xml b/spring-cloud-config-server/pom.xml index 4dd71cfc..1ba3b63f 100644 --- a/spring-cloud-config-server/pom.xml +++ b/spring-cloud-config-server/pom.xml @@ -28,6 +28,11 @@ spring-boot-starter-jdbc true + + org.springframework.boot + spring-boot-starter-data-redis + true + org.springframework.cloud spring-cloud-config-client @@ -100,6 +105,12 @@ org.eclipse.jgit.junit.http test + + it.ozimov + embedded-redis + 0.7.2 + test + 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 396d82b0..09b9b482 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 @@ -54,6 +54,9 @@ import org.springframework.cloud.config.server.environment.MultipleJGitEnvironme import org.springframework.cloud.config.server.environment.NativeEnvironmentProperties; import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository; import org.springframework.cloud.config.server.environment.NativeEnvironmentRepositoryFactory; +import org.springframework.cloud.config.server.environment.RedisEnvironmentProperties; +import org.springframework.cloud.config.server.environment.RedisEnvironmentRepository; +import org.springframework.cloud.config.server.environment.RedisEnvironmentRepositoryFactory; import org.springframework.cloud.config.server.environment.SearchPathCompositeEnvironmentRepository; import org.springframework.cloud.config.server.environment.SvnEnvironmentRepositoryFactory; import org.springframework.cloud.config.server.environment.SvnKitEnvironmentProperties; @@ -69,6 +72,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.credhub.core.CredHubOperations; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.jdbc.core.JdbcTemplate; /** @@ -81,12 +85,13 @@ import org.springframework.jdbc.core.JdbcTemplate; @Configuration @EnableConfigurationProperties({ SvnKitEnvironmentProperties.class, CredhubEnvironmentProperties.class, JdbcEnvironmentProperties.class, - NativeEnvironmentProperties.class, VaultEnvironmentProperties.class }) + NativeEnvironmentProperties.class, VaultEnvironmentProperties.class, + RedisEnvironmentProperties.class }) @Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class, CredhubConfiguration.class, CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class, - DefaultRepositoryConfiguration.class }) + RedisRepositoryConfiguration.class, DefaultRepositoryConfiguration.class }) public class EnvironmentRepositoryConfiguration { @Bean @@ -199,6 +204,19 @@ public class EnvironmentRepositoryConfiguration { } + @Configuration + @ConditionalOnClass(StringRedisTemplate.class) + static class RedisFactoryConfig { + + @Bean + @ConditionalOnBean(StringRedisTemplate.class) + public RedisEnvironmentRepositoryFactory redisEnvironmentRepositoryFactory( + StringRedisTemplate redis) { + return new RedisEnvironmentRepositoryFactory(redis); + } + + } + @Configuration @ConditionalOnClass(CredHubOperations.class) static class CredhubFactoryConfig { @@ -312,6 +330,21 @@ class JdbcRepositoryConfiguration { } +@Configuration +@Profile("redis") +@ConditionalOnClass(StringRedisTemplate.class) +class RedisRepositoryConfiguration { + + @Bean + @ConditionalOnBean(StringRedisTemplate.class) + public RedisEnvironmentRepository redisEnvironmentRepository( + RedisEnvironmentRepositoryFactory factory, + RedisEnvironmentProperties environmentProperties) { + return factory.build(environmentProperties); + } + +} + @Configuration @Profile("composite") class CompositeRepositoryConfiguration { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentProperties.java new file mode 100644 index 00000000..a2bb5271 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentProperties.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018-2019 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.environment; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties; +import org.springframework.core.Ordered; + +/** + * @author Piotr Mińkowski + */ +@ConfigurationProperties("spring.cloud.config.server.redis") +public class RedisEnvironmentProperties implements EnvironmentRepositoryProperties { + + private int order = Ordered.LOWEST_PRECEDENCE; + + public int getOrder() { + return this.order; + } + + @Override + public void setOrder(int order) { + this.order = order; + } + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepository.java new file mode 100644 index 00000000..2435baef --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepository.java @@ -0,0 +1,68 @@ +/* + * Copyright 2018-2019 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.environment; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.cloud.config.environment.Environment; +import org.springframework.cloud.config.environment.PropertySource; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.util.StringUtils; + +/** + * @author Piotr Mińkowski + */ +public class RedisEnvironmentRepository implements EnvironmentRepository { + + private final StringRedisTemplate redis; + + private final RedisEnvironmentProperties properties; + + public RedisEnvironmentRepository(StringRedisTemplate redis, + RedisEnvironmentProperties properties) { + this.redis = redis; + this.properties = properties; + } + + @Override + public Environment findOne(String application, String profile, String label) { + String[] profiles = StringUtils.commaDelimitedListToStringArray(profile); + Environment environment = new Environment(application, profiles, label, null, + null); + final List keys = addKeys(application, Arrays.asList(profiles)); + keys.forEach(it -> { + Map m = redis.opsForHash().entries(it); + environment.add(new PropertySource("redis:" + it, m)); + }); + return environment; + } + + private List addKeys(String application, List profiles) { + List keys = new ArrayList<>(); + keys.add(application); + for (String profile : profiles) { + keys.add(application + "-" + profile); + } + Collections.reverse(keys); + return keys; + } + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepositoryFactory.java new file mode 100644 index 00000000..924d6fc0 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepositoryFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018-2019 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.environment; + +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author Piotr Mińkowski + */ +public class RedisEnvironmentRepositoryFactory implements + EnvironmentRepositoryFactory { + + private StringRedisTemplate redis; + + public RedisEnvironmentRepositoryFactory(StringRedisTemplate redis) { + this.redis = redis; + } + + @Override + public RedisEnvironmentRepository build( + RedisEnvironmentProperties environmentProperties) { + return new RedisEnvironmentRepository(this.redis, environmentProperties); + } + +} diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeClasspathTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeClasspathTests.java index fdf1bc50..0770ab12 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeClasspathTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/CompositeClasspathTests.java @@ -27,7 +27,7 @@ import org.springframework.cloud.test.ModifiedClassPathRunner; public class CompositeClasspathTests { @RunWith(ModifiedClassPathRunner.class) - @ClassPathExclusions("spring-jdbc-*.jar") + @ClassPathExclusions({"spring-jdbc-*.jar", "spring-data-redis-*.jar"}) public static class JdbcTests { @Test diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepositoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepositoryIntegrationTests.java new file mode 100644 index 00000000..ad1fd542 --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/RedisEnvironmentRepositoryIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2018-2019 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.environment; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import redis.embedded.RedisServer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; +import org.springframework.cloud.config.environment.Environment; +import org.springframework.data.redis.core.BoundHashOperations; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@DataRedisTest +@ActiveProfiles("redis") +public class RedisEnvironmentRepositoryIntegrationTests { + + private RedisServer redisServer; + + @Before + public void startRedis() throws IOException { + redisServer = new RedisServer(6379); + redisServer.start(); + } + + @After + public void stopRedis() { + redisServer.stop(); + } + + @Autowired + StringRedisTemplate redis; + + @Test + public void test() { + BoundHashOperations bound = redis.boundHashOps("foo-bar"); + bound.put("name", "foo"); + bound.put("tag", "myapp"); + + Environment env = new RedisEnvironmentRepository(redis, + new RedisEnvironmentProperties()).findOne("foo", "bar", ""); + assertThat(env.getName()).isEqualTo("foo"); + assertThat(env.getPropertySources()).isNotEmpty(); + assertThat(env.getPropertySources().get(0).getSource().get("tag")) + .isEqualTo("myapp"); + } + +}