Skip to content

Commit

Permalink
Add ConfigData IGNORE_PROFILES support
Browse files Browse the repository at this point in the history
Add a new `ConfigData.Option` that allows profile properties to be
ignored. This update will allow Spring Cloud Config Server to provide
`ConfigData` that only has profile properties processed on the
server-side.

Closes gh-24890
  • Loading branch information
philwebb committed Jan 20, 2021
1 parent 7b4fbd2 commit 80dbbaf
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 45 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
Expand Down Expand Up @@ -90,7 +90,13 @@ public enum Option {
/**
* Ignore all imports properties from the sources.
*/
IGNORE_IMPORTS;
IGNORE_IMPORTS,

/**
* Ignore all profile activation and include properties.
* @since 2.4.3
*/
IGNORE_PROFILES;

}

Expand Down
Expand Up @@ -181,6 +181,11 @@ private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
return createContributors(contributors);
}

protected ConfigDataEnvironmentContributors createContributors(
List<ConfigDataEnvironmentContributor> contributors) {
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
}

Expand Down Expand Up @@ -263,7 +268,8 @@ private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvir
private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext) {
this.logger.trace("Deducing profiles from current config data environment contributors");
Binder binder = contributors.getBinder(activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
Binder binder = contributors.getBinder(activationContext,
ConfigDataEnvironmentContributor::isNotIgnoringProfiles, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
try {
Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles);
additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext));
Expand All @@ -285,16 +291,15 @@ private Collection<? extends String> getIncludedProfiles(ConfigDataEnvironmentCo
Set<String> result = new LinkedHashSet<>();
for (ConfigDataEnvironmentContributor contributor : contributors) {
ConfigurationPropertySource source = contributor.getConfigurationPropertySource();
if (source == null) {
continue;
if (source != null && contributor.isNotIgnoringProfiles()) {
Binder binder = new Binder(Collections.singleton(source), placeholdersResolver);
binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> {
if (!contributor.isActive(activationContext)) {
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, Profiles.INCLUDE_PROFILES);
}
result.addAll(includes);
});
}
Binder binder = new Binder(Collections.singleton(source), placeholdersResolver);
binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> {
if (!contributor.isActive(activationContext)) {
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, Profiles.INCLUDE_PROFILES);
}
result.addAll(includes);
});
}
return result;
}
Expand Down
Expand Up @@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

Expand Down Expand Up @@ -51,6 +52,9 @@
*/
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {

private static final Set<ConfigData.Option> EMPTY_LOCATION_OPTIONS = Collections
.unmodifiableSet(Collections.singleton(ConfigData.Option.IGNORE_IMPORTS));

private final ConfigDataLocation location;

private final ConfigDataResource resource;
Expand All @@ -63,7 +67,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment

private final ConfigDataProperties properties;

private final boolean ignoreImports;
private final Set<ConfigData.Option> configDataOptions;

private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;

Expand All @@ -79,21 +83,22 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* @param configurationPropertySource the configuration property source for the data
* or {@code null}
* @param properties the config data properties or {@code null}
* @param ignoreImports if import properties should be ignored
* @param configDataOptions any config data options that should apply
* @param children the children of this contributor at each {@link ImportPhase}
*/
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource,
boolean profileSpecific, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
boolean ignoreImports, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
Set<ConfigData.Option> configDataOptions,
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind;
this.location = location;
this.resource = resource;
this.profileSpecific = profileSpecific;
this.properties = properties;
this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource;
this.ignoreImports = ignoreImports;
this.configDataOptions = (configDataOptions != null) ? configDataOptions : Collections.emptySet();
this.children = (children != null) ? children : Collections.emptyMap();
}

Expand Down Expand Up @@ -150,6 +155,15 @@ ConfigurationPropertySource getConfigurationPropertySource() {
return this.configurationPropertySource;
}

/**
* Returns {@code true} if this contributor is not ignoring profile properties.
* @return if the contributor is not ignoring profiles
* @see ConfigData.Option#IGNORE_PROFILES
*/
boolean isNotIgnoringProfiles() {
return !this.configDataOptions.contains(ConfigData.Option.IGNORE_PROFILES);
}

