This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 421aba16cd56 chore: fix various bugs in Kubernetes plugin
421aba16cd56 is described below

commit 421aba16cd56df8a0a70a2107905d650e9f90cdc
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 10 19:31:13 2026 +0200

    chore: fix various bugs in Kubernetes plugin
    
    - KubernetesDelete: fail fast when name is null to prevent deleting all
      labeled resources in namespace
    - KubernetesRun: use Boolean.TRUE.equals() for knative-service and
      cronjob enabled checks to prevent NPE on null Boolean unboxing
    - KubernetesRun: propagate namespace to delete commands in dev-mode
      reload and cleanup shutdown hook
    - KubernetesRun: null-check configmap/secret get() to prevent NPE
      when referencing missing resources
    - KubernetesHelper: fix getKubernetesClient(String) returning null
      on first call (clients.put returns previous mapping)
    - KubernetesHelper: null-check Knative config-deployment ConfigMap
      in skipKnativeImageTagResolutionInMinikube
    - KubernetesHelper: add imageWithoutTag/imageTag helpers for proper
      image name parsing with registries containing ports
    - KubernetesHelper: fix sanitize() to strip leading/trailing dashes
      and truncate to 63-char Kubernetes name limit
    - KubernetesBaseCommand: wire --kube-config option through client()
    - KubernetesExport: remove duplicate addAnnotations block, use
      split("=", 2) for annotations/labels to preserve values with =
    - MountTrait: null-safe key extraction in createConfig to prevent NPE
      when optional /key segment is omitted
    - EnvTrait: validate env var format before accessing parts[1]
    - KubernetesPodLogs: return error code after invalid label syntax
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../commands/kubernetes/KubernetesBaseCommand.java | 14 ++++++-
 .../core/commands/kubernetes/KubernetesDelete.java |  4 ++
 .../core/commands/kubernetes/KubernetesExport.java | 21 +++++------
 .../core/commands/kubernetes/KubernetesHelper.java | 43 +++++++++++++++++++++-
 .../commands/kubernetes/KubernetesPodLogs.java     |  3 +-
 .../core/commands/kubernetes/KubernetesRun.java    | 34 ++++++++++++++---
 .../core/commands/kubernetes/traits/EnvTrait.java  |  6 +++
 .../commands/kubernetes/traits/MountTrait.java     |  2 +-
 8 files changed, 103 insertions(+), 24 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java
