From 80659a36b11f24b8a3ea9c117c49f73824932cc0 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 15 Nov 2018 15:40:29 +0100 Subject: [PATCH] Spring Cloud Compatibility Verification Due to the fact that some users have problem with setting up Spring Cloud application, we've decided to add a compatibility verification mechanism. It will break if your current setup is not compatible with Spring Cloud requirements, together with a report, showing what exactly went wrong. At the moment we verify which version of Spring Boot is added to your classpath. Example of a report ``` *************************** APPLICATION FAILED TO START *************************** Description: Your project setup is incompatible with our requirements due to following reasons: - Spring Boot [1.5.16.RELEASE] is not compatible with this Spring Cloud release train Action: Consider applying the following actions: - Change Spring Boot version to one of the following versions [1.2.x, 1.3.x] . You can find the latest Spring Boot versions here [https://spring.io/projects/spring-boot#learn]. If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://spring.io/projects/spring-cloud#overview] and check the [Release Trains] section. ``` In order to disable this feature, set `spring.cloud.compatibility-verifier.enabled` to `false`. If you want to override the compatible Spring Boot versions, just set the `spring.cloud.compatibility-verifier.compatible-boot-versions` property with a comma separated list of compatible Spring Boot versions. fixes gh-442 --- .../main/asciidoc/spring-cloud-commons.adoc | 39 +++- .../CompatibilityNotMetException.java | 29 +++ .../CompatibilityNotMetFailureAnalyzer.java | 46 ++++ .../configuration/CompatibilityPredicate.java | 21 ++ .../configuration/CompatibilityVerifier.java | 24 ++ ...ompatibilityVerifierAutoConfiguration.java | 52 +++++ .../CompatibilityVerifierProperties.java | 53 +++++ .../CompositeCompatibilityVerifier.java | 55 +++++ .../SpringBootVersionVerifier.java | 178 +++++++++++++++ .../configuration/VerificationResult.java | 48 ++++ .../main/resources/META-INF/spring.factories | 5 + ...ibilityVerifierAutoConfigurationTests.java | 61 +++++ ...erifierDisabledAutoConfigurationTests.java | 45 ++++ ...VerifierFailureAutoConfigurationTests.java | 50 +++++ .../CompatibilityVerifierTests.java | 65 ++++++ .../SpringBootDependencyTests.java | 209 ++++++++++++++++++ 16 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetException.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetFailureAnalyzer.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityPredicate.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifier.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfiguration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompositeCompatibilityVerifier.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/VerificationResult.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfigurationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierDisabledAutoConfigurationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierFailureAutoConfigurationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 77edc054..dd1dc39e 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -576,4 +576,41 @@ HTTP client and `OkHttpClientConnectionPoolFactory` for the OK HTTP client. You your own implementation of these beans if you would like to customize how the HTTP clients are created in downstream projects. You can also disable the creation of these beans by setting `spring.cloud.httpclientfactories.apache.enabled` or `spring.cloud.httpclientfactories.ok.enabled` to -`false`. \ No newline at end of file +`false`. + +=== Spring Cloud Compatibility Verification + +Due to the fact that some users have problem with setting up Spring Cloud application, we've decided +to add a compatibility verification mechanism. It will break if your current setup is not compatible +with Spring Cloud requirements, together with a report, showing what exactly went wrong. + +At the moment we verify which version of Spring Boot is added to your classpath. + +Example of a report + +---- +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Your project setup is incompatible with our requirements due to following reasons: + +- Spring Boot [1.5.16.RELEASE] is not compatible with this Spring Cloud release train + + +Action: + +Consider applying the following actions: + +- Change Spring Boot version to one of the following versions [1.2.x, 1.3.x] . +You can find the latest Spring Boot versions here [https://spring.io/projects/spring-boot#learn]. +If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://spring.io/projects/spring-cloud#overview] and check the [Release Trains] section. +---- + + +In order to disable this feature, set `spring.cloud.compatibility-verifier.enabled` to `false`. +If you want to override the compatible Spring Boot versions, just set the +`spring.cloud.compatibility-verifier.compatible-boot-versions` property with a comma separated list +of compatible Spring Boot versions. \ No newline at end of file diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetException.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetException.java new file mode 100644 index 00000000..6be61a3f --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.List; + +/** + * Exception thrown when the current setup is not compatible + * + * @author Marcin Grzejszczak + * @since 1.3.6 + */ +class CompatibilityNotMetException extends RuntimeException { + final List results; + + CompatibilityNotMetException(List results) { + this.results = results; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetFailureAnalyzer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetFailureAnalyzer.java new file mode 100644 index 00000000..c4703e99 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityNotMetFailureAnalyzer.java @@ -0,0 +1,46 @@ +package org.springframework.cloud.configuration; + +import java.util.List; + +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; + +/** + * Analyzer for the {@link CompatibilityNotMetException}. Prints a list of found + * issues and actions that should be taken to fix them. + * + * @author Marcin Grzejszczak + * @since 1.3.6 + */ +public final class CompatibilityNotMetFailureAnalyzer extends AbstractFailureAnalyzer { + @Override + protected FailureAnalysis analyze(Throwable rootFailure, CompatibilityNotMetException cause) { + return new FailureAnalysis(getDescription(cause), getAction(cause), cause); + } + + private String getDescription(CompatibilityNotMetException ex) { + return String.format("Your project setup is incompatible with our requirements " + + "due to following reasons:%s", descriptions(ex.results)); + } + + private String descriptions(List results) { + StringBuilder builder = new StringBuilder("\n\n"); + for (VerificationResult result : results) { + builder.append("- ").append(result.description).append("\n"); + } + return builder.toString(); + } + + private String getAction(CompatibilityNotMetException ex) { + return String.format("Consider applying the following actions:%s", + actions(ex.results)); + } + + private String actions(List results) { + StringBuilder builder = new StringBuilder("\n\n"); + for (VerificationResult result : results) { + builder.append("- ").append(result.action).append("\n"); + } + return builder.toString(); + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityPredicate.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityPredicate.java new file mode 100644 index 00000000..5e564ed7 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityPredicate.java @@ -0,0 +1,21 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +/** + * So that can be used for jdk 7 - otherwise we would use a predicate + * @author Marcin Grzejszczak + */ +interface CompatibilityPredicate { + boolean isCompatible(); +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifier.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifier.java new file mode 100644 index 00000000..c6ae7b60 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifier.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +/** + * Implementations will verify the compatibility and return a result that + * says whether the check is compatible or not for the current release train + * + * @author Marcin Grzejszczak + * @since 1.3.6 + */ +interface CompatibilityVerifier { + VerificationResult verify(); +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfiguration.java new file mode 100644 index 00000000..c31916d4 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.List; + +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +/** + * {@link EnableAutoConfiguration Auto-configuration} that fails the build fast for incompatible + * versions of dependencies (e.g. invalid version of Boot). + * + * @author Marcin Grzejszczak + * @since 1.3.6 + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.compatibility-verifier.enabled", matchIfMissing = true) +@AutoConfigureOrder(0) +@EnableConfigurationProperties(CompatibilityVerifierProperties.class) +public class CompatibilityVerifierAutoConfiguration { + + @Bean + CompositeCompatibilityVerifier compositeCompatibilityVerifier(List verifiers) { + CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(verifiers); + verifier.verifyDependencies(); + return verifier; + } + + @Bean + SpringBootVersionVerifier springBootVersionVerifier( + CompatibilityVerifierProperties properties) { + return new SpringBootVersionVerifier(properties.getCompatibleBootVersions()); + } + +} + diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java new file mode 100644 index 00000000..24b6fb18 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Marcin Grzejszczak + */ +@ConfigurationProperties("spring.cloud.compatibility-verifier") +public class CompatibilityVerifierProperties { + + /** + * Enables creation of Spring Cloud compatibility verification. + */ + private boolean enabled; + + /** + * Default accepted versions for the Spring Boot dependency. You can set {@code x} + * for the patch version if you don't want to specify a concrete value. + * Example: {@code 3.4.x} + */ + private List compatibleBootVersions = Collections.singletonList("1.5.x"); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getCompatibleBootVersions() { + return this.compatibleBootVersions; + } + + public void setCompatibleBootVersions(List compatibleBootVersions) { + this.compatibleBootVersions = compatibleBootVersions; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompositeCompatibilityVerifier.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompositeCompatibilityVerifier.java new file mode 100644 index 00000000..7a01fc6c --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompositeCompatibilityVerifier.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Iterates over {@link CompatibilityVerifier} and prepares a report if exceptions were found + */ +class CompositeCompatibilityVerifier { + + private static final Log log = LogFactory.getLog(CompositeCompatibilityVerifier.class); + + private final List verifiers; + + CompositeCompatibilityVerifier(List verifiers) { + this.verifiers = verifiers; + } + + void verifyDependencies() { + List errors = verifierErrors(); + if (errors.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("All conditions are passing"); + } + return; + } + throw new CompatibilityNotMetException(errors); + } + + private List verifierErrors() { + List errors = new ArrayList<>(); + for (CompatibilityVerifier verifier : this.verifiers) { + VerificationResult result = verifier.verify(); + if (result.isNotCompatible()) { + errors.add(result); + } + } + return errors; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java new file mode 100644 index 00000000..525a014e --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.SpringBootVersion; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.util.StringUtils; + +/** + * Verifies if Spring Boot has proper version + */ +class SpringBootVersionVerifier implements CompatibilityVerifier { + + private static final Log log = LogFactory.getLog(SpringBootVersionVerifier.class); + + final Map ACCEPTED_VERSIONS = new HashMap() { + { + this.put("1.5", is1_5()); + this.put("2.0", is2_0()); + this.put("2.1", is2_1()); + } + }; + + private final List acceptedVersions; + + SpringBootVersionVerifier(List acceptedVersions) { + this.acceptedVersions = acceptedVersions; + } + + @Override + public VerificationResult verify() { + boolean matches = springBootVersionMatches(); + if (matches) { + return VerificationResult.compatible(); + } + return VerificationResult.notCompatible(errorDescription(), action()); + } + + CompatibilityPredicate is1_5() { + return new CompatibilityPredicate() { + + @Override + public String toString() { + return "Predicate for Boot 1.5"; + } + + @Override + public boolean isCompatible() { + try { + // deprecated 1.5 + Class.forName("org.springframework.boot.context.config.ResourceNotFoundException"); + return true; + } + catch (ClassNotFoundException e) { + return false; + } + } + }; + } + + private boolean bootVersionFromManifest(String s) { + String version = getVersionFromManifest(); + if (log.isDebugEnabled()) { + log.debug("Version found in Boot manifest [" + version + "]"); + } + return StringUtils.hasText(version) && version.startsWith(s); + } + + String getVersionFromManifest() { + return SpringBootVersion.getVersion(); + } + + CompatibilityPredicate is2_0() { + return new CompatibilityPredicate() { + @Override + public String toString() { + return "Predicate for Boot 2.0"; + } + + @Override + public boolean isCompatible() { + + try { + // present in 2.0, 1.5 missing in 2.1 + SpringApplicationBuilder.class.getMethod("web", boolean.class); + return !is1_5().isCompatible(); + } + catch (NoSuchMethodException e) { + return false; + } + } + }; + } + + CompatibilityPredicate is2_1() { + return new CompatibilityPredicate() { + + @Override + public String toString() { + return "Predicate for Boot 2.1"; + } + + @Override + public boolean isCompatible() { + try { + // since 2.1 + Class.forName("org.springframework.boot.task.TaskExecutorCustomizer"); + return true; + } + catch (ClassNotFoundException e) { + return false; + } + + + } + }; + } + + private String errorDescription() { + String versionFromManifest = getVersionFromManifest(); + if (StringUtils.hasText(versionFromManifest)) { + return String.format("Spring Boot [%s] is not compatible with this Spring Cloud release train", versionFromManifest); + } + return "Spring Boot is not compatible with this Spring Cloud release train"; + } + + private String action() { + return String.format("Change Spring Boot version to one of the following versions %s .\n" + + "You can find the latest Spring Boot versions here [%s]. \n" + + "If you want to learn more about the Spring Cloud Release train compatibility, you " + + "can visit this page [%s] and check the [Release Trains] section.", + this.acceptedVersions, "https://spring.io/projects/spring-boot#learn", "https://spring.io/projects/spring-cloud#overview"); + } + + private boolean springBootVersionMatches() { + for (String acceptedVersion : acceptedVersions) { + if (bootVersionFromManifest(acceptedVersion)) { + return true; + } + else { + // 2.0, 2.1 + CompatibilityPredicate predicate = ACCEPTED_VERSIONS.get( + acceptedVersionWithoutX(acceptedVersion)); + if (predicate != null && predicate.isCompatible()) { + if (log.isDebugEnabled()) { + log.debug("Predicate [" + predicate + "] was matched"); + } + return true; + } + } + } + return false; + } + + private String acceptedVersionWithoutX(String acceptedVersion) { + if (acceptedVersion.endsWith(".x")) { + return acceptedVersion.substring(0, acceptedVersion.indexOf(".x")); + } + return acceptedVersion; + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/VerificationResult.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/VerificationResult.java new file mode 100644 index 00000000..e3b53b3d --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/VerificationResult.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import org.springframework.util.StringUtils; + +/** + * @author Marcin Grzejszczak + */ +class VerificationResult { + final String description; + final String action; + + // if OK + private VerificationResult() { + this.description = ""; + this.action = ""; + } + + // if not OK + private VerificationResult(String errorDescription, String action) { + this.description = errorDescription; + this.action = action; + } + + static VerificationResult compatible() { + return new VerificationResult(); + } + + static VerificationResult notCompatible(String errorDescription, String action) { + return new VerificationResult(errorDescription, action); + } + + boolean isNotCompatible() { + return StringUtils.hasText(this.description) || StringUtils.hasText(this.action); + } + +} diff --git a/spring-cloud-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-commons/src/main/resources/META-INF/spring.factories index 71538019..059bb891 100644 --- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories @@ -7,6 +7,7 @@ org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\ org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\ org.springframework.cloud.commons.util.UtilAutoConfiguration,\ +org.springframework.cloud.configuration.CompatibilityVerifierAutoConfiguration,\ org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\ org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\ org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\ @@ -16,3 +17,7 @@ org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConf # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.client.HostInfoEnvironmentPostProcessor + +# Failure Analyzers +org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.cloud.configuration.CompatibilityNotMetFailureAnalyzer \ No newline at end of file diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfigurationTests.java new file mode 100644 index 00000000..5b821d1a --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierAutoConfigurationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class CompatibilityVerifierAutoConfigurationTests { + + @Autowired + MyCompatibilityVerifier myMismatchVerifier; + + @Test + public void contextLoads() { + BDDAssertions.then(this.myMismatchVerifier.called).isTrue(); + } + + @Configuration + @EnableAutoConfiguration + static class TestConfiguration { + @Bean + MyCompatibilityVerifier myMismatchVerifier() { + return new MyCompatibilityVerifier(); + } + } + + private static class MyCompatibilityVerifier implements CompatibilityVerifier { + + boolean called; + + @Override + public VerificationResult verify() { + this.called = true; + return VerificationResult.compatible(); + } + } +} + diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierDisabledAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierDisabledAutoConfigurationTests.java new file mode 100644 index 00000000..ba803079 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierDisabledAutoConfigurationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringRunner.class) +@SpringBootTest(properties = "spring.cloud.compatibility-verifier.enabled=false") +public class CompatibilityVerifierDisabledAutoConfigurationTests { + + @Autowired(required = false) + CompositeCompatibilityVerifier compositeCompatibilityVerifier; + + @Test + public void contextLoads() { + BDDAssertions.then(this.compositeCompatibilityVerifier).isNull(); + } + + @Configuration + @EnableAutoConfiguration + static class TestConfiguration { + } +} + diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierFailureAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierFailureAutoConfigurationTests.java new file mode 100644 index 00000000..a081b851 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierFailureAutoConfigurationTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.NestedExceptionUtils; + +/** + * @author Marcin Grzejszczak + */ +public class CompatibilityVerifierFailureAutoConfigurationTests { + + @Rule public OutputCapture outputCapture = new OutputCapture(); + + @Test + public void contextFailsToLoad() { + try { + SpringApplication.run(TestConfiguration.class, + "--spring.cloud.compatibility-verifier.compatible-boot-versions=1.2.x,1.3.x"); + BDDAssertions.fail("should throw exception"); + } catch (BeanCreationException ex) { + Throwable cause = NestedExceptionUtils.getRootCause(ex); + BDDAssertions.then(((CompatibilityNotMetException) cause).results).hasSize(1); + } + } + + @Configuration + @EnableAutoConfiguration + static class TestConfiguration { + } +} + diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierTests.java new file mode 100644 index 00000000..23487d7e --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/CompatibilityVerifierTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.boot.test.rule.OutputCapture; + +/** + * @author Marcin Grzejszczak + */ +public class CompatibilityVerifierTests { + + @Rule public OutputCapture outputCapture = new OutputCapture(); + + @Test + public void should_not_print_the_report_when_no_errors_were_found() { + CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(new ArrayList()); + + verifier.verifyDependencies(); + + BDDAssertions.then(outputCapture.toString()).doesNotContain("SPRING CLOUD VERIFICATION FAILED"); + } + + @Test + public void should_print_the_report_when_errors_were_found() { + List list = new ArrayList<>(); + list.add(new CompatibilityVerifier() { + @Override + public VerificationResult verify() { + return VerificationResult.notCompatible("Wrong Boot version", "Use Boot version 1.2"); + } + }); + list.add(new CompatibilityVerifier() { + @Override + public VerificationResult verify() { + return VerificationResult.notCompatible("Wrong JDK version", "Use JDK 25"); + } + }); + CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(list); + + try { + verifier.verifyDependencies(); + BDDAssertions.fail("should fail"); + } catch (CompatibilityNotMetException ex) { + BDDAssertions.then(ex.results).hasSize(2); + } + } + +} \ No newline at end of file diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java new file mode 100644 index 00000000..92ffb579 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.configuration; + +import java.util.Collections; +import java.util.List; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Test; + +/** + * @author Marcin Grzejszczak + */ +public class SpringBootDependencyTests { + + @Test + public void should_read_concrete_version_from_manifest() { + List acceptedVersions = Collections.singletonList("2.0.3.RELEASE"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return "2.0.3.RELEASE"; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isEmpty(); + BDDAssertions.then(verificationResult.action).isEmpty(); + } + + @Test + public void should_read_concrete_version_from_manifest_and_return_false_when_version_is_not_matched() { + List acceptedVersions = Collections.singletonList("2.0.9.RELEASE"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return "2.0.3.RELEASE"; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isNotEmpty(); + BDDAssertions.then(verificationResult.action).isNotEmpty(); + } + + @Test + public void should_read_concrete_version_from_manifest_and_return_false_when_minor_version_is_not_matched() { + List acceptedVersions = Collections.singletonList("2.0"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return "2.1.3.RELEASE"; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isNotEmpty(); + BDDAssertions.then(verificationResult.action).isNotEmpty(); + } + + @Test + public void should_read_concrete_version_from_manifest_and_match_it_against_minor_version() { + List acceptedVersions = Collections.singletonList("2.0"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return "2.0.3.RELEASE"; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isEmpty(); + BDDAssertions.then(verificationResult.action).isEmpty(); + } + + @Test + public void should_match_against_predicate() { + List acceptedVersions = Collections.singletonList("2.5"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return ""; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + versionVerifier.ACCEPTED_VERSIONS.put("2.5", new CompatibilityPredicate() { + @Override + public boolean isCompatible() { + return true; + } + }); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isEmpty(); + BDDAssertions.then(verificationResult.action).isEmpty(); + } + + @Test + public void should_fail_to_match_against_predicate_when_none_is_matching() { + List acceptedVersions = Collections.singletonList("2.5"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return ""; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isNotEmpty(); + BDDAssertions.then(verificationResult.action).isNotEmpty(); + } + + @Test + public void should_match_against_current_manifest() { + List acceptedVersions = Collections.singletonList("1.5"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions); + versionVerifier.ACCEPTED_VERSIONS.clear(); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isEmpty(); + BDDAssertions.then(verificationResult.action).isEmpty(); + } + + @Test + public void should_match_against_current_predicate() { + List acceptedVersions = Collections.singletonList("1.5"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions){ + @Override + String getVersionFromManifest() { + return ""; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + versionVerifier.ACCEPTED_VERSIONS.put("1.5", versionVerifier.is1_5()); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isEmpty(); + BDDAssertions.then(verificationResult.action).isEmpty(); + } + + @Test + public void should_match_against_current_predicate_with_version_ending_with_x() { + List acceptedVersions = Collections.singletonList("1.5.x"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions){ + @Override + String getVersionFromManifest() { + return ""; + } + }; + versionVerifier.ACCEPTED_VERSIONS.clear(); + versionVerifier.ACCEPTED_VERSIONS.put("1.5", versionVerifier.is1_5()); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isEmpty(); + BDDAssertions.then(verificationResult.action).isEmpty(); + } + + @Test + public void should_fail_to_match_against_predicate_for_non_current_versions() { + List acceptedVersions = Collections.singletonList("1.5"); + SpringBootVersionVerifier + versionVerifier = new SpringBootVersionVerifier(acceptedVersions) { + @Override + String getVersionFromManifest() { + return ""; + } + }; + versionVerifier.ACCEPTED_VERSIONS.remove("1.5"); + + VerificationResult verificationResult = versionVerifier.verify(); + + BDDAssertions.then(verificationResult.description).isNotEmpty(); + BDDAssertions.then(verificationResult.action).isNotEmpty(); + } + +} \ No newline at end of file