/**
* Return any imports requested by this contributor.
* @return the imports
Expand Down Expand Up @@ -209,12 +223,12 @@ public Iterator<ConfigDataEnvironmentContributor> iterator() {
ConfigDataEnvironmentContributor withBoundProperties(Binder binder) {
UseLegacyConfigProcessingException.throwIfRequested(binder);
ConfigDataProperties properties = ConfigDataProperties.get(binder);
if (this.ignoreImports) {
if (this.configDataOptions.contains(ConfigData.Option.IGNORE_IMPORTS)) {
properties = properties.withoutImports();
}
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.resource,
this.profileSpecific, this.propertySource, this.configurationPropertySource, properties,
this.ignoreImports, null);
this.configDataOptions, null);
}

/**
Expand All @@ -229,7 +243,7 @@ ConfigDataEnvironmentContributor withChildren(ImportPhase importPhase,
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children);
updatedChildren.put(importPhase, children);
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource, this.profileSpecific,
this.propertySource, this.configurationPropertySource, this.properties, this.ignoreImports,
this.propertySource, this.configurationPropertySource, this.properties, this.configDataOptions,
updatedChildren);
}

Expand All @@ -255,7 +269,7 @@ ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributo
updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors));
});
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource, this.profileSpecific,
this.propertySource, this.configurationPropertySource, this.properties, this.ignoreImports,
this.propertySource, this.configurationPropertySource, this.properties, this.configDataOptions,
updatedChildren);
}

Expand All @@ -267,7 +281,7 @@ ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributo
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, false, null, null, null, false, children);
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, false, null, null, null, null, children);
}

/**
Expand All @@ -281,7 +295,7 @@ static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initi
List<ConfigDataLocation> imports = Collections.singletonList(initialImport);
ConfigDataProperties properties = new ConfigDataProperties(imports, null);
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, false, null, null, properties,
false, null);
null, null);
}

/**
Expand All @@ -293,7 +307,7 @@ static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initi
*/
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, null, false, propertySource,
ConfigurationPropertySource.from(propertySource), null, false, null);
ConfigurationPropertySource.from(propertySource), null, null, null);
}

/**
Expand All @@ -311,9 +325,8 @@ static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation locat
boolean profileSpecific, ConfigData configData, int propertySourceIndex) {
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS);
return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, resource, profileSpecific,
propertySource, configurationPropertySource, null, ignoreImports, null);
propertySource, configurationPropertySource, null, configData.getOptions(), null);
}

/**
Expand All @@ -324,7 +337,7 @@ static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation locat
*/
static ConfigDataEnvironmentContributor ofEmptyLocation(ConfigDataLocation location, boolean profileSpecific) {
return new ConfigDataEnvironmentContributor(Kind.EMPTY_LOCATION, location, null, profileSpecific, null, null,
null, true, null);
null, EMPTY_LOCATION_OPTIONS, null);
}

/**
Expand Down
Expand Up @@ -24,8 +24,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;

Expand All @@ -52,6 +52,8 @@
*/
class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmentContributor> {

private static final Predicate<ConfigDataEnvironmentContributor> NO_CONTRIBUTOR_FILTER = (contributor) -> true;

private final Log logger;

private final ConfigDataEnvironmentContributor root;
Expand Down Expand Up @@ -186,38 +188,47 @@ ConfigDataEnvironmentContributor getRoot() {
}

/**
* Return a {@link Binder} that works against all active contributors.
* Return a {@link Binder} backed by the contributors.
* @param activationContext the activation context
* @param options binder options to apply
* @return a binder instance
*/
Binder getBinder(ConfigDataActivationContext activationContext, BinderOption... options) {
return getBinder(activationContext, asBinderOptionsSet(options));
return getBinder(activationContext, NO_CONTRIBUTOR_FILTER, options);
}

/**
* Return a {@link Binder} backed by the contributors.
* @param activationContext the activation context
* @param filter a filter used to limit the contributors
* @param options binder options to apply
* @return a binder instance
*/
Binder getBinder(ConfigDataActivationContext activationContext, Predicate<ConfigDataEnvironmentContributor> filter,
BinderOption... options) {
return getBinder(activationContext, filter, asBinderOptionsSet(options));
}

private Set<BinderOption> asBinderOptionsSet(BinderOption... options) {
return ObjectUtils.isEmpty(options) ? EnumSet.noneOf(BinderOption.class)
: EnumSet.copyOf(Arrays.asList(options));
}

private Binder getBinder(ConfigDataActivationContext activationContext, Set<BinderOption> options) {
private Binder getBinder(ConfigDataActivationContext activationContext,
Predicate<ConfigDataEnvironmentContributor> filter, Set<BinderOption> options) {
boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(activationContext,
!options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
filter.and((contributor) -> failOnInactiveSource || contributor.isActive(activationContext)));
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root,
activationContext, failOnInactiveSource);
BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext);
return new Binder(sources, placeholdersResolver, null, null, bindHandler);
}

