diff --git a/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulConfigDataMissingEnvironmentPostProcessor.java b/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulConfigDataMissingEnvironmentPostProcessor.java new file mode 100644 index 00000000..2514d4b6 --- /dev/null +++ b/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulConfigDataMissingEnvironmentPostProcessor.java @@ -0,0 +1,101 @@ +/* + * 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.consul.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.StringUtils; + +import static org.springframework.cloud.consul.config.ConsulConfigDataLocationResolver.PREFIX; +import static org.springframework.cloud.util.PropertyUtils.bootstrapEnabled; +import static org.springframework.cloud.util.PropertyUtils.useLegacyProcessing; + +public class ConsulConfigDataMissingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + /** + * Order of post processor, set to run after + * {@link ConfigDataEnvironmentPostProcessor}. + */ + public static final int ORDER = ConfigDataEnvironmentPostProcessor.ORDER + 1000; + + @Override + public int getOrder() { + return ORDER; + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + // don't run if using bootstrap or legacy processing + if (bootstrapEnabled(environment) || useLegacyProcessing(environment)) { + return; + } + boolean configEnabled = environment.getProperty(ConsulConfigProperties.PREFIX + ".enabled", Boolean.class, + true); + if (!configEnabled) { + return; + } + boolean importCheckEnabled = environment.getProperty(ConsulConfigProperties.PREFIX + ".import-check.enabled", + Boolean.class, true); + if (!importCheckEnabled) { + return; + } + String property = environment.getProperty("spring.config.import"); + if (!StringUtils.hasText(property)) { + throw new ImportException("No spring.config.import set", false); + } + if (!property.contains(PREFIX)) { + throw new ImportException("spring.config.import missing " + PREFIX, true); + } + } + + static class ImportException extends RuntimeException { + + final boolean missingPrefix; + + ImportException(String message, boolean missingPrefix) { + super(message); + this.missingPrefix = missingPrefix; + } + + } + + static class ImportExceptionFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, ImportException cause) { + String description; + if (cause.missingPrefix) { + description = "The spring.config.import property is missing a " + PREFIX + " entry"; + } + else { + description = "No spring.config.import property has been defined"; + } + String action = "Add a spring.config.import=consul: property to your configuration.\n" + + "\tIf configuration in not required add spring.config.import=optional:consul: instead.\n" + + "\tTo disable this check, set spring.cloud.consul.config.enabled=false or \n" + + "\tspring.cloud.consul.config.import-check.enabled=false."; + return new FailureAnalysis(description, action, cause); + } + + } + +} diff --git a/spring-cloud-consul-config/src/main/resources/META-INF/spring.factories b/spring-cloud-consul-config/src/main/resources/META-INF/spring.factories index fc259785..6c1c19a8 100644 --- a/spring-cloud-consul-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-consul-config/src/main/resources/META-INF/spring.factories @@ -5,6 +5,13 @@ org.springframework.cloud.consul.config.ConsulConfigAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration +# Environment PostProcessor +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.cloud.consul.config.ConsulConfigDataMissingEnvironmentPostProcessor + +org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.cloud.consul.config.ConsulConfigDataMissingEnvironmentPostProcessor.ImportExceptionFailureAnalyzer + # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.cloud.consul.config.ConsulConfigDataLocationResolver diff --git a/spring-cloud-consul-config/src/test/java/org/springframework/cloud/consul/config/ConsulConfigDataNoImportIntegrationTests.java b/spring-cloud-consul-config/src/test/java/org/springframework/cloud/consul/config/ConsulConfigDataNoImportIntegrationTests.java new file mode 100644 index 00000000..085cc1ce --- /dev/null +++ b/spring-cloud-consul-config/src/test/java/org/springframework/cloud/consul/config/ConsulConfigDataNoImportIntegrationTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2013-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.consul.config; + +import com.ecwid.consul.v1.ConsulClient; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.cloud.consul.config.ConsulConfigDataMissingEnvironmentPostProcessor.ImportException; +import org.springframework.cloud.consul.test.ConsulTestcontainers; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.consul.config.ConsulConfigDataLocationResolver.PREFIX; + +/** + * @author Spencer Gibb + */ +@ExtendWith(OutputCaptureExtension.class) +public class ConsulConfigDataNoImportIntegrationTests { + + private static final String APP_NAME = "testConsulConfigDataNoImport"; + + private static final String KV_PREFIX = "_configDataNoImportIntegrationTests_config__"; + + private static ConsulClient client; + + @BeforeAll + public static void setup() { + ConsulTestcontainers.start(); + client = ConsulTestcontainers.client(); + client.deleteKVValues(KV_PREFIX); + } + + @AfterAll + public static void teardown() { + client.deleteKVValues(KV_PREFIX); + } + + @Test + public void exceptionThrownIfNoImport(CapturedOutput output) { + Assertions.assertThatThrownBy(() -> new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE) + .run("--spring.application.name=" + APP_NAME)).isInstanceOf(ImportException.class); + + assertThat(output).contains("No spring.config.import property has been defined") + .contains("Add a spring.config.import=consul: property to your configuration"); + } + + @Test + public void exceptionThrownIfImportMissingConsul(CapturedOutput output) { + Assertions.assertThatThrownBy(() -> new SpringApplicationBuilder(Config.class).web(WebApplicationType.NONE).run( + "--spring.config.import=optional:file:somefile.properties", "--spring.application.name=" + APP_NAME)) + .isInstanceOf(ImportException.class); + + assertThat(output).contains("spring.config.import property is missing a " + PREFIX) + .contains("Add a spring.config.import=consul: property to your configuration"); + } + + @Test + public void noExceptionThrownIfConsulConfigDisabled() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE) + .run("--spring.cloud.consul.config.enabled=false", "--spring.application.name=" + APP_NAME)) { + // nothing to do + } + } + + @Test + public void noExceptionThrownIfImportCheckDisabled() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE).run("--spring.cloud.consul.config.import-check.enabled=false", + "--spring.application.name=" + APP_NAME)) { + // nothing to do + } + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + } + +}