Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.core.Ordered;
import org.springframework.core.env.Profiles;
import org.springframework.core.io.InputStreamResource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -154,6 +155,11 @@ private void addPropertySources(Environment environment, List<String> apps, Stri
addPropertySourcesForApps(apps,
app -> addNonProfileSpecificPropertySource(environment, app, profile, label));
}
// Handle documents with negated profile expressions (e.g. on-profile:
// "!my-profile")
// once per label rather than once per profile to avoid duplicates
addPropertySourcesForApps(apps,
app -> addNegatedProfilePropertySource(environment, app, profiles, label));
}
Comment on lines +158 to 163
}
}
Expand All @@ -162,6 +168,29 @@ private void addPropertySourcesForApps(List<String> apps, Consumer<String> addPr
apps.forEach(addPropertySource);
}

private void addNegatedProfilePropertySource(Environment environment, String app, String[] allProfiles,
String label) {
List<S3ConfigFile> s3ConfigFiles = getNegatedProfileS3ConfigFileYaml(app, allProfiles, label);
addPropertySource(environment, s3ConfigFiles);
}

private List<S3ConfigFile> getNegatedProfileS3ConfigFileYaml(String application, String[] allProfiles,
String label) {
List<S3ConfigFile> configFiles = new ArrayList<>();
try {
S3ConfigFile configFile = new NegatedProfileYamlDocumentS3ConfigFile(application, label, bucketName,
useApplicationAsDirectory, s3Client, allProfiles);
configFiles.add(configFile);
}
catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Did not find negated profile yaml document in non-profile specific file using application <"
+ application + "> label <" + label + ">.");
}
}
return configFiles;
}

private void addProfileSpecificPropertySource(Environment environment, String app, String profile, String label) {
List<S3ConfigFile> s3ConfigFiles = getS3ConfigFile(app, profile, label, this::getS3PropertiesOrJsonConfigFile,
this::getProfileSpecificS3ConfigFileYaml);
Expand Down Expand Up @@ -576,3 +605,44 @@ protected List<String> getExtensions() {
}

}

class NegatedProfileYamlDocumentS3ConfigFile extends YamlS3ConfigFile {

NegatedProfileYamlDocumentS3ConfigFile(String application, String label, String bucketName,
boolean useApplicationAsDirectory, S3Client s3Client, String[] allProfiles) {
super(application, null, label, bucketName, useApplicationAsDirectory, s3Client, properties -> {
Comment on lines +609 to +613
Object onProfileValue = properties.get("spring.config.activate.on-profile");
if (onProfileValue == null) {
onProfileValue = properties.get("spring.config.activate.onProfile");
}
if (onProfileValue == null) {
return YamlProcessor.MatchStatus.NOT_FOUND;
}
String expression = onProfileValue.toString().trim();
// Simple positive profile names are already handled by
// ProfileSpecificYamlDocumentS3ConfigFile. Only process complex or negated
// expressions here to avoid adding duplicate property sources.
if (isSimpleProfileName(expression)) {
return YamlProcessor.MatchStatus.NOT_FOUND;
}
boolean matches = Profiles.of(expression).matches(p -> Arrays.asList(allProfiles).contains(p));
return matches ? YamlProcessor.MatchStatus.FOUND : YamlProcessor.MatchStatus.NOT_FOUND;
Comment on lines +624 to +629
});
}

private static boolean isSimpleProfileName(String expression) {
return !expression.contains("!") && !expression.contains("&") && !expression.contains("|")
&& !expression.contains("(");
}

@Override
protected String buildObjectKeyPrefix() {
return super.buildObjectKeyPrefix(false);
}

@Override
public boolean isShouldIncludeWithEmptyProperties() {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Properties;

import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -167,6 +168,260 @@ public void multiDocumentYaml() throws IOException {
// @formatter:on
}

@Test
public void negatedProfileDocumentIncludedWhenProfileNotActive() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-negated-profile.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

final Environment env = envRepo.findOne("application", "default", null);

List<PropertySource> propertySources = env.getPropertySources();
Optional<PropertySource> negatedProfileSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"))
.findFirst();
assertThat(negatedProfileSource)
.as("negated profile document should be present when 'my-profile' is not active")
.isPresent();
assertThat(negatedProfileSource.get().getSource().get("demo.negatedProfileMarker"))
.isEqualTo("present-when-my-profile-is-not-active");

Optional<PropertySource> baseSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.base"))
.findFirst();
assertThat(baseSource).as("non-profile-specific document should be present").isPresent();
assertThat(baseSource.get().getSource().get("demo.base")).isEqualTo("base-value");
}

@Test
public void negatedProfileDocumentExcludedWhenProfileIsActive() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-negated-profile.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

final Environment env = envRepo.findOne("application", "my-profile", null);

List<PropertySource> propertySources = env.getPropertySources();
boolean hasNegatedProfileMarker = propertySources.stream()
.anyMatch(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"));
assertThat(hasNegatedProfileMarker)
.as("negated profile document should NOT be present when 'my-profile' is active")
.isFalse();

Optional<PropertySource> baseSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.base"))
.findFirst();
assertThat(baseSource).as("non-profile-specific document should still be present").isPresent();
assertThat(baseSource.get().getSource().get("demo.base")).isEqualTo("base-value");
}

@Test
public void negatedProfileDocumentExcludedWhenNegatedProfileIsOneOfMultipleActiveProfiles() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-negated-profile.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

// "my-profile" is among the active profiles, so "!my-profile" should not match
final Environment env = envRepo.findOne("application", "my-profile,other-profile", null);

List<PropertySource> propertySources = env.getPropertySources();
boolean hasNegatedProfileMarker = propertySources.stream()
.anyMatch(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"));
assertThat(hasNegatedProfileMarker)
.as("negated profile document should NOT be present when 'my-profile' is among the active profiles")
.isFalse();
}

@Test
public void negatedProfileDocumentIncludedWhenNegatedProfileIsAbsentFromMultipleActiveProfiles()
throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-negated-profile.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

// neither "default" nor "other-profile" is "my-profile", so "!my-profile" should
// match
final Environment env = envRepo.findOne("application", "default,other-profile", null);

List<PropertySource> propertySources = env.getPropertySources();
Optional<PropertySource> negatedProfileSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"))
.findFirst();
assertThat(negatedProfileSource)
.as("negated profile document should be present when 'my-profile' is absent from all active profiles")
.isPresent();
assertThat(negatedProfileSource.get().getSource().get("demo.negatedProfileMarker"))
.isEqualTo("present-when-my-profile-is-not-active");
}

