Skip to content

Commit 6ec9c43

Browse files
Ryan Baxterryanjbaxter
andauthored
Support for profile specific config data loading (#2260)
Fixes #1922 Co-authored-by: Ryan Baxter <[email protected]>
1 parent f59f8b5 commit 6ec9c43

7 files changed

Lines changed: 267 additions & 43 deletions

File tree

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLoader.java

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
import java.util.LinkedHashMap;
2525
import java.util.List;
2626
import java.util.Map;
27+
import java.util.stream.Collectors;
2728

2829
import org.apache.commons.logging.Log;
2930

3031
import org.springframework.boot.context.config.ConfigData;
3132
import org.springframework.boot.context.config.ConfigData.Option;
32-
import org.springframework.boot.context.config.ConfigData.Options;
3333
import org.springframework.boot.context.config.ConfigDataLoader;
3434
import org.springframework.boot.context.config.ConfigDataLoaderContext;
3535
import org.springframework.boot.context.properties.bind.Binder;
@@ -49,6 +49,7 @@
4949
import org.springframework.http.HttpStatus;
5050
import org.springframework.http.MediaType;
5151
import org.springframework.http.ResponseEntity;
52+
import org.springframework.util.ObjectUtils;
5253
import org.springframework.util.StringUtils;
5354
import org.springframework.web.client.HttpClientErrorException;
5455
import org.springframework.web.client.HttpServerErrorException;
@@ -103,6 +104,7 @@ public ConfigData load(ConfigDataLoaderContext context, ConfigServerConfigDataRe
103104
}
104105

105106
public ConfigData doLoad(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource) {
107+
106108
ConfigClientProperties properties = resource.getProperties();
107109
List<PropertySource<?>> propertySources = new ArrayList<>();
108110
Exception error = null;
@@ -151,27 +153,15 @@ else if (ALL_OPTIONS.size() == 2) {
151153
}
152154
else if (ALL_OPTIONS.size() > 2) {
153155
// boot 2.4.5+
154-
return new ConfigData(propertySources, propertySource -> {
155-
String propertySourceName = propertySource.getName();
156-
List<Option> options = new ArrayList<>();
157-
options.add(Option.IGNORE_IMPORTS);
158-
options.add(Option.IGNORE_PROFILES);
159-
// TODO: the profile is now available on the backend
160-
// in a future minor, add the profile associated with a
161-
// PropertySource see
162-
// https://github.com/spring-cloud/spring-cloud-config/issues/1874
163-
for (String profile : resource.getAcceptedProfiles()) {
164-
// TODO: switch to match
165-
// , is used as a profile-separator for property sources
166-
// from vault
167-
// - is the default profile-separator for property sources
168-
if (propertySourceName.matches(".*[-,]" + profile + ".*")) {
169-
// // TODO: switch to Options.with() when implemented
170-
options.add(Option.PROFILE_SPECIFIC);
171-
}
172-
}
173-
return Options.of(options.toArray(new Option[0]));
174-
});
156+
if (resource.isProfileSpecific()) {
157+
List<PropertySource<?>> filteredSources = propertySources.stream()
158+
.filter(propertySource -> resource.getAcceptedProfiles().stream()
159+
.anyMatch(profile -> isProfileSpecificPropertySource(
160+
resource.getProperties().getName(), propertySource, profile)))
161+
.collect(Collectors.toList());
162+
return new ConfigData(filteredSources, Option.IGNORE_IMPORTS, Option.PROFILE_SPECIFIC);
163+
}
164+
return new ConfigData(propertySources, Option.IGNORE_IMPORTS);
175165
}
176166
}
177167
}
@@ -202,6 +192,36 @@ else if (ALL_OPTIONS.size() > 2) {
202192
return null;
203193
}
204194

