Support for Redis backend repository (#1282)

* initial structure for Redis support

* redis support pre-implementation

* redis support implementation

* fixes

* changing to stringredistemplate

* fixing names of redis beans

* #1282 fixing build errors

* #1282 fixing test errors

* #1282 fixing failed tests

* #1282 adding documentation

* #1282 fixing documentation
This commit is contained in:
Piotr Mińkowski
2019-03-20 16:56:16 +01:00
committed by Ryan Baxter
parent cbeceaaefd
commit 8bb4cc68b2
8 changed files with 312 additions and 3 deletions

View File

@@ -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
----
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
----
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.

View File

@@ -28,6 +28,11 @@
<artifactId>spring-boot-starter-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
@@ -100,6 +105,12 @@
<artifactId>org.eclipse.jgit.junit.http</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>

View File

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

View File

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

View File

@@ -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<String> 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<String> addKeys(String application, List<String> profiles) {
List<String> keys = new ArrayList<>();
keys.add(application);
for (String profile : profiles) {
keys.add(application + "-" + profile);
}
Collections.reverse(keys);
return keys;
}
}

View File

@@ -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<RedisEnvironmentRepository, RedisEnvironmentProperties> {
private StringRedisTemplate redis;
public RedisEnvironmentRepositoryFactory(StringRedisTemplate redis) {
this.redis = redis;
}
@Override
public RedisEnvironmentRepository build(
RedisEnvironmentProperties environmentProperties) {
return new RedisEnvironmentRepository(this.redis, environmentProperties);
}
}

View File

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

View File

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