@Test
public void twoNegatedProfileDocumentsBothIncludedWhenNeitherProfileIsActive() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-two-negated-profiles.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

final Environment env = envRepo.findOne("application", "default", null);

List<PropertySource> propertySources = env.getPropertySources();

// Both negated profile documents match and are merged into one property source
Optional<PropertySource> negatedSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.negatedProfileMarker")
|| ps.getSource().containsKey("demo.otherNegatedMarker"))
.findFirst();
assertThat(negatedSource)
.as("negated profile documents should be present when neither 'my-profile' nor 'other-profile' is active")
.isPresent();
assertThat(negatedSource.get().getSource().get("demo.negatedProfileMarker"))
.isEqualTo("present-when-my-profile-is-not-active");
assertThat(negatedSource.get().getSource().get("demo.otherNegatedMarker"))
.isEqualTo("present-when-other-profile-is-not-active");

Optional<PropertySource> baseSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.base"))
.findFirst();
assertThat(baseSource).as("non-profile-specific document should be present").isPresent();
assertThat(baseSource.get().getSource().get("demo.base")).isEqualTo("base-value");
}

@Test
public void twoNegatedProfileDocumentsOnlyOneIncludedWhenOneProfileIsActive() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-two-negated-profiles.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

// "my-profile" is active, so the "!my-profile" document should be excluded
// but the "!other-profile" document should still be included
final Environment env = envRepo.findOne("application", "my-profile", null);

List<PropertySource> propertySources = env.getPropertySources();

boolean hasNegatedProfileMarker = propertySources.stream()
.anyMatch(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"));
assertThat(hasNegatedProfileMarker)
.as("'!my-profile' document should NOT be present when 'my-profile' is active")
.isFalse();

Optional<PropertySource> otherNegatedSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.otherNegatedMarker"))
.findFirst();
assertThat(otherNegatedSource)
.as("'!other-profile' document should still be present when 'my-profile' is active")
.isPresent();
assertThat(otherNegatedSource.get().getSource().get("demo.otherNegatedMarker"))
.isEqualTo("present-when-other-profile-is-not-active");

Optional<PropertySource> baseSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.base"))
.findFirst();
assertThat(baseSource).as("non-profile-specific document should still be present").isPresent();
assertThat(baseSource.get().getSource().get("demo.base")).isEqualTo("base-value");
}

@Test
public void complexProfileExpressionsAreEvaluatedCorrectly() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-complex-profile-expressions.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