index 40d25b847124..1a2c6d3cf412 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java
@@ -17,6 +17,9 @@
 
 package org.apache.camel.dsl.jbang.core.commands.kubernetes;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -36,7 +39,6 @@ import 
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.common.SourceScheme;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.StringHelper;
 import picocli.CommandLine;
 
 /**
@@ -69,7 +71,7 @@ public abstract class KubernetesBaseCommand extends 
CamelCommand {
     }
 
     protected String projectNameFromImage(Supplier<String> imageSupplier) {
-        return 
KubernetesHelper.sanitize(StringHelper.beforeLast(imageSupplier.get(), ":"));
+        return 
KubernetesHelper.sanitize(KubernetesHelper.imageWithoutTag(imageSupplier.get()));
     }
 
     protected String projectNameFromGav(Supplier<String> gavSupplier) {
@@ -126,6 +128,14 @@ public abstract class KubernetesBaseCommand extends 
CamelCommand {
      * uses default client.
      */
     protected KubernetesClient client() {
+        if (kubeConfig != null) {
+            try {
+                String content = Files.readString(Path.of(kubeConfig));
+                return KubernetesHelper.getKubernetesClient(content);
+            } catch (IOException e) {
+                throw new RuntimeCamelException("Failed to read kube config 
file: " + kubeConfig, e);
+            }
+        }
         return KubernetesHelper.getKubernetesClient();
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java
index 0ac26cda4ca4..5b2044a62451 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java
@@ -43,6 +43,10 @@ public class KubernetesDelete extends KubernetesBaseCommand {
     }
 
     public Integer doCall() throws Exception {
+        if (name == null || name.isBlank()) {
+            printer().printErr("Missing required --name option or integration 
name");
+            return 1;
+        }
         var client = getKubernetesClient();
         namespace = 
Optional.ofNullable(namespace).orElse(client.getNamespace());
         namespace = Optional.ofNullable(namespace).orElse("default");
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 c192ca8d92a7..8baf5bc56aa1 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
@@ -56,7 +56,6 @@ import org.apache.camel.dsl.jbang.core.common.SourceHelper;
 import org.apache.camel.tooling.maven.MavenGav;
 import org.apache.camel.util.CamelCaseOrderedProperties;
 import org.apache.camel.util.FileUtil;
-import org.apache.camel.util.StringHelper;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 
@@ -264,13 +263,7 @@ public class KubernetesExport extends Export {
         //
         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])));
-
-        annotations = Optional.ofNullable(annotations).orElse(new String[0]);
-        context.addAnnotations(Arrays.stream(annotations)
-                .map(item -> item.split("="))
+                .map(item -> item.split("=", 2))
                 .filter(parts -> parts.length == 2)
                 .collect(Collectors.toMap(parts -> parts[0], parts -> 
parts[1])));
 
@@ -287,7 +280,7 @@ public class KubernetesExport extends Export {
         }
         if (labels != null) {
             context.addLabels(Arrays.stream(labels)
-                    .map(item -> item.split("="))
+                    .map(item -> item.split("=", 2))
                     .filter(parts -> parts.length == 2)
                     .collect(Collectors.toMap(parts -> parts[0], parts -> 
parts[1])));
         }
@@ -391,7 +384,8 @@ public class KubernetesExport extends Export {
         var managementPort = httpManagementPort(settingsPath);
         buildProperties.add("jkube.version=%s".formatted(jkubeVersion));
 
-        boolean cronJobEnabled = traitsSpec.getCronjob() != null && 
traitsSpec.getCronjob().getEnabled();
+        boolean cronJobEnabled = traitsSpec.getCronjob() != null
+                && Boolean.TRUE.equals(traitsSpec.getCronjob().getEnabled());
         if (cronJobEnabled) {
             // set this property to allow the JVM to finish quickly once there 
are no more exchange messages
             // important for cronjobs so that the jvm can end quickly
@@ -603,14 +597,17 @@ public class KubernetesExport extends Export {
             return KubernetesHelper.sanitize(name);
         }
         if (image != null) {
-            return KubernetesHelper.sanitize(StringHelper.beforeLast(image, 
":"));
+            return 
KubernetesHelper.sanitize(KubernetesHelper.imageWithoutTag(image));
         }
         return KubernetesHelper.sanitize(super.getProjectName());
     }
 
     protected String getVersion() {
         if (image != null) {
-            return StringHelper.afterLast(image, ":");
+            String tag = KubernetesHelper.imageTag(image);
+            if (tag != null) {
+                return tag;
+            }
         }
         return super.getVersion();
     }
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 276e3745f4cc..e5a3c3693b07 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
@@ -97,7 +97,8 @@ public final class KubernetesHelper {
         }
         setKubernetesClientProperties();
         var client = new KubernetesClientBuilder().withConfig(config).build();
-        return clients.put(config, client);
+        clients.put(config, client);
+        return client;
     }
 
     // set short timeouts to fail fast in case it's not connected to a cluster 
and don't waste time
@@ -198,6 +199,9 @@ public final class KubernetesHelper {
     // 
https://knative.dev/docs/serving/configuration/deployment/#skipping-tag-resolution
     public static void skipKnativeImageTagResolutionInMinikube() {
         ConfigMap cm = 
getKubernetesClient().configMaps().inNamespace("knative-serving").withName("config-deployment").get();
+        if (cm == null) {
+            return;
+        }
         Map<String, String> data = cm.getData();
         String skipTag = data.get("registries-skipping-tag-resolving");
         if (skipTag == null || !skipTag.contains("localhost:5000")) {
@@ -221,11 +225,46 @@ public final class KubernetesHelper {
             name = StringHelper.camelCaseToDash(name);
             name = name.toLowerCase(Locale.US);
             name = name.replaceAll("[^a-z0-9-]", "");
-            name = name.trim();
+            name = name.replaceAll("^-+|-+$", "");
+            if (name.length() > 63) {
+                name = name.substring(0, 63).replaceAll("-+$", "");
+            }
         }
         return name;
     }
 
+    /**
+     * Extracts the image name without the tag from a container image 
reference. Handles registries with ports (e.g.
+     * localhost:5000/myapp).
+     */
+    public static String imageWithoutTag(String image) {
+        if (image == null) {
+            return null;
+        }
+        int lastSlash = image.lastIndexOf('/');
+        int tagColon = image.indexOf(':', lastSlash + 1);
+        if (tagColon >= 0) {
+            return image.substring(0, tagColon);
+        }
+        return image;
+    }
+
+    /**
+     * Extracts the tag from a container image reference. Returns null if no 
tag is present. Handles registries with
+     * ports (e.g. localhost:5000/myapp:1.0).
+     */
+    public static String imageTag(String image) {
+        if (image == null) {
+            return null;
+        }
+        int lastSlash = image.lastIndexOf('/');
+        int tagColon = image.indexOf(':', lastSlash + 1);
+        if (tagColon >= 0) {
+            return image.substring(tagColon + 1);
+        }
+        return null;
+    }
+
     /**
      * Overwrites the kubernetes client. Typically used by unit tests.
      */
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesPodLogs.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesPodLogs.java
index 488761fe3922..97f3614842d9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesPodLogs.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesPodLogs.java
@@ -74,7 +74,8 @@ public class KubernetesPodLogs extends KubernetesBaseCommand {
 
         String[] parts = label.split("=", 2);
         if (parts.length != 2) {
-            printer().println("--label selector must be in syntax: key=value");
+            printer().printErr("--label selector must be in syntax: 
key=value");
+            return 1;
         }
 
         var retry = watchLogs();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
index a8651d4642b3..80b0ab78749b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
@@ -342,7 +342,8 @@ public class KubernetesRun extends KubernetesBaseCommand {
 
         String workingDir = getIndexedWorkingDir(projectName);
         KubernetesExport export = configureExport(workingDir, baseDir);
-        boolean cronEnabled = computedTraits.getCronjob() != null && 
computedTraits.getCronjob().getEnabled();
+        boolean cronEnabled = computedTraits.getCronjob() != null
+                && 
Boolean.TRUE.equals(computedTraits.getCronjob().getEnabled());
         if (cronEnabled) {
             // disable observability-services as CronJob doesn't use the 
container probes
             export.setObserve(false);
@@ -354,7 +355,8 @@ public class KubernetesRun extends KubernetesBaseCommand {
         }
 
         if (output != null) {
-            boolean ksvcEnabled = computedTraits.getKnativeService() != null 
&& computedTraits.getKnativeService().getEnabled();
+            boolean ksvcEnabled = computedTraits.getKnativeService() != null
+                    && 
Boolean.TRUE.equals(computedTraits.getKnativeService().getEnabled());
 
             exit = buildProjectOutput(workingDir);
             if (exit != 0) {
@@ -551,6 +553,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
                     //
                     KubernetesDelete deleteCommand = new 
KubernetesDelete(getMain());
                     deleteCommand.name = projectName;
+                    deleteCommand.namespace = namespace;
                     deleteCommand.doCall();
 
                     // Re-deploy updated project
@@ -620,6 +623,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
         devModeShutdownTask = new Thread(() -> {
             KubernetesDelete deleteCommand = new KubernetesDelete(getMain());
             deleteCommand.name = projectName;
+            deleteCommand.namespace = namespace;
             try (var client = createKubernetesClientForShutdownHook()) {
                 KubernetesHelper.setKubernetesClient(client);
                 deleteCommand.doCall();
@@ -676,7 +680,8 @@ public class KubernetesRun extends KubernetesBaseCommand {
         // suppress maven transfer progress
         args.add("-ntp");
 
-        if (computedTraits.getKnativeService() != null && 
computedTraits.getKnativeService().getEnabled()) {
+        if (computedTraits.getKnativeService() != null
+                && 
Boolean.TRUE.equals(computedTraits.getKnativeService().getEnabled())) {
             // by default jkube creates a Deployment manifest and it doesn't 
support knative controller yet.
             // however when knative-service is enabled the knative-service 
trait generates a src/main/jkube/service.yml
             // and there is no need for the regular Deployment as the knative 
Service manifest, once deployed
@@ -745,7 +750,8 @@ public class KubernetesRun extends KubernetesBaseCommand {
 
         boolean isOpenshift = ClusterType.OPENSHIFT.isEqualTo(clusterType);
         var prefix = isOpenshift ? "oc" : "k8s";
-        if (computedTraits.getKnativeService() != null && 
computedTraits.getKnativeService().getEnabled()) {
+        if (computedTraits.getKnativeService() != null
+                && 
Boolean.TRUE.equals(computedTraits.getKnativeService().getEnabled())) {
             // by default jkube creates a Deployment manifest and it doesn't 
support knative controller yet.
             // however when knative-service is enabled the knative-service 
trait generates a src/main/jkube/service.yml
             // and there is no need for the regular Deployment as the knative 
Service manifest, once deployed
@@ -868,7 +874,15 @@ public class KubernetesRun extends KubernetesBaseCommand {
         if (namespace != null || client.getNamespace() != null) {
             ns = namespace != null ? namespace : client.getNamespace();
         }
-        Map<String, String> data = 
client.configMaps().inNamespace(ns).withName(name).get().getData();
+        var cm = client.configMaps().inNamespace(ns).withName(name).get();
+        if (cm == null) {
+            printer().printf("ConfigMap '%s' not found in namespace '%s'%n", 
name, ns);
+            return Set.of();
+        }
+        Map<String, String> data = cm.getData();
+        if (data == null) {
+            return Set.of();
+        }
         return data.keySet();
     }
 
@@ -878,7 +892,15 @@ public class KubernetesRun extends KubernetesBaseCommand {
         if (namespace != null || client.getNamespace() != null) {
             ns = namespace != null ? namespace : client.getNamespace();
         }
-        Map<String, String> data = 
client.secrets().inNamespace(ns).withName(name).get().getData();
+        var secret = client.secrets().inNamespace(ns).withName(name).get();
+        if (secret == null) {
+            printer().printf("Secret '%s' not found in namespace '%s'%n", 
name, ns);
+            return Set.of();
+        }
+        Map<String, String> data = secret.getData();
+        if (data == null) {
+            return Set.of();
+        }
         return data.keySet();
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/EnvTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/EnvTrait.java
index db7fb1c264d8..f7556cc88b68 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/EnvTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/EnvTrait.java
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
 
 import io.fabric8.kubernetes.api.model.EnvVarBuilder;
 import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
+import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Environment;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits;
 import org.apache.camel.util.ObjectHelper;
@@ -55,6 +56,11 @@ public class EnvTrait extends BaseTrait {
                             .editFirstContainer()
                             
.addAllToEnv(envTrait.getVars().stream().map(envvar -> {
                                 String[] parts = envvar.split("=", 2);
+                                if (parts.length != 2) {
+                                    throw new RuntimeCamelException(
+                                            "Invalid environment variable 
'%s', must be in NAME=value format"
+                                                    .formatted(envvar));
+                                }
                                 return new EnvVarBuilder()
                                         .withName(parts[0])
                                         .withValue(parts[1])
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/MountTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/MountTrait.java
index 933afa8cb450..7738a1e649b5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/MountTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/MountTrait.java
@@ -250,7 +250,7 @@ public class MountTrait extends BaseTrait {
         Matcher resourceCoordinates = 
RESOURCE_VALUE_EXPRESSION.matcher(expression);
         if (resourceCoordinates.matches()) {
             name = resourceCoordinates.group(1);
-            key = resourceCoordinates.group(3);
+            key = Optional.ofNullable(resourceCoordinates.group(3)).orElse("");
             destPath = resourceCoordinates.group(4);
             if (!Strings.isNullOrEmpty(destPath)) {
                 destPath = destPath.replace("@", "");

Reply via email to