Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config-server): add MongoDB environment repository support #2390

Merged
merged 4 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*** xref:server/environment-repository/aws-parameter-store-backend.adoc[]
*** xref:server/environment-repository/aws-secrets-manager-backend.adoc[]
*** xref:server/environment-repository/credhub-backend.adoc[]
*** xref:server/environment-repository/mongo-backend.adoc[]
*** xref:server/environment-repository/composite-repositories.adoc[]
*** xref:server/environment-repository/custom-enviroment-repository.adoc[]
*** xref:server/environment-repository/property-overrides.adoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[[mongo-backend]]
= MongoDB Backend
:page-section-summary-toc: 1

Spring Cloud Config Server supports MongoDB as a backend for configuration properties.
You can enable this feature by adding `spring-boot-starter-data-mongodb` to the classpath and using the `mongodb` profile.

[source,xml,indent=0]
.pom.xml
----
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
----

Configure your application's `application.properties` or `application.yml` to point to your MongoDB instance:

[source,yaml]
----
spring:
data:
apappascs marked this conversation as resolved.
Show resolved Hide resolved
mongodb:
database: your-database-name
port: '27017'
host: localhost
----

The configuration properties should be stored in documents within the `properties` collection. Each document represents a set of properties for a given application, profile, and label.

Example MongoDB document:

[source,json]
----
{
"application": "myapp",
"profile": "development",
"label": "master",
"properties": {
"property1": "value1",
"property2": "value2"
}
}
----

You can disable autoconfiguration for `MongoDbEnvironmentRepository` by setting the `spring.cloud.config.server.mongodb.enabled` property to `false`.

The default values for MongoDB backend configuration are as follows:

- **Collection Name:** `"properties"` (Name of the MongoDB collection to query for configuration properties.)

- **Default Label:** `"master"` (Default label to use if none is specified.)

NOTE: You can change these defaults by setting `spring.cloud.config.server.mongodb.collection` and `spring.cloud.config.server.mongodb.defaultLabel` in your application's configuration.
15 changes: 15 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 Expand Up @@ -243,6 +248,16 @@
<artifactId>spring-core-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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 @@ -99,6 +102,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 @@ -112,19 +116,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 @@ -378,6 +384,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 @@ -579,3 +598,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,127 @@
/*
* 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.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 = StringUtils.hasText(label) ? label : this.properties.getDefaultLabel();
profile = StringUtils.hasText(profile) ? profile : "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();
}

}
Loading
Loading