// profile1 active, profile2 not active
// "profile1 & !profile2" -> true
// "!(profile1 & profile2)" -> !(true & false) -> true
// "!profile1 | !profile2" -> false | true -> true
Environment env = envRepo.findOne("application", "profile1", null);
List<PropertySource> sources = env.getPropertySources();

assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.andExpression")))
.as("'profile1 & !profile2' should match when profile1 active, profile2 not active")
.isTrue();
assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.notAndExpression")))
.as("'!(profile1 & profile2)' should match when not both profiles active")
.isTrue();
assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.orNegatedExpression")))
.as("'!profile1 | !profile2' should match when at least one profile is not active")
.isTrue();
}

@Test
public void complexProfileExpressionsAreExcludedWhenNotSatisfied() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-complex-profile-expressions.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application.yaml", yamlString);

// both profile1 and profile2 active
// "profile1 & !profile2" -> true & false -> false
// "!(profile1 & profile2)" -> !(true & true) -> false
// "!profile1 | !profile2" -> false | false -> false
Environment env = envRepo.findOne("application", "profile1,profile2", null);
List<PropertySource> sources = env.getPropertySources();

assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.andExpression")))
.as("'profile1 & !profile2' should NOT match when both profiles are active")
.isFalse();
assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.notAndExpression")))
.as("'!(profile1 & profile2)' should NOT match when both profiles are active")
.isFalse();
assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.orNegatedExpression")))
.as("'!profile1 | !profile2' should NOT match when both profiles are active")
.isFalse();
}

@Test
public void negatedProfileDocumentIncludedWhenProfileNotActive_ApplicationDirVariant() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-negated-profile.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application/application.yaml", yamlString);

final Environment env = envRepoApplicationDir.findOne("application", "default", null);

List<PropertySource> propertySources = env.getPropertySources();
Optional<PropertySource> negatedProfileSource = propertySources.stream()
.filter(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"))
.findFirst();
assertThat(negatedProfileSource)
.as("negated profile document should be present when 'my-profile' is not active")
.isPresent();
assertThat(negatedProfileSource.get().getSource().get("demo.negatedProfileMarker"))
.isEqualTo("present-when-my-profile-is-not-active");
}

@Test
public void negatedProfileDocumentExcludedWhenNegatedProfileIsOneOfMultipleActiveProfiles_ApplicationDirVariant()
throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-negated-profile.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application/application.yaml", yamlString);

final Environment env = envRepoApplicationDir.findOne("application", "my-profile,other-profile", null);

boolean hasNegatedProfileMarker = env.getPropertySources()
.stream()
.anyMatch(ps -> ps.getSource().containsKey("demo.negatedProfileMarker"));
assertThat(hasNegatedProfileMarker)
.as("negated profile document should NOT be present when 'my-profile' is among the active profiles")
.isFalse();
}

@Test
public void complexProfileExpressionsAreEvaluatedCorrectly_ApplicationDirVariant() throws IOException {
Resource resource = new ClassPathResource("awss3/application-with-complex-profile-expressions.yaml");
String yamlString = new String(Files.readAllBytes(Paths.get(resource.getURI())));
putFiles("application/application.yaml", yamlString);

Environment env = envRepoApplicationDir.findOne("application", "profile1", null);
List<PropertySource> sources = env.getPropertySources();

assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.andExpression")))
.as("'profile1 & !profile2' should match when profile1 active, profile2 not active")
.isTrue();
assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.notAndExpression")))
.as("'!(profile1 & profile2)' should match when not both profiles active")
.isTrue();
assertThat(sources.stream().anyMatch(ps -> ps.getSource().containsKey("demo.orNegatedExpression")))
.as("'!profile1 | !profile2' should match when at least one profile is not active")
.isTrue();
}

@Test
public void failToFindNonexistentObject() {
Environment env = envRepo.findOne("foo", "bar", null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
demo:
base: base-value
---
spring:
config:
activate:
on-profile: "profile1 & !profile2"
demo:
andExpression: present-when-profile1-active-and-profile2-not-active
---
spring:
config:
activate:
on-profile: "!(profile1 & profile2)"
demo:
notAndExpression: present-when-not-both-profile1-and-profile2-active
---
spring:
config:
activate:
on-profile: "!profile1 | !profile2"
demo:
orNegatedExpression: present-when-profile1-or-profile2-not-active
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
demo:
base: base-value
---
spring:
config:
activate:
on-profile: "!my-profile"
demo:
negatedProfileMarker: present-when-my-profile-is-not-active
Loading
Loading