195+
private boolean isProfileSpecificPropertySource(String applicationName, PropertySource<?> propertySource,
196+
String profile) {
197+
// Application names can have - so before checking if the application name
198+
// contains a -
199+
// check to see if the application name matches the property source name, if so
200+
// its not a profile specific property source.
201+
202+
// TODO: switch to match
203+
// , is used as a profile-separator for property sources
204+
// from vault
205+
// - is the default profile-separator for property sources
206+
return !applicationName.equals(extractApplicationName(propertySource))
207+
&& propertySource.getName().matches(".*[-,]" + profile + ".*");
208+
}
209+
210+
private String extractApplicationName(PropertySource<?> propertySource) {
211+
if (ObjectUtils.isEmpty(propertySource.getName())) {
212+
return "";
213+
}
214+
int lastSlash = propertySource.getName().lastIndexOf("/");
215+
int lastPeriod = propertySource.getName().lastIndexOf(".");
216+
if (lastSlash == -1) {
217+
lastSlash = 0;
218+
}
219+
if (lastPeriod == -1) {
220+
lastPeriod = propertySource.getName().length();
221+
}
222+
return propertySource.getName().substring(lastSlash + 1, lastPeriod);
223+
}
224+
205225
protected void log(Environment result) {
206226
if (logger.isInfoEnabled()) {
207227
logger.info(String.format("Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s",

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.cloud.config.client;
1818

1919
import java.util.ArrayList;
20-
import java.util.Collections;
2120
import java.util.List;
2221
import java.util.Properties;
2322

@@ -40,6 +39,7 @@
4039
import org.springframework.core.Ordered;
4140
import org.springframework.core.log.LogMessage;
4241
import org.springframework.retry.support.RetryTemplate;
42+
import org.springframework.util.ObjectUtils;
4343
import org.springframework.util.StringUtils;
4444
import org.springframework.web.client.RestTemplate;
4545

@@ -157,7 +157,7 @@ protected String getPrefix() {
157157
public List<ConfigServerConfigDataResource> resolve(ConfigDataLocationResolverContext context,
158158
ConfigDataLocation location)
159159
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
160-
return Collections.emptyList();
160+
return resolveProfileSpecific(context, location, null);
161161
}
162162

163163
@Override
@@ -188,6 +188,7 @@ public List<ConfigServerConfigDataResource> resolveProfileSpecific(
188188

189189
ConfigServerConfigDataResource resource = new ConfigServerConfigDataResource(properties, location.isOptional(),
190190
profiles);
191+
resource.setProfileSpecific(!ObjectUtils.isEmpty(profiles));
191192
resource.setLog(log);
192193
resource.setRetryProperties(propertyHolder.retryProperties);
193194

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataResource.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.config.client;
1818

19+
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.Objects;
2122

@@ -38,6 +39,8 @@ public class ConfigServerConfigDataResource extends ConfigDataResource {
3839

3940
private Log log;
4041

42+
private boolean isProfileSpecific = false;
43+
4144
public ConfigServerConfigDataResource(ConfigClientProperties properties, boolean optional, Profiles profiles) {
4245
this.properties = properties;
4346
this.optional = optional;
@@ -48,6 +51,14 @@ public ConfigClientProperties getProperties() {
4851
return this.properties;
4952
}
5053

54+
public boolean isProfileSpecific() {
55+
return isProfileSpecific;
56+
}
57+
58+
public void setProfileSpecific(boolean profileSpecific) {
59+
isProfileSpecific = profileSpecific;
60+
}
61+
5162
public boolean isOptional() {
5263
return this.optional;
5364
}
@@ -61,6 +72,10 @@ public String getProfiles() {
6172
}
6273

6374
List<String> getAcceptedProfiles() {
75+
if (profiles == null) {
76+
return Collections.singletonList(!properties.getProfile().equals(ConfigClientProperties.DEFAULT_PROFILE)
77+
? properties.getProfile() : ConfigClientProperties.DEFAULT_PROFILE);
78+
}
6479
return this.profiles.getAccepted();
6580
}
6681

@@ -101,7 +116,7 @@ public int hashCode() {
101116
@Override
102117
public String toString() {
103118
return new ToStringCreator(this).append("uris", properties.getUri()).append("optional", optional)
104-
.append("profiles", profiles.getAccepted()).toString();
119+
.append("profiles", getAcceptedProfiles()).toString();
105120

106121
}
107122

spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616

1717
package org.springframework.cloud.config.client;
1818

19+
import java.util.ArrayList;
1920
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.HashMap;
2324
import java.util.LinkedHashMap;
2425
import java.util.List;
2526
import java.util.Map;
27+
import java.util.stream.Collectors;
28+
import java.util.stream.Stream;
2629

2730
import org.apache.commons.logging.Log;
2831
import org.apache.commons.logging.LogFactory;
@@ -47,6 +50,7 @@
4750
import org.springframework.http.ResponseEntity;
4851
import org.springframework.retry.annotation.Retryable;
4952
import org.springframework.util.Assert;
53+
import org.springframework.util.ObjectUtils;
5054
import org.springframework.util.StringUtils;
5155
import org.springframework.web.client.HttpClientErrorException;
5256
import org.springframework.web.client.HttpServerErrorException;
@@ -75,10 +79,37 @@ public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperti
7579
this.defaultProperties = defaultProperties;
7680
}
7781

82+
/**
83+
* Combine properties from the config client properties and the active profiles from
84+
* the environment.
85+
* @param properties config client properties,
86+
* @param environment application environment.
87+
* @return A list of combined profiles.
88+
*/
89+
private List<String> combineProfiles(ConfigClientProperties properties,
90+
org.springframework.core.env.Environment environment) {
91+
List<String> combinedProfiles = new ArrayList<>();
92+
if (!ObjectUtils.isEmpty(properties.getProfile())) {
93+
combinedProfiles = Stream.of(properties.getProfile().split(",")).map(String::trim).filter(s -> !s.isEmpty())
94+
.collect(Collectors.toList());
95+
}
96+
if (environment.getActiveProfiles().length > 0) {
97+
List<String> finalCombinedProfiles = combinedProfiles;
98+
List<String> filteredActiveProfiles = Stream.of(environment.getActiveProfiles())
99+
.filter(s -> !finalCombinedProfiles.contains(s)).collect(Collectors.toList());
100+
combinedProfiles.addAll(filteredActiveProfiles);
101+
}
102+
else if (environment.getDefaultProfiles().length > 0 && combinedProfiles.isEmpty()) {
103+
combinedProfiles = Arrays.asList(environment.getDefaultProfiles());
104+
}
105+
return combinedProfiles;
106+
}
107+
78108
@Override
79109
@Retryable(interceptor = "configServerRetryInterceptor")
80110
public org.springframework.core.env.PropertySource<?> locate(org.springframework.core.env.Environment environment) {
81111
ConfigClientProperties properties = this.defaultProperties.override(environment);
112+
properties.setProfile(String.join(",", combineProfiles(properties, environment)));
82113

83114
if (StringUtils.startsWithIgnoreCase(properties.getName(), "application-")) {
84115
InvalidApplicationNameException exception = new InvalidApplicationNameException(properties.getName());

spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,19 @@ public ConfigData apply(ConfigServerBootstrapper.LoadContext context) {
113113
hasBinder = context.getBinder() != null;
114114
ConfigData configData = context.getInvocation().apply(context.getLoaderContext(), context.getResource());
115115
assertThat(configData).as("ConfigData was null for location %s", context.getResource()).isNotNull();
116-
assertThat(configData.getPropertySources()).hasSize(1);
117-
PropertySource<?> propertySource = configData.getPropertySources().iterator().next();
118-
Options options = configData.getOptions(propertySource);
119-
assertThat(options).as("ConfigData.options was null for location %s property source %s",
120-
context.getResource(), propertySource.getName()).isNotNull();
121-
assertThat(options.contains(Option.IGNORE_IMPORTS)).isTrue();
122-
assertThat(options.contains(Option.IGNORE_PROFILES)).isTrue();
123-
assertThat(options.contains(Option.PROFILE_SPECIFIC)).isFalse();
116+
if (!context.getResource().isProfileSpecific()) {
117+
assertThat(configData.getPropertySources()).hasSize(1);
118+
119+
PropertySource<?> propertySource = configData.getPropertySources().iterator().next();
120+
Options options = configData.getOptions(propertySource);
121+
assertThat(options).as("ConfigData.options was null for location %s property source %s",
122+
context.getResource(), propertySource.getName()).isNotNull();
123+
assertThat(options.contains(Option.IGNORE_IMPORTS)).isTrue();
124+
assertThat(options.contains(Option.PROFILE_SPECIFIC)).isFalse();
125+
}
126+
else {
127+
assertThat(configData.getPropertySources()).hasSize(0);
128+
}
124129
return configData;
125130
}
126131

0 commit comments

Comments
 (0)