From ba9d29c51e67b793a03b3efaa090a653675389c8 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 10 Mar 2021 14:23:05 -0500 Subject: [PATCH] Fail if no zookeeper config import. Adds ZookeeperConfigDataMissingEnvironmentPostProcessor that checks if there is a spring.config.import=zookeeper: statement. If not, an exception is thrown and a FailureAnalyzer provides hints to fix the issue. Fixes gh-284 --- ...igDataMissingEnvironmentPostProcessor.java | 101 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 7 ++ ...perConfigDataNoImportIntegrationTests.java | 95 ++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataMissingEnvironmentPostProcessor.java create mode 100644 spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataNoImportIntegrationTests.java diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataMissingEnvironmentPostProcessor.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataMissingEnvironmentPostProcessor.java new file mode 100644 index 00000000..ffac83d7 --- /dev/null +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataMissingEnvironmentPostProcessor.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.zookeeper.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.cloud.zookeeper.ZookeeperProperties; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.StringUtils; + +import static org.springframework.cloud.util.PropertyUtils.bootstrapEnabled; +import static org.springframework.cloud.util.PropertyUtils.useLegacyProcessing; +import static org.springframework.cloud.zookeeper.config.ZookeeperConfigDataLocationResolver.PREFIX; + +public class ZookeeperConfigDataMissingEnvironmentPostProcessor 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 coreEnabled = environment.getProperty(ZookeeperProperties.PREFIX + ".enabled", Boolean.class, + true); + boolean configEnabled = environment.getProperty(ZookeeperConfigProperties.PREFIX + ".enabled", Boolean.class, + true); + boolean importCheckEnabled = environment.getProperty(ZookeeperConfigProperties.PREFIX + ".import-check.enabled", + Boolean.class, true); + if (!coreEnabled || !configEnabled || !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=zookeeper: property to your configuration.\n" + + "\tIf configuration in not required add spring.config.import=optional:zookeeper: instead.\n" + + "\tTo disable this check, set spring.cloud.zookeeper.config.enabled=false or \n" + + "\tspring.cloud.zookeeper.config.import-check.enabled=false."; + return new FailureAnalysis(description, action, cause); + } + + } + +} diff --git a/spring-cloud-zookeeper-config/src/main/resources/META-INF/spring.factories b/spring-cloud-zookeeper-config/src/main/resources/META-INF/spring.factories index bf6df9bd..3cafecca 100644 --- a/spring-cloud-zookeeper-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-zookeeper-config/src/main/resources/META-INF/spring.factories @@ -6,6 +6,13 @@ org.springframework.cloud.zookeeper.config.ZookeeperConfigAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.zookeeper.config.ZookeeperConfigBootstrapConfiguration +# Environment PostProcessor +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.cloud.zookeeper.config.ZookeeperConfigDataMissingEnvironmentPostProcessor + +org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.cloud.zookeeper.config.ZookeeperConfigDataMissingEnvironmentPostProcessor.ImportExceptionFailureAnalyzer + # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.cloud.zookeeper.config.ZookeeperConfigDataLocationResolver diff --git a/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataNoImportIntegrationTests.java b/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataNoImportIntegrationTests.java new file mode 100644 index 00000000..d8e77955 --- /dev/null +++ b/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigDataNoImportIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * 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.zookeeper.config; + +import org.assertj.core.api.Assertions; +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.zookeeper.config.ZookeeperConfigDataMissingEnvironmentPostProcessor.ImportException; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.zookeeper.config.ZookeeperConfigDataLocationResolver.PREFIX; + +/** + * @author Spencer Gibb + */ +@ExtendWith(OutputCaptureExtension.class) +public class ZookeeperConfigDataNoImportIntegrationTests { + + private static final String APP_NAME = "testZookeeperConfigDataNoImport"; + + @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=zookeeper: property to your configuration"); + } + + @Test + public void exceptionThrownIfImportMissingZookeeper(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=zookeeper: property to your configuration"); + } + + @Test + public void noExceptionThrownIfZookeeperDisabled() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE) + .run("--spring.cloud.zookeeper.enabled=false", "--spring.application.name=" + APP_NAME)) { + // nothing to do + } + } + + @Test + public void noExceptionThrownIfZookeeperConfigDisabled() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE) + .run("--spring.cloud.zookeeper.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.zookeeper.config.import-check.enabled=false", + "--spring.application.name=" + APP_NAME)) { + // nothing to do + } + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + } + +}