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 94dc40783305 CAMEL-25454: camel-jbang - kubernetes run, k3s manifest 
with --output and explicit image options with --cluster-type (#27591)
94dc40783305 is described below

commit 94dc4078330547021ac7f7da20a809acc43b4c13
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 10 01:20:45 2026 +0530

    CAMEL-25454: camel-jbang - kubernetes run, k3s manifest with --output and 
explicit image options with --cluster-type (#27591)
    
    Follow-up to CAMEL-24662: --cluster-type=k3s with --output reads the
    kubernetes.yml manifest built by the kubernetes-maven-plugin. The Minikube
    defaults no longer replace an --image-builder or --image-push set by the 
user,
    the no-push default applies only with the docker builder (jib falls back to
    pushing), and the docker-env hint is printed once and only when an image is
    built.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   8 +
 .../modules/ROOT/pages/camel-jbang-kubernetes.adoc |  13 +-
 .../jbang-commands/camel-jbang-kubernetes-run.adoc |   4 +-
 .../camel-jbang-plugin-commands-metadata.json      |   2 +-
 .../core/commands/kubernetes/KubernetesHelper.java |  20 ++-
 .../core/commands/kubernetes/KubernetesRun.java    |  50 ++++--
 .../commands/kubernetes/KubernetesHelperTest.java  |  71 ++++++++
 .../kubernetes/KubernetesRunClusterTypeTest.java   | 196 +++++++++++++++++++++
 .../kubernetes/KubernetesRunCustomTest.java        |  35 +++-
 9 files changed, 368 insertions(+), 31 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 0652619a5217..adfdf36b2044 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2122,6 +2122,14 @@ validated before can now report errors, and 
`camel_write_file` refuses to write
 TUI Source tab an XML route file with such problems is not saved, as a YAML
 file; a Java file is saved and its problems are shown.
 
+`camel kubernetes run --cluster-type=<type>` now uses the given cluster type. 
Before, unless `--disable-auto` was set,
+the detected cluster type (OpenShift, Minikube or Kubernetes) replaced it, 
together with its image settings. The
+defaults of the cluster type still apply, also to an explicit type (Minikube: 
`--image-builder=docker` and, with the
+docker builder, `--image-push=false`; OpenShift: the current namespace as 
`--image-group`), but they no longer replace
+an `--image-builder` or `--image-push` that you set, also when Minikube is 
detected. With another builder on Minikube,
+such as `--image-builder=jib`, the image is pushed by default. 
`--disable-auto` turns off both the
+detection and the defaults, as before.
+
 === camel-jbang (MCP servers)
 
 The Camel authoring tools for AI agents are now defined once, in 
`camel-jbang-core`, and exposed under the
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
index 094c55ed5b79..70206c8308c2 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
@@ -29,8 +29,10 @@ camel kubernetes run * --runtime=spring-boot --name=my-app
 This exports a Spring Boot project named `my-app`, builds the container image, 
pushes it, and deploys it to the cluster
 `kubectl` is connected to. The runtime is one of `spring-boot`, `quarkus` (the 
default) or `main`.
 
-Local clusters (Kind, Minikube) and OpenShift are detected, and the image 
registry is configured for them.
-Use `--cluster-type` to set the cluster type explicitly, skipping 
auto-detection while still applying the cluster-specific defaults.
+Minikube and OpenShift are detected, and the image build and registry are 
configured for them.
+Use `--cluster-type` to set the cluster type explicitly (for example 
`--cluster-type=kind`, which is not detected),
+skipping auto-detection while still applying the cluster-specific defaults.
+The image options you set yourself, such as `--image-builder` and 
`--image-push`, are kept.
 For any other cluster, tell the plugin where to push the image:
 
 [source,bash]
@@ -57,7 +59,7 @@ Useful options of `run`:
 | Print the logs once the application has started.
 
 | --disable-auto
-| Do not detect the cluster type (Kind, Minikube, OpenShift).
+| Do not detect the cluster type (Minikube, OpenShift), and do not apply the 
cluster-specific defaults.
 
 | --verbose
 | Show the output of the Maven build and the deployment.
@@ -288,7 +290,10 @@ eval $(minikube -p minikube docker-env)
 camel kubernetes run * --runtime=spring-boot
 ----
 
-On Minikube the image is built with Docker straight into the Minikube Docker 
daemon, so nothing is pushed.
+On Minikube the image is built with Docker straight into the Minikube Docker 
daemon, so nothing is pushed
+(unless you set `--image-push`). If you set another `--image-builder`, such as 
`jib`, the image is pushed as on any other cluster.
+Minikube is only detected when its Docker environment is active (`eval 
$(minikube docker-env)`);
+with `--cluster-type=minikube`, `run` prints a hint when it is not.
 
 == Troubleshooting
 
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-kubernetes-run.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-kubernetes-run.adoc
index f9abdf15fbef..b70363a1d899 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-kubernetes-run.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-kubernetes-run.adoc
@@ -39,10 +39,10 @@ camel kubernetes run [options]
 | `--gav` | The Maven group:artifact:version |  | String
 | `--image` | The image name to be built. |  | String
 | `--image-build` | Whether to build container image as part of the run. | 
true | boolean
-| `--image-builder` | The image builder used to build the container image 
(e.g. docker, jib, s2i). | jib | String
+| `--image-builder` | The image builder used to build the container image 
(e.g. docker, jib, s2i). Default is jib, or docker on Minikube unless 
--disable-auto is set. |  | String
 | `--image-group` | The image registry group used to push images to. |  | 
String
 | `--image-platform` | List of target platforms. Each platform is defined 
using os and architecture (e.g. linux/amd64). |  | String
-| `--image-push` | Whether to push image to given image registry as part of 
the run. | true | boolean
+| `--image-push` | Whether to push image to given image registry as part of 
the run. Default is true, or false on Minikube with the docker image builder 
unless --disable-auto is set. |  | Boolean
 | `--image-registry` | The image registry to hold the app container image. |  
| String
 | `--java-version,--java` | Java version | 21 | String
 | `--kamelets-version` | Apache Camel Kamelets version |  | String
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/generated/resources/META-INF/camel-jbang-plugin-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/generated/resources/META-INF/camel-jbang-plugin-commands-metadata.json
index 3af001de5e32..eaad31dec933 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/generated/resources/META-INF/camel-jbang-plugin-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/generated/resources/META-INF/camel-jbang-plugin-commands-metadata.json
@@ -1,5 +1,5 @@
 {
   "commands": [
-    { "name": "kubernetes", "fullName": "kubernetes", "description": "Manage 
Camel applications running on Kubernetes", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesCommand", 
"plugin": "kubernetes", "options": [ { "names": "--kube-config", "description": 
"Path to the kube config file to initialize Kubernetes client", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--name", "description": 
"The integration name. Use this when the name should [...]
+    { "name": "kubernetes", "fullName": "kubernetes", "description": "Manage 
Camel applications running on Kubernetes", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesCommand", 
"plugin": "kubernetes", "options": [ { "names": "--kube-config", "description": 
"Path to the kube config file to initialize Kubernetes client", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--name", "description": 
"The integration name. Use this when the name should [...]
   ]
 }
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 e5a3c3693b07..c5f68c743634 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
@@ -176,10 +176,7 @@ public final class KubernetesHelper {
             GenericKubernetesResourceList list = 
getKubernetesClient().genericKubernetesResources(nodecrd)
                     
.withLabels(Collections.singletonMap("minikube.k8s.io/name", null)).list();
             minikube = list.getItems().size() > 0;
-            // thse env properties are set when running eval $(minikube 
docker-env) in the console
-            // this is important for the docker builder to actually build the 
image in the exposed docker from the minikube registry
-            minikubeEnv = System.getenv("MINIKUBE_ACTIVE_DOCKERD") != null
-                    && System.getenv("DOCKER_TLS_VERIFY") != null;
+            minikubeEnv = isMinikubeDockerEnv();
             if (minikube && !minikubeEnv) {
                 System.out.println(
                         "It seems you have minikube running but forgot to run 
\"eval $(minikube docker-env)\", default cluster to kubernetes.");
@@ -190,6 +187,15 @@ public final class KubernetesHelper {
         return minikube && minikubeEnv;
     }
 
+    /**
+     * Whether the Minikube Docker environment is active in this console, 
which is what {@code eval $(minikube
+     * docker-env)} sets. The docker image builder needs it to build the image 
in the Docker daemon of Minikube.
+     */
+    static boolean isMinikubeDockerEnv() {
+        return System.getenv("MINIKUBE_ACTIVE_DOCKERD") != null
+                && System.getenv("DOCKER_TLS_VERIFY") != null;
+    }
+
     // when minikube is used with the registry addon exposed
     // the build of images uses docker builder directly from the registry 
inside minikube
     // that doesn't generate the container image digest in the registry
@@ -341,7 +347,8 @@ public final class KubernetesHelper {
 
     public static File getKubernetesManifest(String clusterType, File 
workingDir, String extension) {
         String manifestFile;
-        if (ClusterType.KIND.isEqualTo(clusterType) || 
ClusterType.MINIKUBE.isEqualTo(clusterType)) {
+        if (ClusterType.KIND.isEqualTo(clusterType) || 
ClusterType.MINIKUBE.isEqualTo(clusterType)
+                || ClusterType.K3S.isEqualTo(clusterType)) {
             manifestFile = "kubernetes";
         } else {
             manifestFile = 
Optional.ofNullable(clusterType).map(String::toLowerCase).orElse("kubernetes");
@@ -355,7 +362,8 @@ public final class KubernetesHelper {
 
     public static Path getKubernetesManifestPath(String clusterType, Path 
workingDir, String extension) {
         String manifestFile;
-        if (ClusterType.KIND.isEqualTo(clusterType) || 
ClusterType.MINIKUBE.isEqualTo(clusterType)) {
+        if (ClusterType.KIND.isEqualTo(clusterType) || 
ClusterType.MINIKUBE.isEqualTo(clusterType)
+                || ClusterType.K3S.isEqualTo(clusterType)) {
             manifestFile = "kubernetes";
         } else {
             manifestFile = 
Optional.ofNullable(clusterType).map(String::toLowerCase).orElse("kubernetes");
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 5b16e24f4516..91ff84200ee2 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
@@ -156,9 +156,11 @@ public class KubernetesRun extends KubernetesBaseCommand {
                         description = "The image registry group used to push 
images to.")
     String imageGroup;
 
-    @CommandLine.Option(names = { "--image-builder" }, defaultValue = "jib",
-                        description = "The image builder used to build the 
container image (e.g. docker, jib, s2i).")
-    String imageBuilder = "jib";
+    // no default value, so that detectCluster() can tell an explicit value 
from the default
+    @CommandLine.Option(names = { "--image-builder" },
+                        description = "The image builder used to build the 
container image (e.g. docker, jib, s2i)."
+                                      + " Default is jib, or docker on 
Minikube unless --disable-auto is set.")
+    String imageBuilder;
 
     @CommandLine.Option(names = { "--cluster-type" },
                         completionCandidates = 
ClusterTypeCompletionCandidates.class,
@@ -170,9 +172,11 @@ public class KubernetesRun extends KubernetesBaseCommand {
                         description = "Whether to build container image as 
part of the run.")
     boolean imageBuild = true;
 
-    @CommandLine.Option(names = { "--image-push" }, defaultValue = "true",
-                        description = "Whether to push image to given image 
registry as part of the run.")
-    boolean imagePush = true;
+    // no default value, so that detectCluster() can tell an explicit value 
from the default
+    @CommandLine.Option(names = { "--image-push" },
+                        description = "Whether to push image to given image 
registry as part of the run."
+                                      + " Default is true, or false on 
Minikube with the docker image builder unless --disable-auto is set.")
+    Boolean imagePush;
 
     @CommandLine.Option(names = { "--image-platform" },
                         description = "List of target platforms. Each platform 
is defined using os and architecture (e.g. linux/amd64).")
@@ -283,6 +287,8 @@ public class KubernetesRun extends KubernetesBaseCommand {
     private CamelContext devModeContext;
     private Thread devModeShutdownTask;
     private int devModeReloadCount;
+    // detectCluster() runs again on every dev mode reload, the Minikube 
docker-env hint is printed only once
+    private boolean dockerEnvHintPrinted;
 
     private KubernetesPodLogs reusablePodLogs;
     private Printer quietPrinter;
@@ -806,8 +812,9 @@ public class KubernetesRun extends KubernetesBaseCommand {
         return 0;
     }
 
-    private void detectCluster() {
-        if (!disableAuto && clusterType == null) {
+    void detectCluster() {
+        boolean explicitClusterType = clusterType != null;
+        if (!disableAuto && !explicitClusterType) {
             if (verbose) {
                 printer().print("Automatic Kubernetes cluster detection... ");
             }
@@ -820,16 +827,37 @@ public class KubernetesRun extends KubernetesBaseCommand {
             this.clusterType = ClusterType.KUBERNETES.name();
         }
         if (!disableAuto) {
-            // Apply per-cluster defaults for the resolved cluster type 
(explicit or detected)
+            // Apply per-cluster defaults for the resolved cluster type 
(explicit or detected),
+            // but keep the image options the user has set
             if (ClusterType.MINIKUBE.isEqualTo(clusterType)) {
-                this.imageBuilder = "docker";
-                this.imagePush = false;
+                if (imageBuilder == null) {
+                    this.imageBuilder = "docker";
+                }
+                // the docker builder builds straight into the Minikube Docker 
daemon, so there is nothing to push;
+                // other builders (jib) still push, as the image would 
otherwise not reach the cluster
+                if (imagePush == null && "docker".equals(imageBuilder)) {
+                    this.imagePush = false;
+                }
+                // the detection only returns Minikube when its Docker 
environment is active, and tells the user otherwise
+                if (explicitClusterType && !dockerEnvHintPrinted && imageBuild 
&& output == null
+                        && "docker".equals(imageBuilder)
+                        && Boolean.FALSE.equals(imagePush) && 
!KubernetesHelper.isMinikubeDockerEnv()) {
+                    printer().println("The Minikube Docker environment is not 
active: run \"eval $(minikube docker-env)\""
+                                      + " so that the image is built in the 
Docker daemon of Minikube.");
+                    dockerEnvHintPrinted = true;
+                }
             } else if (ClusterType.OPENSHIFT.isEqualTo(clusterType)) {
                 if (ObjectHelper.isEmpty(imageGroup)) {
                     this.imageGroup = client().getNamespace();
                 }
             }
         }
+        if (imageBuilder == null) {
+            this.imageBuilder = "jib";
+        }
+        if (imagePush == null) {
+            this.imagePush = true;
+        }
     }
 
     /*
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelperTest.java
new file mode 100644
index 000000000000..3b3985e4af02
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelperTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.kubernetes;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Locale;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+class KubernetesHelperTest {
+
+    private final Path workingDir = Path.of("target", "kubernetes");
+
+    // KubernetesExport builds with the kubernetes-maven-plugin for every 
cluster type but OpenShift,
+    // and that plugin writes the manifest as kubernetes.yml (kubernetes.json)
+    @ParameterizedTest
+    @EnumSource(value = ClusterType.class, names = "OPENSHIFT", mode = 
EnumSource.Mode.EXCLUDE)
+    void manifestOfKubernetesMavenPlugin(ClusterType clusterType) {
+        String name = clusterType.name().toLowerCase(Locale.ROOT);
+
+        Assertions.assertEquals(workingDir.resolve("kubernetes.yml"),
+                KubernetesHelper.getKubernetesManifestPath(name, workingDir));
+        Assertions.assertEquals(workingDir.resolve("kubernetes.json"),
+                KubernetesHelper.getKubernetesManifestPath(clusterType.name(), 
workingDir, "json"));
+        Assertions.assertEquals(new File(workingDir.toFile(), 
"kubernetes.yml"),
+                KubernetesHelper.getKubernetesManifest(name, 
workingDir.toFile()));
+    }
+
+    @Test
+    void manifestOfOpenShiftMavenPlugin() {
+        Assertions.assertEquals(workingDir.resolve("openshift.yml"),
+                KubernetesHelper.getKubernetesManifestPath("openshift", 
workingDir));
+        Assertions.assertEquals(workingDir.resolve("openshift.json"),
+                KubernetesHelper.getKubernetesManifestPath("OPENSHIFT", 
workingDir, "json"));
+        Assertions.assertEquals(new File(workingDir.toFile(), "openshift.yml"),
+                KubernetesHelper.getKubernetesManifest("openshift", 
workingDir.toFile()));
+    }
+
+    @Test
+    void manifestOfKnativeService() {
+        // KubernetesRun reads the service.yml that the knative-service trait 
writes
+        Path jkubeDir = Path.of("src", "main", "jkube");
+        Assertions.assertEquals(jkubeDir.resolve("service.yml"),
+                KubernetesHelper.getKubernetesManifestPath("service", 
jkubeDir));
+    }
+
+    @Test
+    void manifestWithoutClusterType() {
+        Assertions.assertEquals(workingDir.resolve("kubernetes.yml"),
+                KubernetesHelper.getKubernetesManifestPath(null, workingDir));
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunClusterTypeTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunClusterTypeTest.java
new file mode 100644
index 000000000000..21a0b5c46ee8
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunClusterTypeTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.kubernetes;
+
+import java.net.HttpURLConnection;
+import java.util.Collections;
+
+import io.fabric8.kubernetes.api.model.Node;
+import io.fabric8.kubernetes.api.model.NodeBuilder;
+import io.fabric8.kubernetes.api.model.NodeListBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.ClearEnvironmentVariable;
+import org.junitpioneer.jupiter.SetEnvironmentVariable;
+import picocli.CommandLine;
+
+/**
+ * Checks how the cluster type and the per-cluster image defaults are resolved 
by {@link KubernetesRun}, without
+ * exporting or building a project.
+ */
+@EnableKubernetesMockClient
+class KubernetesRunClusterTypeTest {
+
+    private static final String DOCKER_ENV_HINT = "eval $(minikube 
docker-env)";
+
+    KubernetesMockServer server;
+    KubernetesClient client;
+
+    private StringPrinter printer;
+
+    @BeforeEach
+    void setup() {
+        CommandLineHelper.useHomeDir("target");
+        printer = new StringPrinter();
+        KubernetesHelper.setKubernetesClient(client);
+    }
+
+    @Test
+    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = 
"minikube")
+    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "1")
+    void explicitMinikubeAppliesImageDefaults() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube");
+
+        
Assertions.assertTrue(ClusterType.MINIKUBE.isEqualTo(command.clusterType), 
command.clusterType);
+        Assertions.assertEquals("docker", command.imageBuilder);
+        Assertions.assertFalse(command.imagePush);
+        Assertions.assertFalse(printer.getOutput().contains(DOCKER_ENV_HINT), 
printer.getOutput());
+    }
+
+    @Test
+    @ClearEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD")
+    @ClearEnvironmentVariable(key = "DOCKER_TLS_VERIFY")
+    void explicitMinikubeWithoutDockerEnvPrintsHint() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube");
+
+        
Assertions.assertTrue(ClusterType.MINIKUBE.isEqualTo(command.clusterType), 
command.clusterType);
+        Assertions.assertEquals("docker", command.imageBuilder);
+        Assertions.assertFalse(command.imagePush);
+        Assertions.assertTrue(printer.getOutput().contains(DOCKER_ENV_HINT),
+                "Expected the minikube docker-env hint, but the output was: " 
+ printer.getOutput());
+    }
+
+    @Test
+    @ClearEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD")
+    @ClearEnvironmentVariable(key = "DOCKER_TLS_VERIFY")
+    void dockerEnvHintIsPrintedOnceAcrossReloads() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube");
+        // dev mode calls detectCluster() again on every reload
+        command.detectCluster();
+        command.detectCluster();
+
+        String output = printer.getOutput();
+        Assertions.assertEquals(output.indexOf(DOCKER_ENV_HINT), 
output.lastIndexOf(DOCKER_ENV_HINT), output);
+        Assertions.assertTrue(output.contains(DOCKER_ENV_HINT), output);
+    }
+
+    @Test
+    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = 
"minikube")
+    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "1")
+    void explicitMinikubeWithBareImagePushFlagPushes() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube", 
"--image-push");
+
+        Assertions.assertEquals("docker", command.imageBuilder);
+        Assertions.assertEquals(Boolean.TRUE, command.imagePush);
+    }
+
+    @Test
+    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = 
"minikube")
+    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "1")
+    void explicitMinikubeKeepsImageOptionsSetByUser() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube", 
"--image-builder=jib", "--image-push=true");
+
+        
Assertions.assertTrue(ClusterType.MINIKUBE.isEqualTo(command.clusterType), 
command.clusterType);
+        Assertions.assertEquals("jib", command.imageBuilder);
+        Assertions.assertTrue(command.imagePush);
+    }
+
+    @Test
+    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = 
"minikube")
+    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "1")
+    void detectedMinikubeKeepsImageBuilderSetByUser() {
+        setupServerExpectsMinikube();
+        KubernetesRun command = detectCluster("--image-builder=jib");
+
+        
Assertions.assertTrue(ClusterType.MINIKUBE.isEqualTo(command.clusterType), 
command.clusterType);
+        Assertions.assertEquals("jib", command.imageBuilder);
+        // not set by the user: the minikube no-push default only applies to 
the docker builder, a jib image is pushed
+        Assertions.assertTrue(command.imagePush);
+    }
+
+    @Test
+    @ClearEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD")
+    @ClearEnvironmentVariable(key = "DOCKER_TLS_VERIFY")
+    void explicitMinikubeWithoutImageBuildPrintsNoHint() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube", 
"--image-build=false");
+
+        Assertions.assertEquals("docker", command.imageBuilder);
+        Assertions.assertFalse(command.imagePush);
+        Assertions.assertFalse(printer.getOutput().contains(DOCKER_ENV_HINT), 
printer.getOutput());
+    }
+
+    @Test
+    @ClearEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD")
+    @ClearEnvironmentVariable(key = "DOCKER_TLS_VERIFY")
+    void explicitMinikubeWithOutputPrintsNoHint() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube", 
"--output=yaml");
+
+        Assertions.assertEquals("docker", command.imageBuilder);
+        Assertions.assertFalse(command.imagePush);
+        Assertions.assertFalse(printer.getOutput().contains(DOCKER_ENV_HINT), 
printer.getOutput());
+    }
+
+    @Test
+    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = 
"minikube")
+    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "1")
+    void disableAutoSkipsMinikubeDefaults() {
+        KubernetesRun command = detectCluster("--cluster-type=minikube", 
"--disable-auto");
+
+        
Assertions.assertTrue(ClusterType.MINIKUBE.isEqualTo(command.clusterType), 
command.clusterType);
+        Assertions.assertEquals("jib", command.imageBuilder);
+        Assertions.assertTrue(command.imagePush);
+    }
+
+    @Test
+    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = 
"minikube")
+    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "1")
+    void explicitK3sIsNotDetected() {
+        setupServerExpectsMinikube();
+        KubernetesRun command = detectCluster("--cluster-type=k3s");
+
+        Assertions.assertTrue(ClusterType.K3S.isEqualTo(command.clusterType), 
command.clusterType);
+        Assertions.assertEquals("jib", command.imageBuilder);
+        Assertions.assertTrue(command.imagePush);
+    }
+
+    private KubernetesRun detectCluster(String... args) {
+        KubernetesRun command = new KubernetesRun(new 
CamelJBangMain().withPrinter(printer));
+        CommandLine.populateCommand(command, args);
+        command.detectCluster();
+        return command;
+    }
+
+    private void setupServerExpectsMinikube() {
+        Node node = new NodeBuilder()
+                .withNewMetadata()
+                .withName("minikube")
+                .withLabels(Collections.singletonMap("minikube.k8s.io/name", 
"minikube"))
+                .endMetadata()
+                .build();
+        
server.expect().get().withPath("/api/v1/nodes?labelSelector=minikube.k8s.io%2Fname")
+                .andReturn(HttpURLConnection.HTTP_OK, new 
NodeListBuilder().addToItems(node).build())
+                .once();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java
index 02c187a7c212..f6eda1fb82d5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java
@@ -161,15 +161,12 @@ class KubernetesRunCustomTest {
     }
 
     @Test
-    @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = "foo")
-    @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "foo")
     public void explicitMinikubeClusterTypeShouldApplyDefaults() throws 
Exception {
-        // Even without auto-detection (minikube env vars are set but ignored 
because clusterType is explicit),
-        // passing --cluster-type=minikube should still apply the minikube 
defaults:
+        // The cluster is not detected as minikube (no minikube node, no 
minikube docker-env),
+        // but passing --cluster-type=minikube should still apply the minikube 
defaults:
         // imageBuilder=docker and imagePush=false.
         KubernetesHelper.setKubernetesClient(client);
-        setupServerExpectsMinikube();
-        KubernetesRun command = createCommand(List.of("classpath:route.yaml"),
+        KubernetesRun command = 
createCommandKeepingImagePush(List.of("classpath:route.yaml"),
                 "--image-registry=quay.io", "--image-group=camel-test", 
"--output=yaml",
                 "--cluster-type=minikube");
         int exit = command.doCall();
@@ -201,6 +198,24 @@ class KubernetesRunCustomTest {
                 printer.getOutput());
     }
 
+    @Test
+    public void explicitK3sClusterTypeShouldOutputKubernetesManifest() throws 
Exception {
+        // K3S is built with the kubernetes-maven-plugin, which writes 
kubernetes.yml (not k3s.yml)
+        KubernetesHelper.setKubernetesClient(client);
+        KubernetesRun command = createCommand(List.of("classpath:route.yaml"),
+                "--image-registry=quay.io", "--image-group=camel-test", 
"--output=yaml",
+                "--cluster-type=k3s");
+        int exit = command.doCall();
+
+        Assertions.assertEquals(0, exit, printer.getOutput());
+        Assertions.assertEquals(ClusterType.K3S.name().toLowerCase(), 
command.clusterType.toLowerCase());
+
+        var manifest = 
KubernetesBaseTestSupport.getKubernetesManifestAsStream(printer.getOutput(), 
command.output);
+        List<HasMetadata> resources = client.load(manifest).items();
+        // expects Service, Deployment manifests in kubernetes.yml
+        Assertions.assertEquals(2, resources.size());
+    }
+
     @Test
     @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = "foo")
     @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "foo")
@@ -340,6 +355,13 @@ class KubernetesRunCustomTest {
     }
 
     private KubernetesRun createCommand(List<String> files, String... args) {
+        KubernetesRun command = createCommandKeepingImagePush(files, args);
+        command.imagePush = false;
+        return command;
+    }
+
+    // keeps --image-push as given (or not set), so that the defaults of the 
cluster type apply to it
+    private KubernetesRun createCommandKeepingImagePush(List<String> files, 
String... args) {
         var argsArr = Optional.ofNullable(args).orElse(new String[0]);
         var argsLst = new ArrayList<>(Arrays.asList(argsArr));
         var jbangMain = new CamelJBangMain().withPrinter(printer);
@@ -351,7 +373,6 @@ class KubernetesRunCustomTest {
         };
         CommandLine.populateCommand(command, argsLst.toArray(new String[0]));
         command.imageBuild = false;
-        command.imagePush = false;
         return command;
     }
 

Reply via email to