From dabc2c519cd510837b6f91b5764f6b986150f314 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 9 Sep 2021 15:38:29 -0400 Subject: [PATCH] Take into account spring.config.imports in referenced imports. Fixes #1006 (#1007) --- ...igDataMissingEnvironmentPostProcessor.java | 44 +++++-- ...aMissingEnvironmentPostProcessorTests.java | 109 ++++++++++++++++++ 2 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessorTests.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java index 20707b59..329cce6a 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java @@ -16,17 +16,23 @@ package org.springframework.cloud.commons; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.Ordered; +import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; /** * @author Ryan Baxter @@ -60,25 +66,45 @@ public abstract class ConfigDataMissingEnvironmentPostProcessor implements Envir if (!shouldProcessEnvironment(environment)) { return; } - List property = getConfigImports(environment); + List property = getConfigImports(environment); if (property == null || property.isEmpty()) { throw new ImportException("No spring.config.import set", false); } - if (!property.stream().anyMatch(impt -> impt.contains(getPrefix()))) { + if (!property.stream().anyMatch(impt -> ((String) impt).contains(getPrefix()))) { throw new ImportException("spring.config.import missing " + getPrefix(), true); } } - private List getConfigImports(ConfigurableEnvironment environment) { - List property = environment.getProperty(CONFIG_IMPORT_PROPERTY, List.class); - if (property == null || property.isEmpty()) { - Binder binder = Binder.get(environment); - property = Arrays - .asList(binder.bind(CONFIG_IMPORT_PROPERTY, CONFIG_DATA_LOCATION_ARRAY).orElse(new String[0])); - } + private List getConfigImports(ConfigurableEnvironment environment) { + MutablePropertySources propertySources = environment.getPropertySources(); + List property = propertySources.stream().filter(this::propertySourceWithConfigImport) + .flatMap(propertySource -> { + List configImports = new ArrayList<>(); + if (propertySource.getProperty(CONFIG_IMPORT_PROPERTY) != null) { + configImports.add(propertySource.getProperty(CONFIG_IMPORT_PROPERTY)); + } + else { + configImports.addAll(Arrays.asList(getConfigImportArray(propertySource))); + } + return configImports.stream(); + }).collect(Collectors.toList()); return property; } + private boolean propertySourceWithConfigImport(PropertySource propertySource) { + if (CompositePropertySource.class.isInstance(propertySource)) { + return ((CompositePropertySource) propertySource).getPropertySources().stream() + .anyMatch(this::propertySourceWithConfigImport); + } + return propertySource.containsProperty(CONFIG_IMPORT_PROPERTY) + || getConfigImportArray(propertySource).length > 0; + } + + private String[] getConfigImportArray(PropertySource propertySource) { + Binder binder = new Binder(ConfigurationPropertySource.from(propertySource)); + return binder.bind(CONFIG_IMPORT_PROPERTY, CONFIG_DATA_LOCATION_ARRAY).orElse(new String[0]); + } + public static class ImportException extends RuntimeException { /** diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessorTests.java new file mode 100644 index 00000000..42b9953d --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessorTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-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. + * 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.commons; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.mock.env.MockPropertySource; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +/** + * @author Ryan Baxter + */ +public class ConfigDataMissingEnvironmentPostProcessorTests { + + @Test + void noSpringConfigImport() { + MockEnvironment environment = new MockEnvironment(); + SpringApplication app = mock(SpringApplication.class); + TestConfigDataMissingEnvironmentPostProcessor processor = new TestConfigDataMissingEnvironmentPostProcessor(); + assertThatThrownBy(() -> processor.postProcessEnvironment(environment, app)) + .isInstanceOf(ConfigDataMissingEnvironmentPostProcessor.ImportException.class); + } + + @Test + void importSinglePropertySource() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("spring.config.import", "configserver:http://localhost:8888"); + SpringApplication app = mock(SpringApplication.class); + TestConfigDataMissingEnvironmentPostProcessor processor = new TestConfigDataMissingEnvironmentPostProcessor(); + assertThatCode(() -> processor.postProcessEnvironment(environment, app)).doesNotThrowAnyException(); + } + + @Test + void importMultiplePropertySource() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("spring.config.import", "configserver:http://localhost:8888,file:./app.properties"); + SpringApplication app = mock(SpringApplication.class); + TestConfigDataMissingEnvironmentPostProcessor processor = new TestConfigDataMissingEnvironmentPostProcessor(); + assertThatCode(() -> processor.postProcessEnvironment(environment, app)).doesNotThrowAnyException(); + } + + @Test + void importMultiplePropertySourceAsList() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("spring.config.import[0]", "configserver:http://localhost:8888"); + environment.setProperty("spring.config.import[1]", "file:./app.properties"); + SpringApplication app = mock(SpringApplication.class); + TestConfigDataMissingEnvironmentPostProcessor processor = new TestConfigDataMissingEnvironmentPostProcessor(); + assertThatCode(() -> processor.postProcessEnvironment(environment, app)).doesNotThrowAnyException(); + } + + @Test + void importCompositePropertySource() { + MockEnvironment environment = new MockEnvironment(); + CompositePropertySource ps1 = new CompositePropertySource("ps1"); + MockPropertySource ps2 = new MockPropertySource("ps2"); + ps2.setProperty("spring.config.import", "file:./app.properties"); + MockPropertySource ps3 = new MockPropertySource("ps3"); + ps3.setProperty("my.property", "value"); + MockPropertySource ps4 = new MockPropertySource("ps4"); + ps4.setProperty("spring.config.import[0]", "file:./moreproperties.yaml"); + ps4.setProperty("spring.config.import[1]", "configserver:http://localhost:8888"); + CompositePropertySource compositePropertySource = new CompositePropertySource("composite"); + compositePropertySource.addPropertySource(ps3); + compositePropertySource.addPropertySource(ps4); + ps1.addPropertySource(compositePropertySource); + environment.getPropertySources().addFirst(ps2); + environment.getPropertySources().addLast(ps1); + SpringApplication app = mock(SpringApplication.class); + TestConfigDataMissingEnvironmentPostProcessor processor = new TestConfigDataMissingEnvironmentPostProcessor(); + assertThatCode(() -> processor.postProcessEnvironment(environment, app)).doesNotThrowAnyException(); + } + + public class TestConfigDataMissingEnvironmentPostProcessor extends ConfigDataMissingEnvironmentPostProcessor { + + @Override + protected boolean shouldProcessEnvironment(Environment environment) { + return true; + } + + @Override + protected String getPrefix() { + return "configserver:"; + } + + } + +}