You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by cd...@apache.org on 2023/12/20 18:07:23 UTC

(camel) branch main updated: CAMEL-20251: Add Camel K commands to Camel JBang

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


The following commit(s) were added to refs/heads/main by this push:
     new 1b51e7a260d CAMEL-20251: Add Camel K commands to Camel JBang
1b51e7a260d is described below

commit 1b51e7a260d69896503bd0c0da7dc07ad63c771c
Author: Christoph Deppisch <cd...@redhat.com>
AuthorDate: Mon Dec 18 14:43:46 2023 +0100

    CAMEL-20251: Add Camel K commands to Camel JBang
    
    - Adds new subcommands to Camel JBang that allow to manage Camel K integrations
    - run command to create Integrations on Kubernetes
    - list command to list all Integrations on the namespace
    - delete command to remove Integrations
    - logs command to print log output of a running Integration
---
 docs/user-manual/modules/ROOT/nav.adoc             |   1 +
 .../modules/ROOT/pages/camel-jbang-k.adoc          | 147 +++++
 .../modules/ROOT/pages/camel-jbang.adoc            |  18 +-
 dsl/camel-jbang/camel-jbang-core/pom.xml           |  27 +
 .../dsl/jbang/core/commands/CamelCommand.java      |   5 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |  99 ++--
 .../jbang/core/commands/k/CompressionHelper.java   |  55 ++
 .../jbang/core/commands/k/IntegrationDelete.java   |  66 +++
 .../dsl/jbang/core/commands/k/IntegrationGet.java  |  96 ++++
 .../dsl/jbang/core/commands/k/IntegrationLogs.java | 107 ++++
 .../dsl/jbang/core/commands/k/IntegrationRun.java  | 571 ++++++++++++++++++++
 .../dsl/jbang/core/commands/k/KubeBaseCommand.java | 108 ++++
 .../dsl/jbang/core/commands/k/KubeCommand.java     |  40 ++
 .../jbang/core/commands/k/KubernetesHelper.java    | 151 ++++++
 .../dsl/jbang/core/commands/k/SourceScheme.java    |  78 +++
 .../dsl/jbang/core/commands/k/TraitHelper.java     | 125 +++++
 .../dsl/jbang/core/commands/k/TraitProfile.java    |  25 +
 .../jbang/core/commands/version/VersionGet.java    |   4 +
 .../camel/dsl/jbang/core/common/Printer.java       |  39 ++
 .../dsl/jbang/core/commands/StringPrinter.java     |  68 +++
 .../core/commands/k/IntegrationDeleteTest.java     |  79 +++
 .../jbang/core/commands/k/IntegrationGetTest.java  | 110 ++++
 .../jbang/core/commands/k/IntegrationLogsTest.java |  69 +++
 .../jbang/core/commands/k/IntegrationRunTest.java  | 594 +++++++++++++++++++++
 .../dsl/jbang/core/commands/k/KubeBaseTest.java    |  87 +++
 .../jbang/core/commands/k/KubeCommandMainTest.java | 119 +++++
 .../dsl/jbang/core/commands/k/integration.yaml     |  35 ++
 .../camel-jbang-core/src/test/resources/pod.yaml   |  28 +
 .../camel-jbang-core/src/test/resources/route.yaml |  23 +
 parent/pom.xml                                     |   1 +
 30 files changed, 2926 insertions(+), 49 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/nav.adoc b/docs/user-manual/modules/ROOT/nav.adoc
index 01f22584ca4..b7c39a2e0ec 100644
--- a/docs/user-manual/modules/ROOT/nav.adoc
+++ b/docs/user-manual/modules/ROOT/nav.adoc
@@ -6,6 +6,7 @@
 ** xref:building.adoc[Building]
 ** xref:camel-console.adoc[Camel Developer Console]
 ** xref:camel-jbang.adoc[Camel JBang]
+*** xref:camel-jbang-k.adoc[Camel Integration with Kubernetes]
 ** xref:camel-maven-plugin.adoc[Camel Maven Plugin]
 ** xref:camel-component-maven-plugin.adoc[Camel Component Maven Plugin]
 ** xref:camel-report-maven-plugin.adoc[Camel Maven Report Plugin]
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-k.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-k.adoc
new file mode 100644
index 00000000000..d202b44c724
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-k.adoc
@@ -0,0 +1,147 @@
+= Camel Integration with Kubernetes
+
+Please make sure to meet these prerequisites for running Camel integrations on Kubernetes:
+
+* Connect to namespace on a Kubernetes cluster where you want to run the integration
+* Camel K operator must be installed on the Kubernetes cluster (either installed on the same namespace or as global operator for the whole cluster)
+
+Running Camel routes on Kubernetes is quite simple with Camel JBang.
+In fact, you can develop and test your Camel route locally with Camel JBang and then promote the same source to running it as an integration on Kubernetes.
+Simply run the integration using the `k` subcommand in Camel JBang.
+
+[source,bash]
+----
+camel k run route.yaml
+----
+
+The command runs the Camel integration on Kubernetes.
+More precisely it creates a Camel K Integration custom resource in the current namespace.
+The Camel K operator makes sure to create a proper runtime image and run the integration (usually as a Pod).
+
+The Camel K operator will automatically manage and configure this integration.
+In particular the operator takes care on exposing services, configuring health endpoints, providing metrics, updating image streams and much more.
+
+By default, the run command will not wait for the integration to in state running.
+You need to add `-w` or `--wait` option in order to wait for the integration to become ready.
+
+The `--logs` option makes the command also print the integration output once the integration Pod is running.
+
+The run command offers a lot more options that you may use to configure the Camel K integration.
+
+[width="100%",cols="1m,3",options="header",]
+|=======================================================================
+|Option |Description
+
+|--name
+|The integration name. Use this when the name should not get derived from the source file name.
+
+|--image
+|An image built externally (for instance via CI/CD). Enabling it will skip the integration build phase.
+
+|--kit, -k
+|The kit used to run the integration.
+
+|--profile
+|The trait profile to use for the deployment.
+
+|--service-account
+|The service account used to run this Integration.
+
+|--pod-template
+|The path of the YAML file containing a PodSpec template to be used for the integration pods.
+
+|--operator-id
+|Operator id selected to manage this integration. (default=camel-k)
+
+|--dependency, -d
+|Adds dependency that should be included, use "camel:" prefix for a Camel component, "mvn:org.my:app:1.0" for a Maven dependency.
+
+|--property, -p
+|Add a runtime property or properties file from a path, a config map or a secret (syntax: [my-key=my-value,file:/path/to/my-conf.properties,[configmap,secret]:name]).
+
+|--build-property
+|Add a build time property or properties file from a path, a config map or a secret  (syntax: [my-key=my-value,file:/path/to/my-conf.properties,[configmap,secret]:name]]).
+
+|--config
+|Add a runtime configuration from a ConfigMap or a Secret (syntax: [configmap,secret]:name[/key], where name represents the configmap/secret name and key optionally represents the configmap/secret key to be filtered).
+
+|--resource
+|Add a runtime resource from a Configmap or a Secret (syntax: [configmap,secret]:name[/key][@path], where name represents the configmap/secret name, key optionally represents the configmap/secret key to be filtered and path represents the destination path).
+
+|--open-api
+|Add an OpenAPI spec (syntax: [configmap,file]:name).
+
+|--env, -e
+|Set an environment variable in the integration container, for instance "-e MY_VAR=my-value".
+
+|--volume, -v
+|Mount a volume into the integration container, for instance "-v pvcname:/container/path".
+
+|--connect, -c
+|A Service that the integration should bind to, specified as [[apigroup/]version:]kind:[namespace/]name.
+
+|--source
+|Add source file to your integration, this is added to the list of files listed as arguments of the command.
+
+|--maven-repository
+|Add a maven repository used to resolve dependencies.
+
+|--annotation
+|Add an annotation to the integration. Use name values pairs like "--annotation my.company=hello".
+
+|--label
+|Add a label to the integration. Use name values pairs like "--label my.company=hello".
+
+|--traits, -t
+|Add a label to the integration. Use name values pairs like "--label my.company=hello".
+
+|--use-flows
+|Write yaml sources as Flow objects in the integration custom resource (default=true).
+
+|--compression
+|Enable storage of sources and resources as a compressed binary blobs.
+
+|--wait, -w
+|Wait for the integration to become ready.
+
+|--logs, -l
+|Print logs after integration has been started.
+
+|--output, -o
+|Just output the generated integration custom resource (supports: yaml or json).
+|=======================================================================
+
+You can list the available integration resources with the following command.
+
+[source,bash]
+----
+camel k get
+NAME      PHASE    KIT            READY
+my-route  Running  kit-123456789   1/1
+----
+
+This looks for all integrations in the current namespace and lists their individual status.
+
+To inspect the log output of a running integration call:
+
+[source,bash]
+----
+camel k logs my-route
+----
+
+The command connects to the running integration Pod and prints the log output.
+Just terminate the process to stop printing the logs.
+
+Of course, you may also delete an integration resource from the cluster.
+
+[source,bash]
+----
+camel k delete my-route
+----
+
+To remove all available integrations on the current namespace use the `--all` option.
+
+[source,bash]
+----
+camel k delete --all
+----
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index d87d829c0af..657a8c9aa3b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -70,7 +70,7 @@ So running a simple route will be as easy as doing the following:
 docker run apache/camel-jbang:3.20.5 run example.yaml
 ----
 
-or 
+or
 
 [source,bash]
 ----
@@ -1082,7 +1082,6 @@ camel run clipboard.xml --dev
 
 Then you can quickly make changes and copy to clipboard, and Camel JBang will update while running.
 
-
 === Sending messages via Camel
 
 *Available since Camel 4*
@@ -1478,6 +1477,13 @@ camel cmd start-route --all
 TIP: You can stop one or more route by their ids by separating using
 comma such as: camel cmd start-route --id=route1,hello. Use `camel cmd start-route --help` for more details.
 
+==== Running Camel integrations on Kubernetes
+
+After developing the Camel routes locally with JBang you may want to run these also on the Kubernetes platform at some point.
+The Camel K commands get you started with this journey and help you to run and manage Camel integrations on Kubernetes.
+
+Read about it in xref:camel-jbang-k.adoc[Camel Integration with Kubernetes].
+
 ==== Configuring logging levels
 
 You can see the current logging levels of the running Camel integrations by:
