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("@", "");