Skip to content

Commit

Permalink
feat(config-server): add MongoDB environment repository support
Browse files Browse the repository at this point in the history
This commit introduces MongoDB as a new backend option for the Spring Cloud Config Server, enabling users to store and manage their configuration properties in a MongoDB database.
  • Loading branch information
apappascs committed Mar 7, 2024
1 parent 9e64b4c commit 1e59a49
Show file tree
Hide file tree
Showing 8 changed files with 637 additions and 1 deletion.
5 changes: 5 additions & 0 deletions spring-cloud-config-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@
<artifactId>google-auth-library-oauth2-http</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
import org.springframework.cloud.config.server.environment.JdbcEnvironmentProperties;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentRepository;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.MongoDbEnvironmentProperties;
import org.springframework.cloud.config.server.environment.MongoDbEnvironmentRepository;
import org.springframework.cloud.config.server.environment.MongoDbEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentProperties;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepositoryFactory;
Expand Down Expand Up @@ -98,6 +101,7 @@
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.vault.core.VaultTemplate;
Expand All @@ -111,19 +115,21 @@
* @author Scott Frederick
* @author Tejas Pandilwar
* @author Iulian Antohe
* @author Alexandros Pappas
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class, CredhubEnvironmentProperties.class,
JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class,
RedisEnvironmentProperties.class, AwsS3EnvironmentProperties.class,
AwsSecretsManagerEnvironmentProperties.class, AwsParameterStoreEnvironmentProperties.class,
GoogleSecretManagerEnvironmentProperties.class })
GoogleSecretManagerEnvironmentProperties.class, MongoDbEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultConfiguration.class,
VaultRepositoryConfiguration.class, SpringVaultRepositoryConfiguration.class, CredhubConfiguration.class,
CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class,
GitRepositoryConfiguration.class, RedisRepositoryConfiguration.class, GoogleCloudSourceConfiguration.class,
AwsS3RepositoryConfiguration.class, AwsSecretsManagerRepositoryConfiguration.class,
AwsParameterStoreRepositoryConfiguration.class, GoogleSecretManagerRepositoryConfiguration.class,
MongoRepositoryConfiguration.class,
// DefaultRepositoryConfiguration must be last
DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
Expand Down Expand Up @@ -376,6 +382,19 @@ public NativeEnvironmentRepositoryFactory nativeEnvironmentRepositoryFactory(

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MongoTemplate.class)
@ConditionalOnProperty(value = "spring.cloud.config.server.mongodb.enabled", matchIfMissing = true)
static class MongoDbFactoryConfig {

@Bean
@ConditionalOnBean(MongoTemplate.class)
public MongoDbEnvironmentRepositoryFactory mongoDbEnvironmentRepositoryFactory(MongoTemplate mongoTemplate) {
return new MongoDbEnvironmentRepositoryFactory(mongoTemplate);
}

}

}

@Configuration(proxyBeanMethods = false)
Expand Down Expand Up @@ -577,3 +596,18 @@ public GoogleSecretManagerEnvironmentRepository googleSecretManagerEnvironmentRe
}

}