@@ -1561,7 +1567,6 @@ $ camel get metric
  11562  MyCoolCamel  gauge  system.load.average.1m                        3.58935546875
 ----
 
-
 ==== Listing state of Circuit Breakers
 
 If your Camel integration uses xref:components:eips:circuitBreaker-eip.adoc[Circuit Breaker] then
@@ -2156,7 +2161,6 @@ This will then automatic insert or update the JBang depencies (`//DEPS`) in the
 You may want to use this for making it easier to load the source into an IDE editor to do coding.
 See previous section for more details.
 
-
 ==== Camel route debugging using VSCode or IDEA editors
 
 The Camel route debugger is available by default (the `camel-debug` component is automatically added to the classpath). By default, it can be reached through JMX at the URL `service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi/camel`.
@@ -2905,13 +2909,13 @@ camel sbom --sbom-format=spdx
 You can also choose the target runtime as either _quarkus_ or _spring-boot_ as shown:
 
 ----
-camel sbom --runtime=quarkus 
+camel sbom --runtime=quarkus
 ----
 
-or 
+or
 
 ----
-camel sbom --runtime=spring-boot 
+camel sbom --runtime=spring-boot
 ----
 
 by default `camel-main` will be used
diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml b/dsl/camel-jbang/camel-jbang-core/pom.xml
index 44e6a63d4d9..7c5bb588e38 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -85,6 +85,20 @@
             <version>${ascii-table-version}</version>
         </dependency>
 
+        <!-- kubernetes -->
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-client</artifactId>
+            <version>${kubernetes-client-version}</version>
+        </dependency>
+
+        <!-- camel k-->
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-crds</artifactId>
+            <version>${camel-k-version}</version>
+        </dependency>
+
         <!-- jolokia -->
         <dependency>
             <groupId>org.jolokia</groupId>
@@ -152,6 +166,19 @@
             <artifactId>jackson-databind</artifactId>
         </dependency>
 
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-server-mock</artifactId>
+            <version>${kubernetes-client-version}</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
 </project>
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
index b27ca2476ce..5ea385a69d3 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
@@ -25,6 +25,7 @@ import java.util.Stack;
 import java.util.concurrent.Callable;
 
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.Printer;
 import org.apache.camel.dsl.jbang.core.common.RuntimeUtil;
 import org.apache.camel.util.StringHelper;
 import picocli.CommandLine;
@@ -129,6 +130,10 @@ public abstract class CamelCommand implements Callable<Integer> {
         return new File(camelDir, pid + "-debug.json");
     }
 
