This is an automated email from the ASF dual-hosted git repository. cdeppisch pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit b1320a3f2130c7fb65c75f82e85b38e918499c38 Author: Thomas Diesler <tdies...@redhat.com> AuthorDate: Tue Jul 23 12:50:10 2024 +0200 [CAMEL-20990] camel-jbang: startup, readiness and liveness probes in kubernetes spring-boot --- .../dsl/jbang/core/commands/ExportBaseCommand.java | 30 ++-- .../dsl/jbang/core/commands/ExportCamelMain.java | 78 ---------- .../dsl/jbang/core/commands/ExportSpringBoot.java | 20 --- .../main/resources/templates/jkube-profiles.tmpl | 48 ------ .../resources/templates/main-kubernetes-pom.tmpl | 2 +- .../templates/spring-boot-kubernetes-pom.tmpl | 70 ++++++--- .../main/resources/templates/spring-boot-pom.tmpl | 2 - .../core/commands/kubernetes/KubernetesExport.java | 161 ++++++++++++++------- .../core/commands/kubernetes/KubernetesHelper.java | 9 +- .../commands/kubernetes/traits/ContainerTrait.java | 1 + .../commands/kubernetes/KubernetesExportTest.java | 101 +++++++------ 11 files changed, 245 insertions(+), 277 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java index 52a044fc207..51977cc4886 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java @@ -619,6 +619,14 @@ public abstract class ExportBaseCommand extends CamelCommand { // noop } + protected Properties mapToProperties(Map<String, Object> map) { + var result = new Properties(); + if (map != null) { + map.forEach((key, value) -> result.setProperty(key, value.toString())); + } + return result; + } + protected void copyMavenWrapper() throws Exception { File wrapper = new File(BUILD_DIR, ".mvn/wrapper"); wrapper.mkdirs(); @@ -739,18 +747,20 @@ public abstract class ExportBaseCommand extends CamelCommand { return answer != null ? answer : "3.4.3"; } - protected static String jkubeMavenPluginVersion(File settings, Properties prop) { + protected static String jkubeMavenPluginVersion(File settings, Properties props) { String answer = null; - if (prop != null) { - answer = prop.getProperty("camel.jbang.jkube-maven-plugin-version"); + if (props != null) { + answer = props.getProperty("camel.jbang.jkube-maven-plugin-version"); } - try { - List<String> lines = RuntimeUtil.loadPropertiesLines(settings); - answer = lines.stream() - .filter(l -> l.startsWith("camel.jbang.jkube-maven-plugin-version=") || l.startsWith("jkube.version=")) - .map(s -> StringHelper.after(s, "=")).findFirst().orElse(null); - } catch (Exception e) { - // ignore + if (answer == null) { + try { + List<String> lines = RuntimeUtil.loadPropertiesLines(settings); + answer = lines.stream() + .filter(l -> l.startsWith("camel.jbang.jkube-maven-plugin-version=") || l.startsWith("jkube.version=")) + .map(s -> StringHelper.after(s, "=")).findFirst().orElse(null); + } catch (Exception e) { + // ignore + } } return answer != null ? answer : "1.16.2"; } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java index efe0a00a9c3..69430050e3a 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java @@ -247,87 +247,9 @@ class ExportCamelMain extends Export { context = context.replaceFirst("\\{\\{ \\.CamelDependencies }}", sb.toString()); - // include kubernetes? - context = enrichMavenPomKubernetes(context, settings, profile); - IOHelper.writeText(context, new FileOutputStream(pom, false)); } - protected String enrichMavenPomKubernetes(String context, File settings, File profile) throws Exception { - StringBuilder sb1 = new StringBuilder(); - StringBuilder sb2 = new StringBuilder(); - - // is kubernetes included? - Properties prop = new CamelCaseOrderedProperties(); - if (profile.exists()) { - RuntimeUtil.loadProperties(prop, profile); - } - boolean jib = prop.stringPropertyNames().stream().anyMatch(s -> s.startsWith("jib.")); - boolean jkube = prop.stringPropertyNames().stream().anyMatch(s -> s.startsWith("jkube.")); - // jib is used for docker and kubernetes, jkube is only used for kubernetes - if (jib || jkube) { - // include all jib/jkube/label properties - String fromImage = null; - for (String key : prop.stringPropertyNames()) { - String value = prop.getProperty(key); - if ("jib.from.image".equals(key)) { - fromImage = value; - } - boolean accept = key.startsWith("jkube.") || key.startsWith("jib.") || key.startsWith("label."); - if (accept) { - sb1.append(String.format(" <%s>%s</%s>%n", key, value, key)); - } - } - // from image is mandatory so use a default image if none provided - if (fromImage == null) { - fromImage = "eclipse-temurin:" + javaVersion + "-jre"; - sb1.append(String.format(" <%s>%s</%s>%n", "jib.from.image", fromImage, "jib.from.image")); - } - - InputStream is = ExportCamelMain.class.getClassLoader().getResourceAsStream("templates/main-docker-pom.tmpl"); - String context2 = IOHelper.loadText(is); - IOHelper.close(is); - - context2 = context2.replaceFirst("\\{\\{ \\.JibMavenPluginVersion }}", jibMavenPluginVersion(settings, prop)); - - // image from/to auth - String auth = ""; - if (prop.stringPropertyNames().stream().anyMatch(s -> s.startsWith("jib.from.auth."))) { - is = ExportCamelMain.class.getClassLoader().getResourceAsStream("templates/main-docker-from-auth-pom.tmpl"); - auth = IOHelper.loadText(is); - IOHelper.close(is); - } - context2 = context2.replace("{{ .JibFromImageAuth }}", auth); - auth = ""; - if (prop.stringPropertyNames().stream().anyMatch(s -> s.startsWith("jib.to.auth."))) { - is = ExportCamelMain.class.getClassLoader().getResourceAsStream("templates/main-docker-to-auth-pom.tmpl"); - auth = IOHelper.loadText(is); - IOHelper.close(is); - } - context2 = context2.replace("{{ .JibToImageAuth }}", auth); - // http port setting - int port = httpServerPort(settings); - if (port == -1) { - port = 8080; - } - context2 = context2.replaceFirst("\\{\\{ \\.Port }}", String.valueOf(port)); - sb2.append(context2); - // jkube is only used for kubernetes - if (jkube) { - is = ExportCamelMain.class.getClassLoader().getResourceAsStream("templates/main-kubernetes-pom.tmpl"); - String context3 = IOHelper.loadText(is); - IOHelper.close(is); - context3 = context3.replaceFirst("\\{\\{ \\.JkubeMavenPluginVersion }}", - jkubeMavenPluginVersion(settings, prop)); - sb2.append(context3); - } - } - - context = context.replace("{{ .CamelKubernetesProperties }}", sb1.toString()); - context = context.replace("{{ .CamelKubernetesPlugins }}", sb2.toString()); - return context; - } - @Override protected Set<String> resolveDependencies(File settings, File profile) throws Exception { Set<String> answer = super.resolveDependencies(settings, profile); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java index 0d7cc907a66..07b2eac1a7f 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java @@ -200,19 +200,6 @@ class ExportSpringBoot extends Export { context = context.replaceFirst(Pattern.quote("{{ .AdditionalProperties }}"), ""); } - // Convert jkube properties to maven properties - Properties allProps = new CamelCaseOrderedProperties(); - if (profile != null && profile.exists()) { - RuntimeUtil.loadProperties(allProps, profile); - } - StringBuilder sbJKube = new StringBuilder(); - allProps.stringPropertyNames().stream().filter(p -> p.startsWith("jkube")).forEach(key -> { - String value = allProps.getProperty(key); - sbJKube.append(" <").append(key).append(">").append(value).append("</").append(key).append(">\n"); - }); - context = context.replaceFirst(Pattern.quote("{{ .jkubeProperties }}"), - Matcher.quoteReplacement(sbJKube.toString())); - if (repos == null || repos.isEmpty()) { context = context.replaceFirst("\\{\\{ \\.MavenRepositories }}", ""); } else { @@ -275,13 +262,6 @@ class ExportSpringBoot extends Export { } context = context.replaceFirst("\\{\\{ \\.CamelDependencies }}", sb.toString()); - // add jkube profiles if there is jkube version property - String jkubeProfiles = ""; - if (allProps.getProperty("jkube.version") != null) { - jkubeProfiles = readResourceTemplate("templates/jkube-profiles.tmpl"); - } - context = context.replaceFirst(Pattern.quote("{{ .jkubeProfiles }}"), Matcher.quoteReplacement(jkubeProfiles)); - IOHelper.writeText(context, new FileOutputStream(pom, false)); } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/jkube-profiles.tmpl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/jkube-profiles.tmpl deleted file mode 100644 index 40bcd01f6a0..00000000000 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/jkube-profiles.tmpl +++ /dev/null @@ -1,48 +0,0 @@ - <profile> - <id>kubernetes</id> - <build> - <plugins> - <plugin> - <groupId>org.eclipse.jkube</groupId> - <artifactId>kubernetes-maven-plugin</artifactId> - <version>${jkube.version}</version> - <configuration> - <resources> - <labels> - <all> - <property> - <name>app.kubernetes.io/runtime</name> - <value>camel</value> - </property> - </all> - </labels> - </resources> - </configuration> - </plugin> - </plugins> - </build> - </profile> - <profile> - <id>openshift</id> - <build> - <plugins> - <plugin> - <groupId>org.eclipse.jkube</groupId> - <artifactId>openshift-maven-plugin</artifactId> - <version>${jkube.version}</version> - <configuration> - <resources> - <labels> - <all> - <property> - <name>app.openshift.io/runtime</name> - <value>camel</value> - </property> - </all> - </labels> - </resources> - </configuration> - </plugin> - </plugins> - </build> - </profile> diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl index e016f531a14..af57b365aee 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl @@ -1,7 +1,7 @@ <plugin> <groupId>org.eclipse.jkube</groupId> <artifactId>kubernetes-maven-plugin</artifactId> - <version>{{ .JkubeMavenPluginVersion }}</version> + <version>{{ .JKubeMavenPluginVersion }}</version> <configuration> <images> <image> diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl index d1c7490e43f..894051b0701 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl @@ -75,36 +75,66 @@ <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> - <groupId>com.google.cloud.tools</groupId> - <artifactId>jib-maven-plugin</artifactId> + <groupId>org.eclipse.jkube</groupId> + <artifactId>kubernetes-maven-plugin</artifactId> + <version>${camel.springboot.jkube.version}</version> <configuration> - <from> - <image>eclipse-temurin:{{ .JavaVersion }}</image> - <platforms> - <platform> - <architecture>amd64</architecture> - <os>linux</os> - </platform> - <platform> - <architecture>arm64</architecture> - <os>linux</os> - </platform> - </platforms> - </from> - <to> - <image>${camel.springboot.kubernetes.image-name}</image> - </to> - <allowInsecureRegistries>${camel.springboot.container-image.insecure}</allowInsecureRegistries> + <imagePullPolicy>${camel.springboot.kubernetes.image-pull-policy}</imagePullPolicy> + <buildStrategy>docker</buildStrategy> + <images> + <image> + <name>${camel.springboot.kubernetes.image-name}</name> + <build> + <from>eclipse-temurin:{{ .JavaVersion }}</from> + <createImageOptions> + <platform>linux/amd64</platform> + <platform>linux/arm64</platform> + </createImageOptions> + <ports> + <port>${camel.springboot.kubernetes.ports.http.container-port}</port> + </ports> + <entryPoint> + <exec> + <arg>java</arg> + <arg>-jar</arg> + <arg>/maven/${project.artifactId}-${project.version}.jar</arg> + </exec> + </entryPoint> + </build> + </image> + </images> </configuration> <executions> <execution> <goals> - <goal>dockerBuild</goal> + <goal>build</goal> + <goal>resource</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <!-- For symmetry with Quarkus, copy kubernetes.yml to the same target location --> + <outputDirectory>${project.build.directory}/kubernetes</outputDirectory> + <resources> + <resource> + <directory>${project.build.directory}/classes/META-INF/jkube</directory> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> </plugins> </build> diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-pom.tmpl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-pom.tmpl index 843c2b47d43..c079cb37c1a 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-pom.tmpl +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-pom.tmpl @@ -16,7 +16,6 @@ <properties> <java.version>{{ .JavaVersion }}</java.version> -{{ .jkubeProperties }} {{ .AdditionalProperties }} </properties> @@ -79,7 +78,6 @@ </build> <profiles> -{{ .jkubeProfiles }} <profile> <id>camel.debug</id> <activation> diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java index 9fe494317f6..3c20e0b70b5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java @@ -20,22 +20,26 @@ package org.apache.camel.dsl.jbang.core.commands.kubernetes; import java.io.ByteArrayInputStream; import java.io.File; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Properties; import java.util.stream.Collectors; import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.Export; import org.apache.camel.dsl.jbang.core.commands.ExportBaseCommand; +import org.apache.camel.dsl.jbang.core.commands.Run; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.ContainerTrait; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitCatalog; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitContext; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitProfile; +import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.apache.camel.dsl.jbang.core.common.RuntimeType; import org.apache.camel.dsl.jbang.core.common.Source; import org.apache.camel.dsl.jbang.core.common.SourceHelper; @@ -139,9 +143,25 @@ public class KubernetesExport extends Export { runtime = RuntimeType.quarkus; } - Map<String, String> exportProps = new HashMap<>(); - String resolvedImageRegistry = resolveImageRegistry(); + // Init export properties + Map<String, Object> exportProps = new LinkedHashMap<>(); + if (additionalProperties != null) { + Arrays.stream(additionalProperties.split(",")) + .filter(item -> !item.isEmpty()) + .forEach(item -> { + String[] keyValue = item.split("="); + exportProps.put(keyValue[0], keyValue[1]); + }); + } + + String propPrefix; + if (runtime == RuntimeType.springBoot) { + propPrefix = "camel.springboot"; + } else { + propPrefix = runtime.runtime(); + } + // Resolve image group and registry String resolvedImageGroup = null; if (image != null) { resolvedImageGroup = extractImageGroup(image); @@ -153,30 +173,7 @@ public class KubernetesExport extends Export { resolvedImageGroup = dotToks[dotToks.length - 1]; } - if (runtime == RuntimeType.quarkus) { - - // Quarkus specific dependencies - addDependencies("io.quarkus:quarkus-kubernetes", "camel:cli-connector"); - - // TODO: remove when fixed kubernetes-client version is part of the Quarkus platform - // pin kubernetes-client to this version because of https://github.com/fabric8io/kubernetes-client/issues/6059 - addDependencies("io.fabric8:kubernetes-client:6.13.1"); - - // Mutually exclusive image build plugins - use Jib by default - if (!getDependenciesList().contains("io.quarkus:quarkus-container-image-docker")) { - addDependencies("io.quarkus:quarkus-container-image-jib"); - } - - // Quarkus specific properties - exportProps.put("quarkus.container-image.build", "true"); - } - - String propPrefix; - if (runtime == RuntimeType.springBoot) { - propPrefix = "camel.springboot"; - } else { - propPrefix = runtime.runtime(); - } + String resolvedImageRegistry = resolveImageRegistry(); if (resolvedImageGroup != null) { exportProps.put("%s.container-image.group".formatted(propPrefix), resolvedImageGroup); @@ -188,10 +185,6 @@ public class KubernetesExport extends Export { exportProps.put("%s.container-image.insecure".formatted(propPrefix), "%b".formatted(allowInsecure)); } - additionalProperties = Optional.ofNullable(additionalProperties).map(str -> str + ",").orElse(""); - additionalProperties += exportProps.entrySet().stream() - .map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue())).collect(Collectors.joining(",")); - String projectName = getProjectName(); CamelCatalog catalog = CatalogHelper.loadCatalog(runtime, runtime.version()); @@ -206,14 +199,22 @@ public class KubernetesExport extends Export { } TraitContext context = new TraitContext(projectName, getVersion(), printer(), catalog, sources); - if (annotations != null) { - context.addAnnotations(Arrays.stream(annotations) - .map(item -> item.split("=")) - .filter(parts -> parts.length == 2) - .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1]))); - } - labels = Optional.ofNullable(labels).orElse(new String[0]); + // Add annotations to TraitContext + // + annotations = Optional.ofNullable(annotations).orElse(new String[0]); + context.addAnnotations(Arrays.stream(annotations) + .map(item -> item.split("=")) + .filter(parts -> parts.length == 2) + .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1]))); + + // Add labels to TraitContext + // + // Generated by quarkus/jkube + // app.kubernetes.io/name + // app.kubernetes.io/version + // + addLabel("app.kubernetes.io/runtime", "camel"); context.addLabels(Arrays.stream(labels) .map(item -> item.split("=")) .filter(parts -> parts.length == 2) @@ -239,27 +240,58 @@ public class KubernetesExport extends Export { Container container = traitsSpec.getContainer(); + exportProps.put("%s.kubernetes.image-name".formatted(propPrefix), container.getImage()); + exportProps.put("%s.kubernetes.ports.%s.container-port".formatted(propPrefix, + Optional.ofNullable(container.getPortName()).orElse(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME)), + Optional.ofNullable(container.getPort()).orElse(ContainerTrait.DEFAULT_CONTAINER_PORT)); + // Need to set quarkus.container properties, otherwise these settings get overwritten by Quarkus if (container.getName() != null && !container.getName().equals(projectName)) { - additionalProperties += ",%s.kubernetes.container-name=%s".formatted(propPrefix, container.getName()); - } - if (container.getImage() != null) { - additionalProperties += ",%s.kubernetes.image-name=%s".formatted(propPrefix, container.getImage()); - } - if (container.getPort() != null) { - additionalProperties += "%s.kubernetes.ports.%s.container-port=%s".formatted(propPrefix, - Optional.ofNullable(container.getPortName()).orElse(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME), - container.getPort()); + exportProps.put("%s.kubernetes.container-name".formatted(propPrefix), container.getName()); } + if (container.getImagePullPolicy() != null) { var imagePullPolicy = container.getImagePullPolicy().getValue(); if (runtime == RuntimeType.quarkus) { imagePullPolicy = StringHelper.camelCaseToDash(imagePullPolicy); } - additionalProperties += ",%s.kubernetes.image-pull-policy=%s".formatted(propPrefix, imagePullPolicy); + exportProps.put("%s.kubernetes.image-pull-policy".formatted(propPrefix), imagePullPolicy); + } + + // Quarkus Runtime specific + if (runtime == RuntimeType.quarkus) { + + // Quarkus specific dependencies + addDependencies("io.quarkus:quarkus-kubernetes", "camel:cli-connector"); + + // TODO: remove when fixed kubernetes-client version is part of the Quarkus platform + // pin kubernetes-client to this version because of https://github.com/fabric8io/kubernetes-client/issues/6059 + addDependencies("io.fabric8:kubernetes-client:6.13.1"); + + // Mutually exclusive image build plugins - use Jib by default + if (!getDependenciesList().contains("io.quarkus:quarkus-container-image-docker")) { + addDependencies("io.quarkus:quarkus-container-image-jib"); + } + + // Quarkus specific properties + exportProps.put("quarkus.container-image.build", "true"); + } + + // SpringBoot Runtime specific + if (runtime == RuntimeType.springBoot) { + Properties props = mapToProperties(exportProps); + File settings = new File(CommandLineHelper.getWorkDir(), Run.RUN_SETTINGS_FILE); + + var jkubeVersion = jkubeMavenPluginVersion(settings, props); + exportProps.put("camel.springboot.jkube.version", jkubeVersion); } - // run export + // Setup additional properties + additionalProperties = exportProps.entrySet().stream() + .map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue())) + .collect(Collectors.joining(",")); + + // Run export int exit = super.export(); if (exit != 0) { if (!quiet) { @@ -268,15 +300,33 @@ public class KubernetesExport extends Export { return exit; } + // Post export processing + // Note, the resulting kubernetes.yml is tested but not used by springboot if (!quiet) { printer().println("Building Kubernetes manifest ..."); } new TraitCatalog().apply(traitsSpec, context, traitProfile); - String yaml = context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---\n")); - safeCopy(new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), - new File(exportDir + "/src/main/kubernetes/kubernetes.yml")); + var kubeFragments = context.buildItems().stream().map(KubernetesHelper::toJsonMap).toList(); + + // Quarkus: dump joined fragments to kubernetes.yml + if (runtime == RuntimeType.quarkus) { + var kubeManifest = kubeFragments.stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---\n")); + safeCopy(new ByteArrayInputStream(kubeManifest.getBytes(StandardCharsets.UTF_8)), + new File(exportDir + "/src/main/kubernetes/kubernetes.yml")); + } + + // SpringBoot: dump each fragment to its respective kind + if (runtime == RuntimeType.springBoot) { + for (var map : kubeFragments) { + var ymlFragment = KubernetesHelper.dumpYaml(map); + var kind = map.get("kind").toString().toLowerCase(); + safeCopy(new ByteArrayInputStream(ymlFragment.getBytes(StandardCharsets.UTF_8)), + new File(exportDir + "/src/main/jkube/%s.yml".formatted(kind))); + + } + } if (!quiet) { printer().println("Project export successful!"); @@ -305,6 +355,13 @@ public class KubernetesExport extends Export { return traitsSpec; } + private void addLabel(String key, String value) { + var labelArray = Optional.ofNullable(labels).orElse(new String[0]); + var labelList = new ArrayList<>(Arrays.asList(labelArray)); + labelList.add("%s=%s".formatted(key, value)); + labels = labelList.toArray(new String[0]); + } + private String resolveImageRegistry() { String resolvedImageRegistry = null; if (image != null) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java index 7accbf83990..4017599e640 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java @@ -135,8 +135,6 @@ public final class KubernetesHelper { /** * Overwrites the kubernetes client. Typically used by unit tests. - * - * @param kubernetesClient */ public static void setKubernetesClient(KubernetesClient kubernetesClient) { KubernetesHelper.kubernetesClient = kubernetesClient; @@ -145,11 +143,12 @@ public final class KubernetesHelper { /** * Dump given domain model object as YAML. Uses Json conversion to generic map as intermediate step. This makes sure * to properly write Json additional properties. - * - * @param model - * @return */ public static String dumpYaml(Object model) { return yaml().dumpAsMap(json().convertValue(model, Map.class)); } + + public static Map<String, Object> toJsonMap(Object model) { + return json().convertValue(model, Map.class); + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java index b7a8e24a929..d52d13d4ab0 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java @@ -29,6 +29,7 @@ import org.apache.camel.v1.integrationspec.traits.Container; public class ContainerTrait extends BaseTrait { public static final int CONTAINER_TRAIT_ORDER = 1600; + public static final long DEFAULT_CONTAINER_PORT = 8080; public static final String DEFAULT_CONTAINER_PORT_NAME = "http"; public ContainerTrait() { diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java index 85130f284bd..0f1ab7c61a2 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java @@ -25,6 +25,7 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -91,7 +92,7 @@ class KubernetesExportTest extends KubernetesBaseTest { int exit = command.doCall(); Assertions.assertEquals(0, exit); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals("route", deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL)); @@ -102,8 +103,8 @@ class KubernetesExportTest extends KubernetesBaseTest { Assertions.assertEquals("quay.io/camel-test/route:1.0-SNAPSHOT", deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); - Assertions.assertFalse(hasService(workingDir)); - Assertions.assertFalse(hasKnativeService(workingDir)); + Assertions.assertFalse(hasService(rt)); + Assertions.assertFalse(hasKnativeService(rt)); } @ParameterizedTest @@ -113,10 +114,10 @@ class KubernetesExportTest extends KubernetesBaseTest { "--image-group=camel-test", "--runtime=" + rt.runtime()); command.doCall(); - Assertions.assertTrue(hasService(workingDir)); - Assertions.assertFalse(hasKnativeService(workingDir)); + Assertions.assertTrue(hasService(rt)); + Assertions.assertFalse(hasKnativeService(rt)); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route-service", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals("camel-test/route-service:1.0-SNAPSHOT", @@ -127,7 +128,7 @@ class KubernetesExportTest extends KubernetesBaseTest { Assertions.assertEquals(8080, deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getPorts().get(0).getContainerPort()); - Service service = getService(workingDir); + Service service = getService(rt); Assertions.assertEquals("route-service", service.getMetadata().getName()); Assertions.assertEquals(1, service.getSpec().getPorts().size()); Assertions.assertEquals("http", service.getSpec().getPorts().get(0).getName()); @@ -152,9 +153,9 @@ class KubernetesExportTest extends KubernetesBaseTest { "container.limit-memory=512Mi" }; command.doCall(); - Assertions.assertTrue(hasService(workingDir)); + Assertions.assertTrue(hasService(rt)); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route-service", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals("camel-test/route-service:1.0.0", @@ -179,7 +180,7 @@ class KubernetesExportTest extends KubernetesBaseTest { deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getResources().getLimits().get("memory") .toString()); - Service service = getService(workingDir); + Service service = getService(rt); Assertions.assertEquals("route-service", service.getMetadata().getName()); Assertions.assertEquals(1, service.getSpec().getPorts().size()); Assertions.assertEquals("custom-port", service.getSpec().getPorts().get(0).getName()); @@ -204,10 +205,10 @@ class KubernetesExportTest extends KubernetesBaseTest { "knative-service.visibility=cluster-local" }; command.doCall(); - Assertions.assertFalse(hasService(workingDir)); - Assertions.assertTrue(hasKnativeService(workingDir)); + Assertions.assertFalse(hasService(rt)); + Assertions.assertTrue(hasKnativeService(rt)); - io.fabric8.knative.serving.v1.Service service = getResource(workingDir, io.fabric8.knative.serving.v1.Service.class) + io.fabric8.knative.serving.v1.Service service = getResource(rt, io.fabric8.knative.serving.v1.Service.class) .orElseThrow(() -> new RuntimeCamelException("Missing Knative service in Kubernetes manifest")); Assertions.assertEquals("route-service", service.getMetadata().getName()); @@ -240,7 +241,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.volumes = new String[] { "pvc-foo:/container/path/foo", "pvc-bar:/container/path/bar" }; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals(2, @@ -269,7 +270,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.envVars = new String[] { "CAMEL_FOO=bar", "MY_ENV=foo" }; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals(2, deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().size()); @@ -290,7 +291,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.annotations = new String[] { "foo=bar" }; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getMetadata().getAnnotations().size()); Assertions.assertEquals("bar", deployment.getMetadata().getAnnotations().get("foo")); @@ -303,11 +304,13 @@ class KubernetesExportTest extends KubernetesBaseTest { "--label=foo=bar", "--runtime=" + rt.runtime()); command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); + Map<String, String> labels = deployment.getMetadata().getLabels(); Assertions.assertEquals("route", deployment.getMetadata().getName()); - Assertions.assertEquals(2, deployment.getMetadata().getLabels().size()); - Assertions.assertEquals("route", deployment.getMetadata().getLabels().get("camel.apache.org/integration")); - Assertions.assertEquals("bar", deployment.getMetadata().getLabels().get("foo")); + Assertions.assertEquals(3, labels.size()); + Assertions.assertEquals("camel", labels.get("app.kubernetes.io/runtime")); + Assertions.assertEquals("route", labels.get("camel.apache.org/integration")); + Assertions.assertEquals("bar", labels.get("foo")); } @ParameterizedTest @@ -317,7 +320,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.configs = new String[] { "secret:foo", "configmap:bar" }; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals(2, @@ -343,7 +346,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.resources = new String[] { "configmap:foo/file.txt" }; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals(1, @@ -363,7 +366,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.openApis = new String[] { "configmap:openapi/spec.yaml" }; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals(1, @@ -383,7 +386,7 @@ class KubernetesExportTest extends KubernetesBaseTest { command.image = "quay.io/camel/demo-app:1.0"; command.doCall(); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(rt); Assertions.assertEquals("demo-app", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals("quay.io/camel/demo-app:1.0", @@ -399,32 +402,48 @@ class KubernetesExportTest extends KubernetesBaseTest { return command; } - private Deployment getDeployment(File workingDir) throws IOException { - return getResource(workingDir, Deployment.class) - .orElseThrow(() -> new RuntimeCamelException("Missing deployment in Kubernetes manifest")); + private Deployment getDeployment(RuntimeType rt) throws IOException { + return getResource(rt, Deployment.class) + .orElseThrow(() -> new RuntimeCamelException("Cannot find deployment for: %s".formatted(rt.runtime()))); } - private Service getService(File workingDir) throws IOException { - return getResource(workingDir, Service.class) - .orElseThrow(() -> new RuntimeCamelException("Missing service in Kubernetes manifest")); + private Service getService(RuntimeType rt) throws IOException { + return getResource(rt, Service.class) + .orElseThrow(() -> new RuntimeCamelException("Cannot find service for: %s".formatted(rt.runtime()))); } - private boolean hasService(File workingDir) throws IOException { - return getResource(workingDir, Service.class).isPresent(); + private boolean hasService(RuntimeType rt) throws IOException { + return getResource(rt, Service.class).isPresent(); } - private boolean hasKnativeService(File workingDir) throws IOException { - return getResource(workingDir, io.fabric8.knative.serving.v1.Service.class).isPresent(); + private boolean hasKnativeService(RuntimeType rt) throws IOException { + return getResource(rt, io.fabric8.knative.serving.v1.Service.class).isPresent(); } - private <T extends HasMetadata> Optional<T> getResource(File workingDir, Class<T> type) throws IOException { - try (FileInputStream fis = new FileInputStream(new File(workingDir, "src/main/kubernetes/kubernetes.yml"))) { - List<HasMetadata> resources = kubernetesClient.load(fis).items(); - return resources.stream() - .filter(it -> type.isAssignableFrom(it.getClass())) - .map(type::cast) - .findFirst(); + private <T extends HasMetadata> Optional<T> getResource(RuntimeType rt, Class<T> type) throws IOException { + if (rt == RuntimeType.quarkus) { + try (FileInputStream fis = new FileInputStream(new File(workingDir, "src/main/kubernetes/kubernetes.yml"))) { + List<HasMetadata> resources = kubernetesClient.load(fis).items(); + return resources.stream() + .filter(it -> type.isAssignableFrom(it.getClass())) + .map(type::cast) + .findFirst(); + } } + if (rt == RuntimeType.springBoot) { + var kind = type.getSimpleName().toLowerCase(); + File file = new File(workingDir, "src/main/jkube/%s.yml".formatted(kind)); + if (file.isFile()) { + try (FileInputStream fis = new FileInputStream(file)) { + List<HasMetadata> resources = kubernetesClient.load(fis).items(); + return resources.stream() + .filter(it -> type.isAssignableFrom(it.getClass())) + .map(type::cast) + .findFirst(); + } + } + } + return Optional.empty(); } private Model readMavenModel() throws Exception {