private Iterator<ConfigurationPropertySource> getBinderSources(ConfigDataActivationContext activationContext,
boolean filterInactive) {
Stream<ConfigDataEnvironmentContributor> sources = this.root.stream()
.filter(this::hasConfigurationPropertySource);
if (filterInactive) {
sources = sources.filter((contributor) -> contributor.isActive(activationContext));
}
return sources.map(ConfigDataEnvironmentContributor::getConfigurationPropertySource).iterator();
Predicate<ConfigDataEnvironmentContributor> filter) {
return this.root.stream().filter(this::hasConfigurationPropertySource).filter(filter)
.map(ConfigDataEnvironmentContributor::getConfigurationPropertySource).iterator();
}

private boolean hasConfigurationPropertySource(ConfigDataEnvironmentContributor contributor) {
Expand Down
Expand Up @@ -114,7 +114,7 @@ static void throwOrWarn(Log logger, ConfigDataEnvironmentContributor contributor
logger.warn(getMessage(property, false, replacement, contributor.getResource()));
}
});
if (contributor.isProfileSpecific()) {
if (contributor.isProfileSpecific() && contributor.isNotIgnoringProfiles()) {
PROFILE_SPECIFIC_ERRORS.forEach((name) -> {
ConfigurationProperty property = propertySource.getConfigurationProperty(name);
if (property != null) {
Expand Down
Expand Up @@ -122,7 +122,7 @@ static class TestConfigDataEnvironmentContributor extends ConfigDataEnvironmentC
private final boolean active;

protected TestConfigDataEnvironmentContributor(PropertySource<?> propertySource, boolean active) {
super(Kind.ROOT, null, null, false, propertySource, null, null, false, null);
super(Kind.ROOT, null, null, false, propertySource, null, null, null, null);
this.active = active;
}

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
Expand All @@ -18,7 +18,9 @@

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand All @@ -34,6 +36,7 @@
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
Expand All @@ -42,6 +45,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;

/**
* Tests for {@link ConfigDataEnvironment}.
Expand Down Expand Up @@ -188,6 +192,30 @@ void processAndApplySetsActiveProfilesAndProfileGroups(TestInfo info) {
assertThat(this.environment.getActiveProfiles()).containsExactly("one", "four", "five", "two", "three");
}

@Test
void processAndApplyDoesNotSetProfilesFromIgnoreProfilesContributors(TestInfo info) {
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles, null) {

@Override
protected ConfigDataEnvironmentContributors createContributors(
List<ConfigDataEnvironmentContributor> contributors) {
Map<String, Object> source = new LinkedHashMap<>();
source.put("spring.profiles.active", "ignore1");
source.put("spring.profiles.include", "ignore2");
ConfigData data = new ConfigData(Collections.singleton(new MapPropertySource("test", source)),
ConfigData.Option.IGNORE_PROFILES);
contributors.add(ConfigDataEnvironmentContributor.ofUnboundImport(ConfigDataLocation.of("test"),
mock(ConfigDataResource.class), false, data, 0));
return super.createContributors(contributors);
}

};
configDataEnvironment.processAndApply();
assertThat(this.environment.getActiveProfiles()).containsExactly("test");
}

@Test
@Disabled("Disabled until spring.profiles support is dropped")
void processAndApplyWhenHasInvalidPropertyThrowsException() {
Expand Down

0 comments on commit 80dbbaf

Please sign in to comment.