@Configuration(proxyBeanMethods = false)
@Profile("mongodb")
@ConditionalOnClass(MongoTemplate.class)
@ConditionalOnProperty(value = "spring.cloud.config.server.mongodb.enabled", matchIfMissing = true)
class MongoRepositoryConfiguration {

@Bean
@ConditionalOnBean(MongoTemplate.class)
public MongoDbEnvironmentRepository mongoDbEnvironmentRepository(MongoDbEnvironmentRepositoryFactory factory,
MongoDbEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* 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
*
* https://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;

/**
* Properties related to MongoDB environment repository.
*
* @author Alexandros Pappas
*/
@ConfigurationProperties("spring.cloud.config.server.mongodb")
public class MongoDbEnvironmentProperties implements EnvironmentRepositoryProperties {

/**
* Flag to indicate that MongoDB environment repository configuration is enabled.
*/
private boolean enabled = true;

/**
* Order of the MongoDB environment repository.
*/
private int order = Ordered.LOWEST_PRECEDENCE - 10;

/**
* Name of the MongoDB collection to query for configuration properties.
*/
private String collection = "properties";

/**
* Flag to determine how to handle query exceptions.
*/
private boolean failOnError = true;

/**
* Default label to use if none is specified.
*/
private String defaultLabel = "master";

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public int getOrder() {
return order;
}

@Override
public void setOrder(int order) {
this.order = order;
}

public String getCollection() {
return collection;
}

public void setCollection(String collection) {
this.collection = collection;
}

public boolean isFailOnError() {
return failOnError;
}

public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}

public String getDefaultLabel() {
return defaultLabel;
}

public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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
*
* https://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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.mongodb.MongoException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.StringUtils;

/**
* @author Alexandros Pappas
*/
public class MongoDbEnvironmentRepository implements EnvironmentRepository, Ordered {

private static final Log logger = LogFactory.getLog(JdbcEnvironmentRepository.class);

private final MongoTemplate mongoTemplate;

private final MongoDbEnvironmentProperties properties;

public MongoDbEnvironmentRepository(MongoTemplate mongoTemplate, MongoDbEnvironmentProperties properties) {
this.mongoTemplate = mongoTemplate;
this.properties = properties;
}

@Override
public Environment findOne(String application, String profile, String label) {
label = Optional.ofNullable(label).filter(StringUtils::hasText).orElse(this.properties.getDefaultLabel());
profile = Optional.ofNullable(profile).filter(StringUtils::hasText).orElse("default");

// Prepare the environment with applications and profiles
String[] profilesArray = StringUtils.commaDelimitedListToStringArray(profile);
Environment environment = new Environment(application, profilesArray, label, null, null);

// Prepend "application," to config if not already present
String config = application.startsWith("application") ? application : "application," + application;

List<String> applications = Arrays.stream(StringUtils.commaDelimitedListToStringArray(config)).distinct()
.collect(Collectors.toList());
List<String> profiles = Arrays.stream(profilesArray).distinct().collect(Collectors.toList());

// Reverse for the intended processing order
Collections.reverse(applications);
Collections.reverse(profiles);

// Add property sources for each combination of application and profile
for (String env : profiles) {
for (String app : applications) {
addPropertySource(environment, app, env, label);
}
}
// add properties without profile, equivalent to foo.yml, application.yml
for (String app : applications) {
addPropertySource(environment, app, null, label);
}
return environment;
}

private void addPropertySource(Environment environment, String application, String profile, String label) {
try {
Criteria criteria = Criteria.where("application").is(application).and("label").is(label);
if (profile != null) {
criteria = criteria.and("profile").is(profile);
}
else {
// Handling properties without profile by explicitly looking for them
criteria = criteria.andOperator(Criteria.where("profile").is(null));
}

Query query = new Query(criteria);
List<Map> propertyMaps = this.mongoTemplate.find(query, Map.class, this.properties.getCollection());

for (Map propertyMap : propertyMaps) {
String propertySourceName = (profile != null) ? application + "-" + profile : application;
@SuppressWarnings("unchecked")
Map<String, Object> source = (Map<String, Object>) propertyMap.get("properties");
if (source != null && !source.isEmpty()) {
environment.add(new PropertySource(propertySourceName, source));
}
}
}
catch (DataAccessException | MongoException e) {
if (!this.properties.isFailOnError()) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to retrieve configuration from MongoDB", e);
}
}
else {
throw e;
}
}
}

@Override
public int getOrder() {
return this.properties.getOrder();
}

}
Original file line number Diff line number Diff line change
@@ -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
*
* https://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.mongodb.core.MongoTemplate;

/**
* Factory for creating instances of MongoDbEnvironmentRepository.
*
* @author Alexandros Pappas
*/
public class MongoDbEnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<MongoDbEnvironmentRepository, MongoDbEnvironmentProperties> {

private final MongoTemplate mongoTemplate;

public MongoDbEnvironmentRepositoryFactory(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}

@Override
public MongoDbEnvironmentRepository build(MongoDbEnvironmentProperties environmentProperties) {
return new MongoDbEnvironmentRepository(this.mongoTemplate, environmentProperties);
}

}
Loading

0 comments on commit 1e59a49

Please sign in to comment.