+    protected Printer printer() {
+        return getMain().getOut();
+    }
+
     protected void printConfigurationValues(String header) {
         final Properties configProperties = new Properties();
         CommandLineHelper.loadProperties(configProperties::putAll);
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index c5a22e8c30c..5933943ce37 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -20,24 +20,7 @@ import java.util.concurrent.Callable;
 
 import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.catalog.DefaultCamelCatalog;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelGCAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelLogAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelReloadAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelResetStatsAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelRouteDumpAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelRouteStartAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelRouteStopAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelSendAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelSourceAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelSourceTop;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelStartupRecorderAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelStubAction;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelThreadDump;
-import org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction;
-import org.apache.camel.dsl.jbang.core.commands.action.LoggerAction;
-import org.apache.camel.dsl.jbang.core.commands.action.RouteControllerAction;
-import org.apache.camel.dsl.jbang.core.commands.action.TransformMessageAction;
+import org.apache.camel.dsl.jbang.core.commands.action.*;
 import org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand;
 import org.apache.camel.dsl.jbang.core.commands.catalog.CatalogComponent;
 import org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDataFormat;
@@ -50,34 +33,18 @@ import org.apache.camel.dsl.jbang.core.commands.config.ConfigGet;
 import org.apache.camel.dsl.jbang.core.commands.config.ConfigList;
 import org.apache.camel.dsl.jbang.core.commands.config.ConfigSet;
 import org.apache.camel.dsl.jbang.core.commands.config.ConfigUnset;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelContextStatus;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelContextTop;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelCount;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelProcessorStatus;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelProcessorTop;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelRouteStatus;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelRouteTop;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelStatus;
-import org.apache.camel.dsl.jbang.core.commands.process.CamelTop;
-import org.apache.camel.dsl.jbang.core.commands.process.Hawtio;
-import org.apache.camel.dsl.jbang.core.commands.process.Jolokia;
-import org.apache.camel.dsl.jbang.core.commands.process.ListBlocked;
-import org.apache.camel.dsl.jbang.core.commands.process.ListCircuitBreaker;
-import org.apache.camel.dsl.jbang.core.commands.process.ListConsumer;
-import org.apache.camel.dsl.jbang.core.commands.process.ListEndpoint;
-import org.apache.camel.dsl.jbang.core.commands.process.ListEvent;
-import org.apache.camel.dsl.jbang.core.commands.process.ListHealth;
-import org.apache.camel.dsl.jbang.core.commands.process.ListInflight;
-import org.apache.camel.dsl.jbang.core.commands.process.ListMetric;
-import org.apache.camel.dsl.jbang.core.commands.process.ListProcess;
-import org.apache.camel.dsl.jbang.core.commands.process.ListService;
-import org.apache.camel.dsl.jbang.core.commands.process.ListVault;
-import org.apache.camel.dsl.jbang.core.commands.process.StopProcess;
+import org.apache.camel.dsl.jbang.core.commands.k.IntegrationDelete;
+import org.apache.camel.dsl.jbang.core.commands.k.IntegrationGet;
+import org.apache.camel.dsl.jbang.core.commands.k.IntegrationLogs;
+import org.apache.camel.dsl.jbang.core.commands.k.IntegrationRun;
+import org.apache.camel.dsl.jbang.core.commands.k.KubeCommand;
+import org.apache.camel.dsl.jbang.core.commands.process.*;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionCommand;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionGet;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionList;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionSet;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.Printer;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 
@@ -85,8 +52,13 @@ import picocli.CommandLine.Command;
 public class CamelJBangMain implements Callable<Integer> {
     private static CommandLine commandLine;
 
+    private Printer out = new Printer.SystemOutPrinter();
+
     public static void run(String... args) {
-        CamelJBangMain main = new CamelJBangMain();
+        run(new CamelJBangMain(), args);
+    }
+
+    public static void run(CamelJBangMain main, String... args) {
         commandLine = new CommandLine(main)
                 .addSubcommand("init", new CommandLine(new Init(main)))
                 .addSubcommand("run", new CommandLine(new Run(main)))
@@ -157,6 +129,11 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("get", new CommandLine(new ConfigGet(main)))
                         .addSubcommand("unset", new CommandLine(new ConfigUnset(main)))
                         .addSubcommand("set", new CommandLine(new ConfigSet(main))))
+                .addSubcommand("k", new CommandLine(new KubeCommand(main))
+                        .addSubcommand("get", new CommandLine(new IntegrationGet(main)))
+                        .addSubcommand("run", new CommandLine(new IntegrationRun(main)))
+                        .addSubcommand("delete", new CommandLine(new IntegrationDelete(main)))
+                        .addSubcommand("logs", new CommandLine(new IntegrationLogs(main))))
                 .addSubcommand("version", new CommandLine(new VersionCommand(main))
                         .addSubcommand("get", new CommandLine(new VersionGet(main)))
                         .addSubcommand("set", new CommandLine(new VersionSet(main)))
@@ -170,6 +147,16 @@ public class CamelJBangMain implements Callable<Integer> {
 
         CommandLineHelper.augmentWithUserConfiguration(commandLine, args);
         int exitCode = commandLine.execute(args);
+        main.quit(exitCode);
+    }
+
+    /**
+     * Finish this main with given exit code. By default, uses system exit to terminate. Subclasses may want to
+     * overwrite this exit behavior e.g. during unit tests.
+     *
+     * @param exitCode
+     */
+    protected void quit(int exitCode) {
         System.exit(exitCode);
     }
 
@@ -179,4 +166,32 @@ public class CamelJBangMain implements Callable<Integer> {
         return 0;
     }
 
+    /**
+     * Gets the main output printer to write command output.
+     *
+     * @return the printer.
+     */
+    public Printer getOut() {
+        return out;
+    }
+
+    /**
+     * Sets the main output printer.
+     *
+     * @param out the printer to use for command output.
+     */
+    public void setOut(Printer out) {
+        this.out = out;
+    }
+
+    /**
+     * Uses this printer for writing command output.
+     *
+     * @param out to use with this main.
+     */
+    public CamelJBangMain withPrinter(Printer out) {
+        this.out = out;
+        return this;
+    }
+
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java
new file mode 100644
index 00000000000..07f16123326
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java
@@ -0,0 +1,55 @@
+/*
+ * 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.k;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.apache.camel.RuntimeCamelException;
+
+/**
+ * Utility helper to handle base64 compression of sources.
+ */
+public class CompressionHelper {
+
+    private CompressionHelper() {
+        // prevent instantiation of utility class.
+    }
+
+    /**
+     * Compress given data with deflate and base64 encoding.
+     *
+     * @param  data to be compressed.
+     * @return      compressed base64 encoded data
+     */
+    public static String compressBase64(String data) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION, true);
+        try (DeflaterOutputStream dos = new DeflaterOutputStream(bos, compressor, true)) {
+            dos.write(data.getBytes(StandardCharsets.UTF_8));
+            dos.flush();
+            return new String(Base64.getEncoder().encode(bos.toByteArray()));
+        } catch (IOException e) {
+            throw new RuntimeCamelException("Failed to compress data", e);
+        }
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java
new file mode 100644
index 00000000000..647963f775d
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java
@@ -0,0 +1,66 @@
+/*
+ * 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.k;
+
+import java.util.Arrays;
+import java.util.List;
+
+import io.fabric8.kubernetes.api.model.StatusDetails;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "delete", description = "Delete integrations deployed on Kubernetes", sortOptions = false)
+public class IntegrationDelete extends KubeBaseCommand {
+
+    @CommandLine.Parameters(description = "Integration names to delete.",
+                            arity = "0..*", paramLabel = "<names>")
+    String[] names;
+
+    @CommandLine.Option(names = { "--all" },
+                        description = "Delete all integrations in current namespace.")
+    boolean all;
+
+    public IntegrationDelete(CamelJBangMain main) {
+        super(main);
+    }
+
+    public Integer doCall() throws Exception {
+        if (all) {
+            client(Integration.class).delete();
+            printer().println("Integrations deleted");
+        } else {
+            if (names == null) {
+                throw new RuntimeCamelException("Missing integration name as argument or --all option.");
+            }
+
+            for (String name : Arrays.stream(names).map(KubernetesHelper::sanitize).toList()) {
+                List<StatusDetails> status = client(Integration.class).withName(name).delete();
+                if (status.isEmpty()) {
+                    printer().printf("Integration %s deletion skipped - not found%n", name);
+                } else {
+                    printer().printf("Integration %s deleted%n", name);
+                }
+            }
+        }
+
+        return 0;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java
new file mode 100644
index 00000000000..55975e13302
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java
@@ -0,0 +1,96 @@
+/*
+ * 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.k;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.github.freva.asciitable.AsciiTable;
+import com.github.freva.asciitable.Column;
+import com.github.freva.asciitable.HorizontalAlign;
+import com.github.freva.asciitable.OverflowBehaviour;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "get", description = "List Camel integrations deployed on Kubernetes", sortOptions = false)
+public class IntegrationGet extends KubeBaseCommand {
+
+    @CommandLine.Option(names = { "--name" },
+                        description = "List only given integration name in the output")
+    boolean name;
+
+    public IntegrationGet(CamelJBangMain main) {
+        super(main);
+    }
+
+    public Integer doCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        List<Integration> integrations = client(Integration.class).list().getItems();
+        integrations
+                .forEach(integration -> {
+                    Row row = new Row();
+                    row.name = integration.getMetadata().getName();
+
+                    row.ready = "0/1";
+                    if (integration.getStatus() != null) {
+                        row.phase = integration.getStatus().getPhase();
+
+                        if (integration.getStatus().getConditions() != null) {
+                            row.ready
+                                    = integration.getStatus().getConditions().stream().filter(c -> c.getType().equals("Ready"))
+                                            .anyMatch(c -> c.getStatus().equals("True")) ? "1/1" : "0/1";
+                        }
+
+                        row.kit = integration.getStatus().getIntegrationKit() != null
+                                ? integration.getStatus().getIntegrationKit().getName() : "";
+                    } else {
+                        row.phase = "Unknown";
+                    }
+
+                    rows.add(row);
+                });
+
+        if (!rows.isEmpty()) {
+            if (name) {
+                rows.forEach(r -> printer().println(r.name));
+            } else {
+                printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList(
+                        new Column().header("NAME").dataAlign(HorizontalAlign.LEFT)
+                                .maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT)
+                                .with(r -> r.name),
+                        new Column().header("PHASE").headerAlign(HorizontalAlign.LEFT)
+                                .with(r -> r.phase),
+                        new Column().header("KIT").headerAlign(HorizontalAlign.LEFT).with(r -> r.kit),
+                        new Column().header("READY").dataAlign(HorizontalAlign.CENTER).with(r -> r.ready))));
+            }
+        }
+
+        return 0;
+    }
+
+    private static class Row {
+        String name;
+        String ready;
+        String phase;
+        String kit;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java
new file mode 100644
index 00000000000..faf96f0c27c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java
@@ -0,0 +1,107 @@
+/*
+ * 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.k;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodList;
+import io.fabric8.kubernetes.client.dsl.LogWatch;
+import io.fabric8.kubernetes.client.dsl.PodResource;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "logs", description = "Print the logs of an integration", sortOptions = false)
+public class IntegrationLogs extends KubeBaseCommand {
+
+    @CommandLine.Parameters(description = "Integration name to grab logs from.",
+                            paramLabel = "<name>")
+    String name;
+
+    @CommandLine.Option(names = { "--tail", "-t" },
+                        defaultValue = "-1",
+                        description = "The number of lines from the end of the logs to show. Defaults to -1 to show all the lines.")
+    int tail = -1;
+
+    public IntegrationLogs(CamelJBangMain main) {
+        super(main);
+    }
+
+    public Integer doCall() throws Exception {
+        String integrationName = KubernetesHelper.sanitize(name);
+        Integration integration = client(Integration.class).withName(integrationName).get();
+
+        if (integration == null) {
+            printer().printf("Integration %s not found%n", integrationName);
+            return 0;
+        }
+
+        watchLogs(integration);
+
+        return 0;
+    }
+
+    void watchLogs(Integration integration) {
+        PodList pods = pods().withLabel(KubeCommand.INTEGRATION_LABEL, integration.getMetadata().getName()).list();
+
+        Pod pod = pods.getItems().stream()
+                .filter(p -> p.getStatus().getPhase() != null && !"Terminated".equals(p.getStatus().getPhase()))
+                .findFirst()
+                .orElseThrow(() -> new RuntimeCamelException("Failed to find integration pod"));
+
+        String containerName = null;
+        if (pod.getSpec() != null && pod.getSpec().getContainers() != null) {
+            if (pod.getSpec().getContainers().stream()
+                    .anyMatch(container -> KubeCommand.INTEGRATION_CONTAINER_NAME.equals(container.getName()))) {
+                containerName = KubeCommand.INTEGRATION_CONTAINER_NAME;
+            } else if (pod.getSpec().getContainers().size() > 0) {
+                containerName = pod.getSpec().getContainers().get(0).getName();
+            }
+        }
+
+        PodResource podRes = pods().withName(pod.getMetadata().getName());
+
+        LogWatch logs;
+        if (tail < 0) {
+            if (containerName != null) {
+                logs = podRes.inContainer(containerName).watchLog();
+            } else {
+                logs = podRes.watchLog();
+            }
+        } else {
+            if (containerName != null) {
+                logs = podRes.inContainer(containerName).tailingLines(tail).watchLog();
+            } else {
+                logs = podRes.tailingLines(tail).watchLog();
+            }
+        }
+
+        try (logs; BufferedReader reader = new BufferedReader(new InputStreamReader(logs.getOutput()))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                printer().println(line);
+            }
+        } catch (IOException e) {
+            printer().println("Failed to read integration pod logs - " + e.getMessage());
+        }
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
new file mode 100644
index 00000000000..6d5b5a267ca
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
@@ -0,0 +1,571 @@
+/*
+ * 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.k;
+
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.GistHelper;
+import org.apache.camel.dsl.jbang.core.common.GitHubHelper;
+import org.apache.camel.dsl.jbang.core.common.JSonHelper;
+import org.apache.camel.dsl.jbang.core.common.Printer;
+import org.apache.camel.github.GistResourceResolver;
+import org.apache.camel.github.GitHubResourceResolver;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.engine.DefaultResourceResolvers;
+import org.apache.camel.spi.ResourceResolver;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.v1.Integration;
+import org.apache.camel.v1.IntegrationSpec;
+import org.apache.camel.v1.integrationspec.Flows;
+import org.apache.camel.v1.integrationspec.IntegrationKit;
+import org.apache.camel.v1.integrationspec.Sources;
+import org.apache.camel.v1.integrationspec.Template;
+import org.apache.camel.v1.integrationspec.Traits;
+import org.apache.camel.v1.integrationspec.template.Spec;
+import org.apache.camel.v1.integrationspec.traits.Builder;
+import org.apache.camel.v1.integrationspec.traits.Camel;
+import org.apache.camel.v1.integrationspec.traits.Container;
+import org.apache.camel.v1.integrationspec.traits.Environment;
+import org.apache.camel.v1.integrationspec.traits.Mount;
+import org.apache.camel.v1.integrationspec.traits.Openapi;
+import org.apache.camel.v1.integrationspec.traits.ServiceBinding;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "run", description = "Run Camel integrations on Kubernetes", sortOptions = false)
+public class IntegrationRun extends KubeBaseCommand {
+
+    @CommandLine.Parameters(description = "The Camel file(s) to run.",
+                            arity = "0..9", paramLabel = "<files>")
+    String[] filePaths;
+
+    @CommandLine.Option(names = { "--name" },
+                        description = "The integration name. Use this when the name should not get derived from the source file name.")
+    String name;
+
+    @CommandLine.Option(names = { "--image" },
+                        description = "An image built externally (for instance via CI/CD). Enabling it will skip the integration build phase.")
+    String image;
+
+    @CommandLine.Option(names = { "--kit", "-k" }, description = "The kit used to run the integration.")
+    String kit;
+
+    @CommandLine.Option(names = { "--profile" }, description = "The trait profile to use for the deployment.")
+    String profile;
+
+    @CommandLine.Option(names = { "--service-account" }, description = "The service account used to run this Integration.")
+    String serviceAccount;
+
+    @CommandLine.Option(names = { "--pod-template" },
+                        description = "The path of the YAML file containing a PodSpec template to be used for the integration pods.")
+    String podTemplate;
+
+    @CommandLine.Option(names = { "--operator-id", "-x" }, defaultValue = "camel-k",
+                        description = "Operator id selected to manage this integration.")
+    String operatorId = "camel-k";
+
+    @CommandLine.Option(names = { "--dependency", "-d" },
+                        description = "Adds dependency that should be included, use \"camel:\" prefix for a Camel component, \"mvn:org.my:app:1.0\" for a Maven dependency.")
+    String[] dependencies;
+
+    @CommandLine.Option(names = { "--property", "-p" },
+                        description = "Add a runtime property or properties file from a path, a config map or a secret (syntax: [my-key=my-value|file:/path/to/my-conf.properties|[configmap|secret]:name]).")
+    String[] properties;
+
+    @CommandLine.Option(names = { "--build-property" },
+                        description = "Add a build time property or properties file from a path, a config map or a secret (syntax: [my-key=my-value|file:/path/to/my-conf.properties|[configmap|secret]:name]]).")
+    String[] buildProperties;
+
+    @CommandLine.Option(names = { "--config" },
+                        description = "Add a runtime configuration from a ConfigMap or a Secret (syntax: [configmap|secret]:name[/key], where name represents the configmap/secret name and key optionally represents the configmap/secret key to be filtered).")
+    String[] configs;
+
+    @CommandLine.Option(names = { "--resource" },
+                        description = "Add a runtime resource from a Configmap or a Secret (syntax: [configmap|secret]:name[/key][@path], where name represents the configmap/secret name, key optionally represents the configmap/secret key to be filtered and path represents the destination path).")
+    String[] resources;
+
+    @CommandLine.Option(names = { "--open-api" }, description = "Add an OpenAPI spec (syntax: [configmap|file]:name).")
+    String[] openApis;
+
+    @CommandLine.Option(names = { "--env", "-e" },
+                        description = "Set an environment variable in the integration container, for instance \"-e MY_VAR=my-value\".")
+    String[] envVars;
+
+    @CommandLine.Option(names = { "--volume", "-v" },
+                        description = "Mount a volume into the integration container, for instance \"-v pvcname:/container/path\".")
+    String[] volumes;
+
+    @CommandLine.Option(names = { "--connect", "-c" },
+                        description = "A Service that the integration should bind to, specified as [[apigroup/]version:]kind:[namespace/]name.")
+    String[] connects;
+
+    @CommandLine.Option(names = { "--source" },
+                        description = "Add source file to your integration, this is added to the list of files listed as arguments of the command.")
+    String[] sources;
+
+    @CommandLine.Option(names = { "--maven-repository" }, description = "Add a maven repository used to resolve dependencies.")
+    String[] repositories;
+
+    @CommandLine.Option(names = { "--annotation" },
+                        description = "Add an annotation to the integration. Use name values pairs like \"--annotation my.company=hello\".")
+    String[] annotations;
+
+    @CommandLine.Option(names = { "--label" },
+                        description = "Add a label to the integration. Use name values pairs like \"--label my.company=hello\".")
+    String[] labels;
+
+    @CommandLine.Option(names = { "--traits", "-t" },
+                        description = "Add a label to the integration. Use name values pairs like \"--label my.company=hello\".")
+    String[] traits;
+
+    @CommandLine.Option(names = { "--use-flows" }, defaultValue = "true",
+                        description = "Write yaml sources as Flow objects in the integration custom resource.")
+    boolean useFlows = true;
+
+    @CommandLine.Option(names = { "--compression" },
+                        description = "Enable storage of sources and resources as a compressed binary blobs.")
+    boolean compression;
+
+    @CommandLine.Option(names = { "--wait", "-w" }, description = "Wait for the integration to become ready.")
+    boolean wait;
+
+    @CommandLine.Option(names = { "--logs", "-l" }, description = "Print logs after integration has been started.")
+    boolean logs;
+
+    @CommandLine.Option(names = { "--output", "-o" },
+                        description = "Just output the generated integration custom resource (supports: yaml or json).")
+    String output;
+
+    public IntegrationRun(CamelJBangMain main) {
+        super(main);
+    }
+
+    public Integer doCall() throws Exception {
+        List<String> integrationSources
+                = Stream.concat(Arrays.stream(Optional.ofNullable(filePaths).orElseGet(() -> new String[] {})),
+                        Arrays.stream(Optional.ofNullable(sources).orElseGet(() -> new String[] {}))).toList();
+
+        Integration integration = new Integration();
+        integration.setSpec(new IntegrationSpec());
+        integration.getMetadata()
+                .setName(getIntegrationName(integrationSources));
+
+        if (dependencies != null && dependencies.length > 0) {
+            List<String> deps = new ArrayList<>();
+            for (String dependency : dependencies) {
+                String normalized = normalizeDependency(dependency);
+                validateDependency(normalized, printer());
+                deps.add(normalized);
+            }
+
+            integration.getSpec().setDependencies(deps);
+        }
+
+        if (kit != null) {
+            IntegrationKit integrationKit = new IntegrationKit();
+            integrationKit.setName(kit);
+            integration.getSpec().setIntegrationKit(integrationKit);
+        }
+
+        if (profile != null) {
+            TraitProfile p = TraitProfile.valueOf(profile.toUpperCase(Locale.US));
+            integration.getSpec().setProfile(p.name().toLowerCase(Locale.US));
+        }
+
+        if (repositories != null && repositories.length > 0) {
+            integration.getSpec().setRepositories(List.of(repositories));
+        }
+
+        if (annotations != null && annotations.length > 0) {
+            integration.getMetadata().setAnnotations(Arrays.stream(annotations)
+                    .filter(it -> it.contains("="))
+                    .map(it -> it.split("="))
+                    .filter(it -> it.length == 2)
+                    .collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim())));
+        }
+
+        if (operatorId != null) {
+            if (integration.getMetadata().getAnnotations() == null) {
+                integration.getMetadata().setAnnotations(new HashMap<>());
+            }
+
+            integration.getMetadata().getAnnotations().put(KubeCommand.OPERATOR_ID_LABEL, operatorId);
+        }
+
+        if (labels != null && labels.length > 0) {
+            integration.getMetadata().setLabels(Arrays.stream(labels)
+                    .filter(it -> it.contains("="))
+                    .map(it -> it.split("="))
+                    .filter(it -> it.length == 2)
+                    .collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim())));
+        }
+
+        Traits traitsSpec;
+        if (traits != null && traits.length > 0) {
+            traitsSpec = TraitHelper.parseTraits(traits);
+        } else {
+            traitsSpec = new Traits();
+        }
+
+        if (image != null) {
+            Container containerTrait = new Container();
+            containerTrait.setImage(image);
+            traitsSpec.setContainer(containerTrait);
+        } else {
+            List<Source> resolvedSources = resolveSources(integrationSources);
+
+            List<Flows> flows = new ArrayList<>();
+            List<Sources> sources = new ArrayList<>();
+            for (Source source : resolvedSources) {
+                if (useFlows && source.isYaml() && !source.compressed()) {
+                    JsonNode json = KubernetesHelper.json().convertValue(
+                            KubernetesHelper.yaml().load(source.content()), JsonNode.class);
+                    if (json.isArray()) {
+                        for (JsonNode item : json) {
+                            Flows flowSpec = new Flows();
+                            flowSpec.setAdditionalProperties(KubernetesHelper.json().readerFor(Map.class).readValue(item));
+                            flows.add(flowSpec);
+                        }
+                    } else {
+                        Flows flowSpec = new Flows();
+                        flowSpec.setAdditionalProperties(KubernetesHelper.json().readerFor(Map.class).readValue(json));
+                        flows.add(flowSpec);
+                    }
+                } else {
+                    Sources sourceSpec = new Sources();
+                    sourceSpec.setName(source.name());
+                    sourceSpec.setLanguage(source.language());
+                    sourceSpec.setContent(source.content());
+                    sourceSpec.setCompression(source.compressed());
+                    sources.add(sourceSpec);
+                }
+            }
+
+            if (!flows.isEmpty()) {
+                integration.getSpec().setFlows(flows);
+            }
+
+            if (!sources.isEmpty()) {
+                integration.getSpec().setSources(sources);
+            }
+        }
+
+        if (podTemplate != null) {
+            Source templateSource = resolveSource(podTemplate);
+            if (!templateSource.isYaml()) {
+                throw new RuntimeCamelException(
+                        ("Unsupported pod template %s - " +
+                         "please use proper YAML source").formatted(templateSource.extension()));
+            }
+
+            Spec podSpec = KubernetesHelper.yaml().loadAs(templateSource.content(), Spec.class);
+            Template template = new Template();
+            template.setSpec(podSpec);
+            integration.getSpec().setTemplate(template);
+        }
+
+        convertOptionsToTraits(traitsSpec);
+        integration.getSpec().setTraits(traitsSpec);
+
+        if (serviceAccount != null) {
+            integration.getSpec().setServiceAccountName(serviceAccount);
+        }
+
+        if (output != null) {
+            switch (output) {
+                case "yaml" -> printer().println(KubernetesHelper.yaml().dumpAsMap(integration));
+                case "json" -> printer().println(
+                        JSonHelper.prettyPrint(KubernetesHelper.json().writer().writeValueAsString(integration), 2));
+                default -> printer().printf("Unsupported output format %s%n", output);
+            }
+
+            return 0;
+        }
+
+        final AtomicBoolean updated = new AtomicBoolean(false);
+        client(Integration.class).resource(integration).createOr(it -> {
+            updated.set(true);
+            return it.update();
+        });
+
+        if (updated.get()) {
+            printer().printf("Integration %s updated%n", integration.getMetadata().getName());
+        } else {
+            printer().printf("Integration %s created%n", integration.getMetadata().getName());
+        }
+
+        if (wait || logs) {
+            client(Integration.class).withName(integration.getMetadata().getName())
+                    .waitUntilCondition(it -> "Running".equals(it.getStatus().getPhase()), 10, TimeUnit.MINUTES);
+        }
+
+        if (logs) {
+            new IntegrationLogs(getMain()).watchLogs(integration);
+        }
+
+        return 0;
+    }
+
+    private void convertOptionsToTraits(Traits traitsSpec) {
+        Mount mountTrait = null;
+
+        if (configs != null && configs.length > 0) {
+            mountTrait = new Mount();
+            mountTrait.setConfigs(List.of(configs));
+        }
+
+        if (resources != null && resources.length > 0) {
+            if (mountTrait == null) {
+                mountTrait = new Mount();
+            }
+            mountTrait.setResources(List.of(resources));
+        }
+
+        if (volumes != null && volumes.length > 0) {
+            if (mountTrait == null) {
+                mountTrait = new Mount();
+            }
+            mountTrait.setVolumes(List.of(volumes));
+        }
+
+        if (mountTrait != null) {
+            traitsSpec.setMount(mountTrait);
+        }
+
+        if (openApis != null && openApis.length > 0) {
+            Openapi openapiTrait = new Openapi();
+            openapiTrait.setConfigmaps(List.of(openApis));
+            traitsSpec.setOpenapi(openapiTrait);
+        }
+
+        if (properties != null && properties.length > 0) {
+            Camel camelTrait = new Camel();
+            camelTrait.setProperties(List.of(properties));
+            traitsSpec.setCamel(camelTrait);
+        }
+
+        if (buildProperties != null && buildProperties.length > 0) {
+            Builder builderTrait = new Builder();
+            builderTrait.setProperties(List.of(buildProperties));
+            traitsSpec.setBuilder(builderTrait);
+        }
+
+        if (envVars != null && envVars.length > 0) {
+            Environment environmentTrait = new Environment();
+            environmentTrait.setVars(List.of(envVars));
+            traitsSpec.setEnvironment(environmentTrait);
+        }
+
+        if (connects != null && connects.length > 0) {
+            ServiceBinding serviceBindingTrait = new ServiceBinding();
+            serviceBindingTrait.setServices(List.of(connects));
+            traitsSpec.setServiceBinding(serviceBindingTrait);
+        }
+    }
+
+    private Source resolveSource(String source) {
+        List<Source> resolved = resolveSources(Collections.singletonList(source));
+        if (resolved.isEmpty()) {
+            throw new RuntimeCamelException("Failed to resolve source file: " + source);
+        } else {
+            return resolved.get(0);
+        }
+    }
+
+    private List<Source> resolveSources(List<String> sources) {
+        List<Source> resolved = new ArrayList<>();
+        for (String source : sources) {
+            SourceScheme sourceScheme = SourceScheme.fromUri(source);
+            String fileExtension = FileUtil.onlyExt(source);
+            String fileName = SourceScheme.onlyName(FileUtil.onlyName(source)) + "." + fileExtension;
+            try {
+                switch (sourceScheme) {
+                    case GIST -> {
+                        StringJoiner all = new StringJoiner(",");
+                        GistHelper.fetchGistUrls(source, all);
+
+                        try (ResourceResolver resolver = new GistResourceResolver()) {
+                            for (String uri : all.toString().split(",")) {
+                                resolved.add(new Source(
+                                        fileName,
+                                        IOHelper.loadText(resolver.resolve(uri).getInputStream()),
+                                        fileExtension, compression, false));
+                            }
+                        }
+                    }
+                    case HTTP -> {
+                        try (ResourceResolver resolver = new DefaultResourceResolvers.HttpResolver()) {
+                            resolved.add(new Source(
+                                    fileName,
+                                    IOHelper.loadText(resolver.resolve(source).getInputStream()),
+                                    fileExtension, compression, false));
+                        }
+                    }
+                    case HTTPS -> {
+                        try (ResourceResolver resolver = new DefaultResourceResolvers.HttpsResolver()) {
+                            resolved.add(new Source(
+                                    fileName,
+                                    IOHelper.loadText(resolver.resolve(source).getInputStream()),
+                                    fileExtension, compression, false));
+                        }
+                    }
+                    case FILE -> {
+                        try (ResourceResolver resolver = new DefaultResourceResolvers.FileResolver()) {
+                            resolved.add(new Source(
+                                    fileName,
+                                    IOHelper.loadText(resolver.resolve(source).getInputStream()),
+                                    fileExtension, compression, true));
+                        }
+                    }
+                    case CLASSPATH -> {
+                        try (ResourceResolver resolver = new DefaultResourceResolvers.ClasspathResolver()) {
+                            resolver.setCamelContext(new DefaultCamelContext());
+                            resolved.add(new Source(
+                                    fileName,
+                                    IOHelper.loadText(resolver.resolve(source).getInputStream()),
+                                    fileExtension, compression, true));
+                        }
+                    }
+                    case GITHUB, RAW_GITHUB -> {
+                        StringJoiner all = new StringJoiner(",");
+                        GitHubHelper.fetchGithubUrls(source, all);
+
+                        try (ResourceResolver resolver = new GitHubResourceResolver()) {
+                            for (String uri : all.toString().split(",")) {
+                                resolved.add(new Source(
+                                        fileName,
+                                        IOHelper.loadText(resolver.resolve(uri).getInputStream()),
+                                        fileExtension, compression, false));
+                            }
+                        }
+                    }
+                    case UNKNOWN -> {
+                        try (FileInputStream fis = new FileInputStream(source)) {
+                            resolved.add(new Source(fileName, IOHelper.loadText(fis), fileExtension, compression, true));
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                throw new RuntimeCamelException("Failed to resolve sources", e);
+            }
+        }
+        return resolved;
+    }
+
+    private String getIntegrationName(List<String> sources) {
+        if (name != null) {
+            return KubernetesHelper.sanitize(name);
+        } else if (image != null) {
+            return KubernetesHelper.sanitize(image.replaceAll(":", "-v"));
+        } else if (ObjectHelper.isNotEmpty(sources)) {
+            return KubernetesHelper.sanitize(SourceScheme.onlyName(FileUtil.onlyName(sources.get(0))));
+        }
+
+        throw new RuntimeCamelException(
+                "Failed to resolve integration name - please give an image, an explicit name option or a single source file");
+    }
+
+    /**
+     * Normalize dependency expression. Basically replaces "camel-" based artifact names to use proper "camel:" prefix.
+     *
+     * @param  dependency to normalize.
+     * @return            normalized dependency.
+     */
+    private static String normalizeDependency(String dependency) {
+        if (dependency.startsWith("camel-quarkus-")) {
+            return "camel:" + dependency.substring("camel-quarkus-".length());
+        }
+
+        if (dependency.startsWith("camel-quarkus:")) {
+            return "camel:" + dependency.substring("camel-quarkus:".length());
+        }
+
+        if (dependency.startsWith("camel-k-")) {
+            return "camel-k:" + dependency.substring("camel-k-".length());
+        }
+
+        if (dependency.startsWith("camel-")) {
+            return "camel:" + dependency.substring("camel-".length());
+        }
+
+        return dependency;
+    }
+
+    /**
+     * Validates given dependency expression.
+     *
+     * @param dependency to validate.
+     * @param printer    to output potential warnings.
+     */
+    private static void validateDependency(String dependency, Printer printer) {
+        if (dependency.startsWith("mvn:org.apache.camel:")) {
+            String suggested = normalizeDependency(dependency.split(":")[2]);
+            printer.printf("Warning: do not use '%s' as a dependency. Please use '%s' instead%n", dependency, suggested);
+        }
+        if (dependency.startsWith("mvn:org.apache.camel.quarkus:")) {
+            String suggested = normalizeDependency(dependency.split(":")[2]);
+            printer.printf("Warning: do not use '%s' as a dependency. Please use '%s' instead%n", dependency, suggested);
+        }
+    }
+
+    private record Source(String name, String content, String extension, boolean compressed, boolean local) {
+
+        /**
+         * Provides source contant and automatically handles compression of content when enabled.
+         *
+         * @return the content, maybe compressed.
+         */
+        public String content() {
+            if (compressed()) {
+                return CompressionHelper.compressBase64(content);
+            }
+
+            return content;
+        }
+
+        public String language() {
+            if ("yml".equals(extension)) {
+                return "yaml";
+            }
+
+            return extension;
+        }
+
+        public boolean isYaml() {
+            return "yaml".equals(language());
+        }
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java
new file mode 100644
index 00000000000..381cc544612
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java
@@ -0,0 +1,108 @@
+/*
+ * 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.k;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.KubernetesResourceList;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodList;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
+import io.fabric8.kubernetes.client.dsl.PodResource;
+import io.fabric8.kubernetes.client.dsl.Resource;
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import picocli.CommandLine;
+
+/**
+ * Bas command supports Kubernetes client related options such as namespace or custom kube config option. Automatically
+ * applies the options to the Kubernetes client instance that is being used to run commands.
+ */
+abstract class KubeBaseCommand extends CamelCommand {
+
+    @CommandLine.Option(names = { "--kube-config" },
+                        description = "Path to the kube config file to initialize Kubernetes client")
+    String kubeConfig;
+
+    @CommandLine.Option(names = { "--namespace", "-n" }, description = "Namespace to use for all operations")
+    String namespace;
+
+    private KubernetesClient kubernetesClient;
+
+    public KubeBaseCommand(CamelJBangMain main) {
+        super(main);
+    }
+
+    /**
+     * Provides access to the Kubernetes client and automatically sets current namespace if option is given.
+     *
+     * @param  resourceType the Kubernetes resource this client will operate with.
+     * @return              namespaced client if applicable.
+     * @param  <T>          resource type parameter.
+     */
+    protected <T extends HasMetadata> NonNamespaceOperation<T, KubernetesResourceList<T>, Resource<T>> client(
+            Class<T> resourceType) {
+        if (namespace != null) {
+            return client().resources(resourceType).inNamespace(namespace);
+        }
+
+        return client().resources(resourceType);
+    }
+
+    /**
+     * Provides access to Pod resources using the Kubernetes client with current namespace automatically set if option
+     * is given.
+     *
+     * @return namespaced client if applicable.
+     */
+    protected NonNamespaceOperation<Pod, PodList, PodResource> pods() {
+        if (namespace != null) {
+            return client().pods().inNamespace(namespace);
+        }
+
+        return client().pods();
+    }
+
+    /**
+     * Gets Kubernetes client. In case custom kubeConfig option is set initializes the client with the config otherwise
+     * uses default client.
+     *
+     * @return
+     */
+    protected KubernetesClient client() {
+        if (kubernetesClient == null) {
+            if (kubeConfig != null) {
+                kubernetesClient = KubernetesHelper.getKubernetesClient(kubeConfig);
+            }
+
+            kubernetesClient = KubernetesHelper.getKubernetesClient();
+        }
+
+        return kubernetesClient;
+    }
+
+    /**
+     * Sets the Kubernetes client.
+     *
+     * @param kubernetesClient
+     */
+    KubeBaseCommand withClient(KubernetesClient kubernetesClient) {
+        this.kubernetesClient = kubernetesClient;
+        return this;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
new file mode 100644
index 00000000000..8bc5105ceaa
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
@@ -0,0 +1,40 @@
+/*
+ * 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.k;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "k",
+                     description = "Manage Camel integrations on Kubernetes (use config --help to see sub commands)")
+public class KubeCommand extends KubeBaseCommand {
+
+    public static final String OPERATOR_ID_LABEL = "camel.apache.org/operator.id";
+    public static final String INTEGRATION_LABEL = "camel.apache.org/integration";
+    public static final String INTEGRATION_CONTAINER_NAME = "integration";
+
+    public KubeCommand(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        // defaults to list integrations deployed on Kubernetes
+        new CommandLine(new IntegrationGet(getMain())).execute();
+        return 0;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java
new file mode 100644
index 00000000000..85dff123daa
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java
@@ -0,0 +1,151 @@
+/*
+ * 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.k;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.StringHelper;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.nodes.NodeTuple;
+import org.yaml.snakeyaml.nodes.Tag;
+import org.yaml.snakeyaml.representer.Representer;
+
+/**
+ * Helper class provides access to cached Kubernetes client. Also provides access to generic Json and Yaml mappers.
+ */
+public final class KubernetesHelper {
+
+    private static KubernetesClient kubernetesClient;
+
+    /** Clients with custom config */
+    private static final Map<String, KubernetesClient> clients = new HashMap<>();
+
+    private static final ObjectMapper OBJECT_MAPPER;
+
+    static {
+        OBJECT_MAPPER = JsonMapper.builder()
+                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+                .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
+                .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
+                .disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
+                .enable(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
+                .build()
+                .setDefaultPropertyInclusion(
+                        JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, JsonInclude.Include.NON_EMPTY));
+    }
+
+    private KubernetesHelper() {
+        //prevent instantiation of utility class.
+    }
+
+    /**
+     * Gets the default Kubernetes client.
+     *
+     * @return
+     */
+    public static KubernetesClient getKubernetesClient() {
+        if (kubernetesClient == null) {
+            kubernetesClient = new KubernetesClientBuilder().build();
+        }
+
+        return kubernetesClient;
+    }
+
+    /**
+     * Create or get Kubernetes client with given config.
+     *
+     * @param  config
+     * @return
+     */
+    public static KubernetesClient getKubernetesClient(String config) {
+        if (clients.containsKey(config)) {
+            return clients.get(config);
+        }
+
+        return clients.put(config, new KubernetesClientBuilder().withConfig(config).build());
+    }
+
+    /**
+     * Creates new Yaml instance. The implementation provided by Snakeyaml is not thread-safe. It is better to create a
+     * fresh instance for every YAML stream.
+     *
+     * @return
+     */
+    public static Yaml yaml() {
+        Representer representer = new Representer(new DumperOptions()) {
+            @Override
+            protected NodeTuple representJavaBeanProperty(
+                    Object javaBean, Property property, Object propertyValue, Tag customTag) {
+                // if value of property is null, ignore it.
+                if (propertyValue == null || (propertyValue instanceof Collection && ((Collection<?>) propertyValue).isEmpty())
+                        ||
+                        (propertyValue instanceof Map && ((Map<?, ?>) propertyValue).isEmpty())) {
+                    return null;
+                } else {
+                    return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
+                }
+            }
+        };
+        representer.getPropertyUtils().setSkipMissingProperties(true);
+        return new Yaml(representer);
+    }
+
+    public static ObjectMapper json() {
+        return OBJECT_MAPPER;
+    }
+
+    /**
+     * Sanitize given name to meet Kubernetes resource naming requirements.
+     *
+     * @param  name to sanitize.
+     * @return      sanitized name ready to be used as a Kubernetes resource name.
+     */
+    public static String sanitize(String name) {
+        name = FileUtil.onlyName(name);
+        name = StringHelper.sanitize(name);
+        name = StringHelper.camelCaseToDash(name);
+        name = name.toLowerCase(Locale.US);
+        name = name.replaceAll("[^a-z0-9-]", "");
+        name = name.trim();
+        return name;
+    }
+
+    /**
+     * Overwrites the kubernetes client. Typically used by unit tests.
+     *
+     * @param kubernetesClient
+     */
+    static void setKubernetesClient(KubernetesClient kubernetesClient) {
+        KubernetesHelper.kubernetesClient = kubernetesClient;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java
new file mode 100644
index 00000000000..90fdbfdfabe
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java
@@ -0,0 +1,78 @@
+/*
+ * 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.k;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Supported set of file resource and URL schemes that may be used to resolve an integration resource (e.g. source
+ * file).
+ */
+public enum SourceScheme {
+
+    GIST("https://gist.github"),
+    GITHUB("https://github.com/"),
+    RAW_GITHUB("https://raw.githubusercontent.com/"),
+    FILE,
+    CLASSPATH,
+    HTTP,
+    HTTPS,
+    UNKNOWN;
+
+    private final String uri;
+
+    SourceScheme() {
+        this(null);
+    }
+
+    SourceScheme(String uri) {
+        this.uri = uri;
+    }
+
+    /**
+     * Try to resolve source scheme from given file path URL. Checks for special GIST and GITHUB endpoint URLs. By
+     * default, uses unknown scheme usually leads to loading resource from file system.
+     *
+     * @param  path
+     * @return
+     */
+    public static SourceScheme fromUri(String path) {
+        return Arrays.stream(values())
+                .filter(scheme -> path.startsWith(scheme.name().toLowerCase(Locale.US) + ":") ||
+                        (scheme.uri != null && path.startsWith(scheme.uri)))
+                .findFirst()
+                .orElse(UNKNOWN); // use file as default scheme
+    }
+
+    /**
+     * If any strip scheme prefix from given name.
+     *
+     * @param  name
+     * @return
+     */
+    public static String onlyName(String name) {
+        for (SourceScheme scheme : values()) {
+            if (name.startsWith(scheme.name().toLowerCase(Locale.US) + ":")) {
+                return name.substring(scheme.name().length() + 1);
+            }
+        }
+
+        return name;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java
new file mode 100644
index 00000000000..6af102f09ca
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java
@@ -0,0 +1,125 @@
+/*
+ * 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.k;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.v1.integrationspec.Traits;
+
+/**
+ * Utility class manages trait expressions and its conversion to proper trait model.
+ */
+public final class TraitHelper {
+
+    private TraitHelper() {
+        //prevent instantiation of utility class.
+    }
+
+    /**
+     * Parses given list of trait expressions to proper trait model object.
+     *
+     * @param  traits
+     * @return
+     */
+    public static Traits parseTraits(String[] traits) {
+        try {
+            Map<String, Map<String, Object>> traitJson = new HashMap<>();
+
+            for (String traitExpression : traits) {
+                //traitName.key=value
+                final String[] trait = traitExpression.split("\\.", 2);
+                final String[] traitConfig = trait[1].split("=", 2);
+
+                final String traitKey = traitConfig[0];
+                final Object traitValue = resolveTraitValue(traitKey, traitConfig[1].trim());
+                if (traitJson.containsKey(trait[0])) {
+                    Map<String, Object> config = traitJson.get(trait[0]);
+
+                    if (config.containsKey(traitKey)) {
+                        Object existingValue = config.get(traitKey);
+
+                        if (existingValue instanceof List) {
+                            List<String> values = (List<String>) existingValue;
+                            values.add(traitValue.toString());
+                        } else {
+                            config.put(traitKey, Arrays.asList(existingValue.toString(), traitValue));
+                        }
+                    } else {
+                        config.put(traitKey, initializeTraitValue(traitValue));
+                    }
+                } else {
+                    Map<String, Object> props = new HashMap<>();
+                    props.put(traitKey, initializeTraitValue(traitValue));
+                    traitJson.put(trait[0], props);
+                }
+            }
+
+            return KubernetesHelper.json().readerFor(Traits.class).readValue(
+                    KubernetesHelper.json().writeValueAsString(traitJson));
+        } catch (IOException e) {
+            throw new RuntimeCamelException("Failed to parse trait options", e);
+        }
+    }
+
+    /**
+     * Resolve trait value with automatic type conversion. Some trait keys (like enabled, verbose) need to be converted
+     * to boolean type.
+     *
+     * @param  traitKey
+     * @param  value
+     * @return
+     */
+    private static Object resolveTraitValue(String traitKey, String value) {
+        if (value.startsWith("\"") && value.endsWith("\"")) {
+            return value.substring(1, value.length() - 1);
+        }
+
+        if (value.startsWith("'") && value.endsWith("'")) {
+            return value.substring(1, value.length() - 1);
+        }
+
+        if (traitKey.equalsIgnoreCase("enabled") ||
+                traitKey.equalsIgnoreCase("verbose")) {
+            return Boolean.valueOf(value);
+        }
+
+        return value;
+    }
+
+    /**
+     * Initialize trait value with support for array type values.
+     *
+     * @param  value
+     * @return
+     */
+    private static Object initializeTraitValue(Object value) {
+        if (value instanceof String && value.toString().startsWith("[") && value.toString().endsWith("]")) {
+            List<String> values = new ArrayList<>();
+            values.add(resolveTraitValue("", value.toString().substring(1, value.toString().length() - 1)).toString());
+            return values;
+        }
+
+        return value;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
new file mode 100644
index 00000000000..4d5e1664b12
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
@@ -0,0 +1,25 @@
+/*
+ * 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.k;
+
+public enum TraitProfile {
+
+    OPENSHIFT,
+    KUBERNETES,
+    KNATIVE
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionGet.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionGet.java
index 1ab33c625d7..49cf8195932 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionGet.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionGet.java
@@ -44,6 +44,7 @@ public class VersionGet extends CamelCommand {
 
         CommandLineHelper.loadProperties(properties -> {
             String uv = properties.getProperty("camel-version");
+            String kv = properties.getProperty("kamelets-version");
             String repos = properties.getProperty("repos");
             String runtime = properties.getProperty("runtime");
             if (uv != null || repos != null || runtime != null) {
@@ -51,6 +52,9 @@ public class VersionGet extends CamelCommand {
                 if (uv != null) {
                     System.out.println("    camel-version = " + uv);
                 }
+                if (kv != null) {
+                    System.out.println("    kamelets-version = " + uv);
+                }
                 if (runtime != null) {
                     System.out.println("    runtime = " + runtime);
                 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/Printer.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/Printer.java
new file mode 100644
index 00000000000..65cc79ee986
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/Printer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.common;
+
+/**
+ * Printer interface used by commands to write output to given print stream. By default, uses System out print stream,
+ * but unit tests for instance may use a different print stream.
+ */
+public interface Printer {
+
+    default void println(String line) {
+        System.out.println(line);
+    }
+
+    default void printf(String format, Object... args) {
+        System.out.printf(format, args);
+    }
+
+    /**
+     * Default printer uses System out print stream.
+     */
+    class SystemOutPrinter implements Printer {
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/StringPrinter.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/StringPrinter.java
new file mode 100644
index 00000000000..3370e0c5991
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/StringPrinter.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.common.Printer;
+
+public class StringPrinter implements Printer {
+
+    private final StringWriter writer = new StringWriter();
+
+    @Override
+    public void println(String line) {
+        writer.write(line + "\n");
+    }
+
+    @Override
+    public void printf(String format, Object... args) {
+        writer.write(format.formatted(args));
+    }
+
+    /**
+     * Provides access to the cached output.
+     *
+     * @return
+     */
+    public String getOutput() {
+        return writer.toString().trim();
+    }
+
+    /**
+     * Provides access to all lines of the cached output.
+     *
+     * @return
+     * @throws IOException
+     */
+    public List<String> getLines() throws IOException {
+        BufferedReader buf = new BufferedReader(new StringReader(getOutput()));
+        List<String> lines = new ArrayList<>();
+        String line;
+        while ((line = buf.readLine()) != null) {
+            lines.add(line.trim());
+        }
+
+        return lines;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java
new file mode 100644
index 00000000000..2d4d8a8c1fa
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.k;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class IntegrationDeleteTest extends KubeBaseTest {
+
+    @Test
+    public void shouldVerifyArguments() throws Exception {
+        Assertions.assertThrows(RuntimeCamelException.class, createCommand()::doCall,
+                "Missing integration name as argument or --all option.");
+    }
+
+    @Test
+    public void shouldDeleteIntegration() throws Exception {
+        Integration integration = createIntegration();
+        kubernetesClient.resources(Integration.class).resource(integration).create();
+
+        IntegrationDelete command = createCommand();
+        command.names = new String[] { integration.getMetadata().getName() };
+        command.doCall();
+
+        Assertions.assertEquals("Integration routes deleted", printer.getOutput());
+
+        Assertions.assertEquals(0, kubernetesClient.resources(Integration.class).list().getItems().size());
+    }
+
+    @Test
+    public void shouldHandleIntegrationNotFound() throws Exception {
+        IntegrationDelete command = createCommand();
+        command.names = new String[] { "mickey-mouse" };
+        command.doCall();
+
+        Assertions.assertEquals("Integration mickey-mouse deletion skipped - not found", printer.getOutput());
+    }
+
+    @Test
+    public void shouldDeleteAll() throws Exception {
+        Integration integration1 = createIntegration("foo");
+        Integration integration2 = createIntegration("bar");
+
+        kubernetesClient.resources(Integration.class).resource(integration1).create();
+        kubernetesClient.resources(Integration.class).resource(integration2).create();
+
+        IntegrationDelete command = createCommand();
+        command.all = true;
+        command.doCall();
+
+        Assertions.assertEquals("Integrations deleted", printer.getOutput());
+        Assertions.assertEquals(0, kubernetesClient.resources(Integration.class).list().getItems().size());
+    }
+
+    private IntegrationDelete createCommand() {
+        IntegrationDelete command = new IntegrationDelete(new CamelJBangMain().withPrinter(printer));
+        command.withClient(kubernetesClient);
+        return command;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java
new file mode 100644
index 00000000000..5bdcbe72192
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.k;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import org.apache.camel.v1.IntegrationStatus;
+import org.apache.camel.v1.integrationstatus.Conditions;
+import org.apache.camel.v1.integrationstatus.IntegrationKit;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class IntegrationGetTest extends KubeBaseTest {
+
+    @Test
+    public void shouldListIntegrationsEmpty() throws Exception {
+        createCommand().doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+    }
+
+    @Test
+    public void shouldListReadyIntegration() throws Exception {
+        Integration integration = createIntegration();
+
+        IntegrationStatus status = new IntegrationStatus();
+
+        IntegrationKit kit = new IntegrationKit();
+        kit.setName("kit-123456789");
+        status.setIntegrationKit(kit);
+
+        status.setPhase("Running");
+        status.setConditions(new ArrayList<>());
+        Conditions readyCondition = new Conditions();
+        readyCondition.setType("Ready");
+        readyCondition.setStatus("True");
+        status.getConditions().add(readyCondition);
+        integration.setStatus(status);
+
+        kubernetesClient.resources(Integration.class).resource(integration).create();
+
+        createCommand().doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals("NAME    PHASE    KIT            READY", output.get(0));
+        Assertions.assertEquals("routes  Running  kit-123456789   1/1", output.get(1));
+    }
+
+    @Test
+    public void shouldListPendingIntegration() throws Exception {
+        Integration integration = createIntegration("building");
+        IntegrationStatus status = new IntegrationStatus();
+
+        status.setPhase("Building Kit");
+        status.setConditions(new ArrayList<>());
+        Conditions readyCondition = new Conditions();
+        readyCondition.setType("Ready");
+        readyCondition.setStatus("False");
+        status.getConditions().add(readyCondition);
+        integration.setStatus(status);
+
+        kubernetesClient.resources(Integration.class).resource(integration).create();
+
+        createCommand().doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals("NAME      PHASE         KIT  READY", output.get(0));
+        Assertions.assertEquals("building  Building Kit        0/1", output.get(1));
+    }
+
+    @Test
+    public void shouldListIntegrationNames() throws Exception {
+        Integration integration1 = createIntegration("foo");
+        Integration integration2 = createIntegration("bar");
+
+        kubernetesClient.resources(Integration.class).resource(integration1).create();
+        kubernetesClient.resources(Integration.class).resource(integration2).create();
+
+        IntegrationGet command = createCommand();
+        command.name = true;
+        command.doCall();
+
+        Assertions.assertEquals("foo\nbar", printer.getOutput());
+    }
+
+    private IntegrationGet createCommand() {
+        IntegrationGet command = new IntegrationGet(new CamelJBangMain().withPrinter(printer));
+        command.withClient(kubernetesClient);
+        return command;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java
new file mode 100644
index 00000000000..de80a26bf29
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.k;
+
+import java.util.Collections;
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class IntegrationLogsTest extends KubeBaseTest {
+
+    @Test
+    public void shouldHandleIntegrationsNotFound() throws Exception {
+        IntegrationLogs command = createCommand();
+        command.name = "mickey-mouse";
+        command.doCall();
+
+        Assertions.assertEquals("Integration mickey-mouse not found", printer.getOutput());
+    }
+
+    @Test
+    public void shouldGetIntegrationLogs() throws Exception {
+        Integration integration = createIntegration();
+        kubernetesClient.resources(Integration.class).resource(integration).create();
+
+        Pod pod = new PodBuilder()
+                .withNewMetadata()
+                .withName(integration.getMetadata().getName())
+                .withLabels(Collections.singletonMap(KubeCommand.INTEGRATION_LABEL, integration.getMetadata().getName()))
+                .endMetadata()
+                .withNewStatus()
+                .withPhase("Running")
+                .endStatus()
+                .build();
+
+        kubernetesClient.pods().resource(pod).create();
+
+        IntegrationLogs command = createCommand();
+
+        command.name = "routes";
+        command.doCall();
+    }
+
+    private IntegrationLogs createCommand() {
+        IntegrationLogs command = new IntegrationLogs(new CamelJBangMain().withPrinter(printer));
+        command.withClient(kubernetesClient);
+        return command;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java
new file mode 100644
index 00000000000..9a5913900ab
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java
@@ -0,0 +1,594 @@
+/*
+ * 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.k;
+
+import java.util.regex.Pattern;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class IntegrationRunTest extends KubeBaseTest {
+
+    @Test
+    public void shouldHandleMissingSourceFile() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "mickey-mouse.groovy" };
+        Assertions.assertThrows(RuntimeCamelException.class, command::doCall, "Failed to resolve sources");
+    }
+
+    @Test
+    public void shouldRunIntegration() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.doCall();
+
+        Assertions.assertEquals("Integration route created", printer.getOutput());
+
+        Integration created = kubernetesClient.resources(Integration.class).withName("route").get();
+        Assertions.assertEquals("camel-k", created.getMetadata().getAnnotations().get(KubeCommand.OPERATOR_ID_LABEL));
+    }
+
+    @Test
+    public void shouldUpdateIntegration() throws Exception {
+        Integration integration = createIntegration("route");
+        kubernetesClient.resources(Integration.class).resource(integration).create();
+
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.doCall();
+
+        Assertions.assertEquals("Integration route updated", printer.getOutput());
+
+        Integration created = kubernetesClient.resources(Integration.class).withName("route").get();
+        Assertions.assertEquals("camel-k", created.getMetadata().getAnnotations().get(KubeCommand.OPERATOR_ID_LABEL));
+    }
+
+    @Test
+    public void shouldAddTraits() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.traits = new String[] { "logging.level=DEBUG", "container.imagePullPolicy=ALWAYS" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    container:
+                      imagePullPolicy: ALWAYS
+                    logging:
+                      level: DEBUG""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldSpecFromOptions() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.name = "custom";
+        command.operatorId = "custom-operator";
+        command.serviceAccount = "service-account-name";
+        command.labels = new String[] { "custom-label=enabled" };
+        command.annotations = new String[] { "custom-annotation=enabled" };
+        command.repositories = new String[] { "http://custom-repository" };
+        command.profile = "knative";
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    custom-annotation: enabled
+                    camel.apache.org/operator.id: custom-operator
+                  labels:
+                    custom-label: enabled
+                  name: custom
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  profile: knative
+                  repositories:
+                  - http://custom-repository
+                  serviceAccountName: service-account-name
+                  traits: {}""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddVolumes() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.volumes = new String[] { "/foo", "/bar" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    mount:
+                      volumes:
+                      - /foo
+                      - /bar""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddDependencies() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.dependencies = new String[] { "camel-jackson", "camel-quarkus-jms", "mvn:foo:bar:1.0" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  dependencies:
+                  - camel:jackson
+                  - camel:jms
+                  - mvn:foo:bar:1.0
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits: {}""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddEnvVars() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.envVars = new String[] { "CAMEL_FOO=bar" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    environment:
+                      vars:
+                      - CAMEL_FOO=bar""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddProperties() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.properties = new String[] { "camel.foo=bar" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    camel:
+                      properties:
+                      - camel.foo=bar""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddBuildProperties() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.buildProperties = new String[] { "camel.foo=bar" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    builder:
+                      properties:
+                      - camel.foo=bar""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldUseKit() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.kit = "kit-123456789";
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  integrationKit:
+                    name: kit-123456789
+                  traits: {}""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddSources() throws Exception {
+        IntegrationRun command = createCommand();
+        command.sources = new String[] { "classpath:route.yaml" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits: {}""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddConnects() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.connects = new String[] { "serving.knative.dev/v1:Service:foo" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    serviceBinding:
+                      services:
+                      - serving.knative.dev/v1:Service:foo""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldUsePodTemplate() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.podTemplate = "classpath:pod.yaml";
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  template:
+                    spec:
+                      containers:
+                      - env:
+                        - name: TEST
+                          value: TEST
+                        name: integration
+                        volumeMounts:
+                        - mountPath: /var/log
+                          name: var-logs
+                      volumes:
+                      - emptyDir: {}
+                        name: var-logs
+                  traits: {}""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddConfigs() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.configs = new String[] { "secret:foo", "configmap:bar" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    mount:
+                      configs:
+                      - secret:foo
+                      - configmap:bar""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddResources() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.resources = new String[] { "configmap:foo/file.txt" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    mount:
+                      resources:
+                      - configmap:foo/file.txt""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldAddOpenApis() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.openApis = new String[] { "configmap:openapi/spec.yaml" };
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits:
+                    openapi:
+                      configmaps:
+                      - configmap:openapi/spec.yaml""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldUseImage() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.image = "quay.io/camel/demo-app:1.0";
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: demo-app-v1
+                spec:
+                  traits:
+                    container:
+                      image: quay.io/camel/demo-app:1.0""", printer.getOutput());
+    }
+
+    @Test
+    public void shouldUseCompression() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.compression = true;
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals(
+                """
+                        apiVersion: camel.apache.org/v1
+                        kind: Integration
+                        metadata:
+                          annotations:
+                            camel.apache.org/operator.id: camel-k
+                          name: route
+                        spec:
+                          sources:
+                          - compression: true
+                            content: ZFNNb6NADL3zK5zk0kr5WO2RPbFpoqKtiBRoqxwnYMAqzLAzZmn+/XoI2UbauSDP2M/vPZtFsIAXylE7LIANcI0QdSqXT2pKHpRF2JteF4rJaHiI0v0jSIgWjEYwFlpjUUByo9nSuWe5aq6AoCqL2KJmtwZIEUf05JDF2x2U1CAU5K5F0nwgrgWHa3IwGPsBpSCpoiDfWDVAWi7aKw2LlbIF6UradhdLVc1gBo3W1dStBSXzMtL9jYm7wo49ReTJ9JOGO7mTC0t4Exjf5Pv6myA9+JT59Dh//AEXKW7VBbRh6B3eIeNnjh0LUWHVdg0pneOXrH8dxIvThGHOrCRdjTLAlPdpoDhYSOV4auYu3GyGYVirke7a2GpzU7d5EUeTdLcaKUvNq27QObHpd09WvD1fQHXCKFdn4dmowQ9unM44dKEwWPFZV0tw09QF5X46X3bd6In [...]
+                            language: yaml
+                            name: route.yaml
+                          traits: {}""",
+                removeLicenseHeader(printer.getOutput()));
+    }
+
+    private final Pattern comments = Pattern.compile("^\\s*#.*$", Pattern.MULTILINE);
+    private final Pattern emptyLine = Pattern.compile("^[\\r?\\n]$", Pattern.MULTILINE);
+
+    private String removeLicenseHeader(String yaml) {
+        return emptyLine.matcher(comments.matcher(yaml).replaceAll("")).replaceAll("");
+    }
+
+    @Test
+    public void shouldHandleUseFlowsDisabledOption() throws Exception {
+        IntegrationRun command = createCommand();
+        command.filePaths = new String[] { "classpath:route.yaml" };
+        command.useFlows = false;
+        command.output = "yaml";
+        command.doCall();
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  sources:
+                  - compression: false
+                    content: |
+
+                      from:
+                        uri: timer:tick
+                        steps:
+                          - set-body:
+                              constant: Hello Camel !!!
+                          - to: log:info
+                    language: yaml
+                    name: route.yaml
+                  traits: {}""", removeLicenseHeader(printer.getOutput()));
+    }
+
+    private IntegrationRun createCommand() {
+        IntegrationRun command = new IntegrationRun(new CamelJBangMain().withPrinter(printer));
+        command.withClient(kubernetesClient);
+        return command;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
new file mode 100644
index 00000000000..86856f6a31c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.k;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesCrudDispatcher;
+import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
+import io.fabric8.mockwebserver.Context;
+import okhttp3.mockwebserver.MockWebServer;
+import org.apache.camel.dsl.jbang.core.commands.StringPrinter;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.v1.Integration;
+import org.apache.camel.v1.IntegrationSpec;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInstance;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class KubeBaseTest {
+
+    protected static Integration integration;
+
+    private KubernetesMockServer k8sServer;
+
+    protected KubernetesClient kubernetesClient;
+
+    protected StringPrinter printer;
+
+    @BeforeAll
+    public void setupFixtures() {
+        k8sServer = new KubernetesMockServer(
+                new Context(), new MockWebServer(),
+                new HashMap<>(), new KubernetesCrudDispatcher(), false);
+
+        kubernetesClient = k8sServer.createClient();
+    }
+
+    @BeforeEach
+    public void setup() {
+        printer = new StringPrinter();
+        k8sServer.reset();
+    }
+
+    @AfterAll
+    public void cleanup() {
+        k8sServer.destroy();
+    }
+
+    protected Integration createIntegration() throws IOException {
+        return createIntegration("routes");
+    }
+
+    protected Integration createIntegration(String name) throws IOException {
+        if (integration == null) {
+            integration = KubernetesHelper.yaml().loadAs(
+                    IOHelper.loadText(KubeBaseTest.class.getResourceAsStream("integration.yaml")), Integration.class);
+        }
+
+        Integration created = new Integration();
+        created.getMetadata().setName(name);
+        created.setSpec(new IntegrationSpec());
+        created.getSpec().setTraits(integration.getSpec().getTraits());
+        created.getSpec().setFlows(integration.getSpec().getFlows());
+
+        return created;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
new file mode 100644
index 00000000000..8333dc0b278
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.k;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.v1.Integration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class KubeCommandMainTest extends KubeBaseTest {
+
+    @Test
+    public void shouldDeleteIntegration() throws IOException {
+        KubernetesHelper.setKubernetesClient(kubernetesClient);
+        CamelJBangMain.run(createMain(), "k", "delete", "--all");
+
+        Assertions.assertEquals("Integrations deleted", printer.getOutput());
+    }
+
+    @Test
+    public void shouldListIntegration() throws IOException {
+        KubernetesHelper.setKubernetesClient(kubernetesClient);
+
+        kubernetesClient.resources(Integration.class).resource(createIntegration()).create();
+
+        CamelJBangMain.run(createMain(), "k", "get");
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals("NAME    PHASE    KIT  READY", output.get(0));
+        Assertions.assertEquals("routes  Unknown        0/1", output.get(1));
+    }
+
+    @Test
+    public void shouldPrintIntegrationLogs() throws IOException {
+        KubernetesHelper.setKubernetesClient(kubernetesClient);
+
+        kubernetesClient.resources(Integration.class).resource(createIntegration()).create();
+
+        Pod pod = new PodBuilder()
+                .withNewMetadata()
+                .withName(integration.getMetadata().getName())
+                .withLabels(Collections.singletonMap(KubeCommand.INTEGRATION_LABEL, integration.getMetadata().getName()))
+                .endMetadata()
+                .withNewStatus()
+                .withPhase("Running")
+                .endStatus()
+                .build();
+
+        kubernetesClient.pods().resource(pod).create();
+
+        CamelJBangMain.run(createMain(), "k", "logs", "routes");
+    }
+
+    @Test
+    public void shouldRunIntegration() {
+        KubernetesHelper.setKubernetesClient(kubernetesClient);
+        CamelJBangMain.run(createMain(), "k", "run", "classpath:route.yaml");
+
+        Integration integration = kubernetesClient.resources(Integration.class).withName("route").get();
+        Assertions.assertNotNull(integration);
+        Assertions.assertEquals(integration.getMetadata().getAnnotations().get(KubeCommand.OPERATOR_ID_LABEL), "camel-k");
+    }
+
+    @Test
+    public void shouldPrintIntegration() {
+        CamelJBangMain.run(createMain(), "k", "run", "classpath:route.yaml", "-o", "yaml");
+
+        Assertions.assertEquals("""
+                apiVersion: camel.apache.org/v1
+                kind: Integration
+                metadata:
+                  annotations:
+                    camel.apache.org/operator.id: camel-k
+                  name: route
+                spec:
+                  flows:
+                  - additionalProperties:
+                      from:
+                        uri: timer:tick
+                        steps:
+                        - set-body:
+                            constant: Hello Camel !!!
+                        - to: log:info
+                  traits: {}""", printer.getOutput());
+    }
+
+    private CamelJBangMain createMain() {
+        return new CamelJBangMain() {
+            @Override
+            protected void quit(int exitCode) {
+                if (exitCode != 0) {
+                    Assertions.fail("Main finished with exit code %d".formatted(exitCode));
+                }
+            }
+        }.withPrinter(printer);
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml b/dsl/camel-jbang/camel-jbang-core/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
new file mode 100644
index 00000000000..6b85aa4c7a5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+
+apiVersion: camel.apache.org/v1
+kind: Integration
+metadata:
+  annotations:
+    camel.apache.org/operator.id: camel-k
+  name: routes
+spec:
+  flows:
+    - additionalProperties:
+        from:
+          uri: timer:tick
+          steps:
+            - set-body:
+                constant: Hello Camel !!!
+            - to: log:info
+  traits:
+    logging:
+      level: DEBUG
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/pod.yaml b/dsl/camel-jbang/camel-jbang-core/src/test/resources/pod.yaml
new file mode 100644
index 00000000000..5c62a01c52b
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/pod.yaml
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+containers:
+  - name: integration
+    env:
+      - name: TEST
+        value: TEST
+    volumeMounts:
+      - name: var-logs
+        mountPath: /var/log
+volumes:
+  - name: var-logs
+    emptyDir: { }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/route.yaml b/dsl/camel-jbang/camel-jbang-core/src/test/resources/route.yaml
new file mode 100644
index 00000000000..4485c1f5379
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/route.yaml
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+from:
+  uri: timer:tick
+  steps:
+    - set-body:
+        constant: Hello Camel !!!
+    - to: log:info
diff --git a/parent/pom.xml b/parent/pom.xml
index b6cd62eb182..718da83cd06 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -82,6 +82,7 @@
         <californium-version>2.8.0</californium-version>
         <californium-scandium-version>2.8.0</californium-scandium-version>
         <camunda-version>7.20.0</camunda-version>
+        <camel-k-version>2.1.0</camel-k-version>
         <cassandra-driver-version>4.17.0</cassandra-driver-version>
         <jta-api-1.2-version>1.2</jta-api-1.2-version>
         <cglib-version>3.3.0</cglib-version>