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 {


Reply via email to