From 6b6d777c4b952b1790ddd8277d9a45a0d9d7e699 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Thu, 7 Nov 2019 19:30:04 +0530 Subject: [PATCH] + Support integer keys in application.yaml + spring-boot generator can not handle multi-profile configration Ported PR https://github.com/fabric8io/fabric8-maven-plugin/pull/1745 Ported PR https://github.com/fabric8io/fabric8-maven-plugin/pull/1746 --- .../jkube/kit/common/util/SpringBootUtil.java | 43 +++++++---- .../io/jkube/kit/common/util/YamlUtil.java | 73 ++++++++++++++----- .../kit/common/util/SpringBootUtilTest.java | 18 +++++ ...est-application-with-multiple-profiles.yml | 35 +++++++++ .../test/resources/util/test-application.yml | 1 + .../generator/SpringBootGenerator.java | 12 ++- 6 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 jkube-kit/common/src/test/resources/util/test-application-with-multiple-profiles.yml diff --git a/jkube-kit/common/src/main/java/io/jkube/kit/common/util/SpringBootUtil.java b/jkube-kit/common/src/main/java/io/jkube/kit/common/util/SpringBootUtil.java index b82e918045..c6b2629442 100644 --- a/jkube-kit/common/src/main/java/io/jkube/kit/common/util/SpringBootUtil.java +++ b/jkube-kit/common/src/main/java/io/jkube/kit/common/util/SpringBootUtil.java @@ -39,25 +39,38 @@ public class SpringBootUtil { /** * Returns the spring boot configuration (supports `application.properties` and `application.yml`) - * or an empty properties object if not found + * or an empty properties object if not found, it assumes first profile as default profile. * - * @param compileClassLoader URLClassLoader for resource access - * @return spring boot configuration as Properties + * @param compileClassLoader compile class loader + * @return properties object */ public static Properties getSpringBootApplicationProperties(URLClassLoader compileClassLoader) { + return getSpringBootApplicationProperties(null, compileClassLoader); + } + + /** + * Returns the spring boot configuration (supports `application.properties` and `application.yml`) + * or an empty properties object if not found + * + * @param springActiveProfile currently active spring-boot profile + * @param compileClassLoader compile class loader + * @return properties object + */ + public static Properties getSpringBootApplicationProperties(String springActiveProfile, URLClassLoader compileClassLoader) { URL ymlResource = compileClassLoader.findResource("application.yml"); URL propertiesResource = compileClassLoader.findResource("application.properties"); - Properties props = YamlUtil.getPropertiesFromYamlResource(ymlResource); + Properties props = getPropertiesFromApplicationYamlResource(springActiveProfile, ymlResource); props.putAll(getPropertiesResource(propertiesResource)); return props; } + public static Properties getPropertiesFromApplicationYamlResource(String springActiveProfile, URL ymlResource) { + return YamlUtil.getPropertiesFromYamlResource(springActiveProfile, ymlResource); + } + /** * Returns the given properties resource on the project classpath if found or an empty properties object if not - * - * @param resource URL of the resource - * @return Properties resource */ protected static Properties getPropertiesResource(URL resource) { Properties answer = new Properties(); @@ -73,9 +86,6 @@ protected static Properties getPropertiesResource(URL resource) { /** * Determine the spring-boot devtools version for the current project - * - * @param mavenProject MavenProject of that project - * @return optional string having spring boot devtools version */ public static Optional getSpringBootDevToolsVersion(MavenProject mavenProject) { return getSpringBootVersion(mavenProject); @@ -83,15 +93,18 @@ public static Optional getSpringBootDevToolsVersion(MavenProject mavenPr /** * Determine the spring-boot major version for the current project - * - * @param mavenProject Maven Project - * @return optional string having spring boot version */ public static Optional getSpringBootVersion(MavenProject mavenProject) { return Optional.ofNullable(MavenUtil.getDependencyVersion(mavenProject, SpringBootConfigurationHelper.SPRING_BOOT_GROUP_ID, SpringBootConfigurationHelper.SPRING_BOOT_ARTIFACT_ID)); } - - + public static String getSpringBootActiveProfile(MavenProject mavenProject) { + if (mavenProject != null && mavenProject.getProperties() != null) { + if (mavenProject.getProperties().get("spring.profiles.active") != null) { + return mavenProject.getProperties().get("spring.profiles.active").toString(); + } + } + return null; + } } diff --git a/jkube-kit/common/src/main/java/io/jkube/kit/common/util/YamlUtil.java b/jkube-kit/common/src/main/java/io/jkube/kit/common/util/YamlUtil.java index 2a0ad276b4..83a6fe496d 100644 --- a/jkube-kit/common/src/main/java/io/jkube/kit/common/util/YamlUtil.java +++ b/jkube-kit/common/src/main/java/io/jkube/kit/common/util/YamlUtil.java @@ -15,10 +15,15 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.SortedMap; @@ -26,22 +31,26 @@ public class YamlUtil { protected static Properties getPropertiesFromYamlResource(URL resource) { + return getPropertiesFromYamlResource(null, resource); + } + + protected static Properties getPropertiesFromYamlResource(String activeProfile, URL resource) { if (resource != null) { - try (InputStream yamlStream = resource.openStream()) { - Yaml yaml = new Yaml(); - @SuppressWarnings("unchecked") - SortedMap source = yaml.loadAs(yamlStream, SortedMap.class); + try { Properties properties = new Properties(); - if (source != null) { + // Splitting file for the possibility of different profiles, by default + // only first profile would be considered. + List profiles = getYamlListFromFile(resource); + if (profiles.size() > 0) { try { - properties.putAll(getFlattenedMap(source)); + properties.putAll(getPropertiesFromYamlString(getYamlFromYamlList(activeProfile, profiles))); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(String.format("Spring Boot configuration file %s is not formatted correctly. %s", resource.toString(), e.getMessage())); } } return properties; - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { throw new IllegalStateException("Error while reading Yaml resource from URL " + resource, e); } } @@ -51,25 +60,27 @@ protected static Properties getPropertiesFromYamlResource(URL resource) { /** * Build a flattened representation of the Yaml tree. The conversion is compliant with the thorntail spring-boot rules. */ - private static Map getFlattenedMap(Map source) { + private static Map getFlattenedMap(Map source) { Map result = new LinkedHashMap<>(); buildFlattenedMap(result, source, null); return result; } - @SuppressWarnings("unchecked") - private static void buildFlattenedMap(Map result, Map source, String path) { - for (Map.Entry entry : source.entrySet()) { + private static void buildFlattenedMap(Map result, Map source, String path) { + for (Map.Entry entry : source.entrySet()) { Object keyObject = entry.getKey(); - // If user creates a wrong application.yml then we get a runtime classcastexception - if (!(keyObject instanceof String)) { + String key; + if (keyObject instanceof String) { + key = (String) keyObject; + } else if (keyObject instanceof Number) { + key = String.valueOf(keyObject); + } else { + // If user creates a wrong application.yml then we get a runtime classcastexception throw new IllegalArgumentException(String.format("Expected to find a key of type String but %s with content %s found.", keyObject.getClass(), keyObject.toString())); } - String key = (String) keyObject; - if (path !=null && path.trim().length()>0) { if (key.startsWith("[")) { key = path + key; @@ -81,11 +92,11 @@ private static void buildFlattenedMap(Map result, Map map = (Map) value; + Map map = (Map) value; buildFlattenedMap(result, map, key); } else if (value instanceof Collection) { - Collection collection = (Collection) value; + Collection collection = (Collection) value; int count = 0; for (Object object : collection) { buildFlattenedMap(result, @@ -98,5 +109,33 @@ else if (value instanceof Collection) { } } + public static Properties getPropertiesFromYamlString(String yamlString) throws IllegalArgumentException { + Yaml yaml = new Yaml(); + Properties properties = new Properties(); + + @SuppressWarnings("unchecked") + SortedMap source = yaml.loadAs(yamlString, SortedMap.class); + if (source != null) { + properties.putAll(getFlattenedMap(source)); + } + return properties; + } + + public static List getYamlListFromFile(URL resource) throws URISyntaxException, IOException { + String fileAsString = new String(Files.readAllBytes(Paths.get(resource.toURI()))); + String[] profiles = fileAsString.split("---"); + return Arrays.asList(profiles); + } + + public static String getYamlFromYamlList(String pattern, List yamlAsStringList) { + if (pattern != null) { + for (String yamlStr : yamlAsStringList) { + if (yamlStr.contains(pattern)) + return yamlStr; + } + } + return yamlAsStringList.get(0); + } + } diff --git a/jkube-kit/common/src/test/java/io/jkube/kit/common/util/SpringBootUtilTest.java b/jkube-kit/common/src/test/java/io/jkube/kit/common/util/SpringBootUtilTest.java index b15ae5038d..a5d903150c 100644 --- a/jkube-kit/common/src/test/java/io/jkube/kit/common/util/SpringBootUtilTest.java +++ b/jkube-kit/common/src/test/java/io/jkube/kit/common/util/SpringBootUtilTest.java @@ -17,6 +17,8 @@ import org.junit.Test; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -39,6 +41,7 @@ public void testYamlToPropertiesParsing() { assertEquals("value1", props.getProperty("example.nested.items[1].value")); assertEquals("sub0", props.getProperty("example.nested.items[2].elements[0].element[0].subelement")); assertEquals("sub1", props.getProperty("example.nested.items[2].elements[0].element[1].subelement")); + assertEquals("integerKeyElement", props.getProperty("example.1")); } @@ -78,4 +81,19 @@ public void testNonExistentPropertiesParsing() { } + @Test + public void testMultipleProfilesParsing() { + Properties props = SpringBootUtil.getPropertiesFromApplicationYamlResource(null, getClass().getResource("/util/test-application-with-multiple-profiles.yml")); + assertTrue(props.size() > 0); + + assertEquals("spring-boot-k8-recipes", props.get("spring.application.name")); + assertEquals("false", props.get("management.endpoints.enabled-by-default")); + assertEquals("true", props.get("management.endpoint.health.enabled")); + assertNull(props.get("cloud.kubernetes.reload.enabled")); + + props = SpringBootUtil.getPropertiesFromApplicationYamlResource("kubernetes", getClass().getResource("/util/test-application-with-multiple-profiles.yml")); + assertEquals("true", props.get("cloud.kubernetes.reload.enabled")); + assertNull(props.get("spring.application.name")); + } + } diff --git a/jkube-kit/common/src/test/resources/util/test-application-with-multiple-profiles.yml b/jkube-kit/common/src/test/resources/util/test-application-with-multiple-profiles.yml new file mode 100644 index 0000000000..8bf08124c9 --- /dev/null +++ b/jkube-kit/common/src/test/resources/util/test-application-with-multiple-profiles.yml @@ -0,0 +1,35 @@ +# +# Copyright (c) 2019 Red Hat, Inc. +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at: +# +# https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Red Hat, Inc. - initial API and implementation +# + +spring: + application: + name: spring-boot-k8-recipes + +management: + endpoints: + web: + base-path: '/' + enabled-by-default: false + endpoint: + health: + enabled: true + metrics: + enabled: true +--- +spring: + profiles: kubernetes +cloud: + kubernetes: + reload: + enabled: true diff --git a/jkube-kit/common/src/test/resources/util/test-application.yml b/jkube-kit/common/src/test/resources/util/test-application.yml index 6788556c20..b6ca22d0b4 100644 --- a/jkube-kit/common/src/test/resources/util/test-application.yml +++ b/jkube-kit/common/src/test/resources/util/test-application.yml @@ -34,5 +34,6 @@ example: - element: - subelement: sub0 - subelement: sub1 + 1: integerKeyElement diff --git a/jkube-kit/jkube-kit-spring-boot/src/main/java/io/jkube/springboot/generator/SpringBootGenerator.java b/jkube-kit/jkube-kit-spring-boot/src/main/java/io/jkube/springboot/generator/SpringBootGenerator.java index c9c2f81851..35e683e4b5 100644 --- a/jkube-kit/jkube-kit-spring-boot/src/main/java/io/jkube/springboot/generator/SpringBootGenerator.java +++ b/jkube-kit/jkube-kit-spring-boot/src/main/java/io/jkube/springboot/generator/SpringBootGenerator.java @@ -89,7 +89,9 @@ protected Map getEnv(boolean prePackagePhase) throws MojoExecuti Map res = super.getEnv(prePackagePhase); if (getContext().getGeneratorMode() == GeneratorMode.WATCH) { // adding dev tools token to env variables to prevent override during recompile - String secret = SpringBootUtil.getSpringBootApplicationProperties(MavenUtil.getCompileClassLoader(getProject())).getProperty(DEV_TOOLS_REMOTE_SECRET); + String secret = SpringBootUtil.getSpringBootApplicationProperties( + SpringBootUtil.getSpringBootActiveProfile(getProject()), + MavenUtil.getCompileClassLoader(getProject())).getProperty(SpringBootConfigurationHelper.DEV_TOOLS_REMOTE_SECRET); if (secret != null) { res.put(SpringBootConfigurationHelper.DEV_TOOLS_REMOTE_SECRET_ENV, secret); } @@ -118,7 +120,9 @@ protected boolean isFatJar() throws MojoExecutionException { @Override protected List extractPorts() { List answer = new ArrayList<>(); - Properties properties = SpringBootUtil.getSpringBootApplicationProperties(MavenUtil.getCompileClassLoader(this.getProject())); + Properties properties = SpringBootUtil.getSpringBootApplicationProperties( + SpringBootUtil.getSpringBootActiveProfile(getProject()), + MavenUtil.getCompileClassLoader(this.getProject())); SpringBootConfigurationHelper propertyHelper = new SpringBootConfigurationHelper(SpringBootUtil.getSpringBootVersion(getProject())); String port = properties.getProperty(propertyHelper.getServerPortPropertyKey(), DEFAULT_SERVER_PORT); addPortIfValid(answer, getConfig(JavaExecGenerator.Config.webPort, port)); @@ -130,7 +134,9 @@ protected List extractPorts() { // ============================================================================= private void ensureSpringDevToolSecretToken() throws MojoExecutionException { - Properties properties = SpringBootUtil.getSpringBootApplicationProperties(MavenUtil.getCompileClassLoader(getProject())); + Properties properties = SpringBootUtil.getSpringBootApplicationProperties( + SpringBootUtil.getSpringBootActiveProfile(getProject()), + MavenUtil.getCompileClassLoader(getProject())); String remoteSecret = properties.getProperty(DEV_TOOLS_REMOTE_SECRET); if (Strings.isNullOrEmpty(remoteSecret)) { addSecretTokenToApplicationProperties();