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

[camel-karavan] branch main updated (2ffe150e -> b383fbc0)

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

marat pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


    from 2ffe150e Cleanup docs
     new 76eb6d6e Bump version to 3.21.1-SNAPSHOT
     new c7c9aaf3 First experiment for #817
     new ac306f7f Infinispan and Karavan up and running #817
     new a7fb2244 Infinispan and Karavan up and running #817
     new b383fbc0 Runner experiment #817

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/app.yml                          |   2 +-
 .github/workflows/builder.yml                      |   2 +-
 .github/workflows/cli.yml                          |   2 +-
 .github/workflows/operator.yml                     |   2 +-
 .github/workflows/runner.yml                       |   2 +-
 karavan-app/pom.xml                                |   2 +-
 .../apache/camel/karavan/api/RunnerResource.java   |  13 +-
 .../karavan/listener/ClientRunnerListener.java     |  46 +++
 .../karavan/listener/LocalRunnerListener.java      |  48 +++
 .../camel/karavan/listener/RunnerListener.java     |  35 ++
 .../org/apache/camel/karavan/model/GroupedKey.java |  12 +
 .../apache/camel/karavan/model/RunnerCommand.java  |  13 +
 .../camel/karavan/service/InfinispanService.java   | 113 +++---
 .../camel/karavan/service/KaravanService.java      |   6 +-
 .../camel/karavan/service/KubernetesService.java   |  64 ++--
 .../camel/karavan/service/RunnerService.java       |  22 +-
 .../src/main/resources/application.properties      |   4 +-
 karavan-app/src/main/webui/package.json            |   2 +-
 .../src/main/webui/src/designer/karavan.css        |   8 +
 .../.dockerignore                                  |   0
 karavan-bashi/.gitignore                           |  43 +++
 karavan-bashi/.mvn/wrapper/.gitignore              |   1 +
 .../.mvn/wrapper/MavenWrapperDownloader.java       |  98 +++++
 .../.mvn/wrapper/maven-wrapper.properties          |   0
 {karavan-app => karavan-bashi}/mvnw                |   0
 {karavan-operator => karavan-bashi}/mvnw.cmd       | 410 ++++++++++-----------
 karavan-bashi/pom.xml                              | 140 +++++++
 karavan-bashi/src/main/docker/Dockerfile.jvm       |  95 +++++
 .../src/main/docker/Dockerfile.legacy-jar          |  91 +++++
 karavan-bashi/src/main/docker/Dockerfile.native    |  27 ++
 .../src/main/docker/Dockerfile.native-micro        |  30 ++
 .../camel/karavan/bashi/ConductorService.java      | 104 ++++++
 .../org/apache/camel/karavan/bashi/Constants.java  |  10 +
 .../apache/camel/karavan/bashi/KaravanBashi.java   |  35 ++
 .../camel/karavan/bashi/RunnerStatusService.java   |  83 +++++
 .../karavan/bashi/docker/DockerEventListener.java  |  84 +++++
 .../camel/karavan/bashi/docker/DockerService.java  | 194 ++++++++++
 .../bashi/infinispan/ClientRunnerListener.java     |  49 +++
 .../karavan/bashi/infinispan}/GroupedKey.java      |  14 +-
 .../bashi/infinispan/InfinispanService.java        | 122 ++++++
 .../camel/karavan/bashi/infinispan}/PodStatus.java |   2 +-
 .../bashi/infinispan/ProjectStoreSchema.java       |   8 +
 .../karavan/bashi/infinispan/RunnerCommand.java    |  13 +
 .../src/main/resources/application.properties      |  26 ++
 karavan-cli/pom.xml                                |   2 +-
 karavan-core/package.json                          |   2 +-
 karavan-designer/package.json                      |   2 +-
 karavan-generator/pom.xml                          |   2 +-
 karavan-operator/Makefile                          |   2 +-
 karavan-operator/pom.xml                           |   2 +-
 .../operator/spec/KaravanOperatorCSVMetadata.java  |   4 +-
 .../src/main/resources/application.properties      |   4 +-
 karavan-space/package.json                         |   2 +-
 karavan-vscode/package.json                        |   2 +-
 54 files changed, 1771 insertions(+), 330 deletions(-)
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/listener/ClientRunnerListener.java
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/listener/LocalRunnerListener.java
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/listener/RunnerListener.java
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerCommand.java
 copy karavan-app/Dockerfile.legacy-jar.dockerignore => karavan-bashi/.dockerignore (100%)
 create mode 100644 karavan-bashi/.gitignore
 create mode 100644 karavan-bashi/.mvn/wrapper/.gitignore
 create mode 100644 karavan-bashi/.mvn/wrapper/MavenWrapperDownloader.java
 copy {karavan-app => karavan-bashi}/.mvn/wrapper/maven-wrapper.properties (100%)
 copy {karavan-app => karavan-bashi}/mvnw (100%)
 copy {karavan-operator => karavan-bashi}/mvnw.cmd (97%)
 mode change 100644 => 100755
 create mode 100644 karavan-bashi/pom.xml
 create mode 100644 karavan-bashi/src/main/docker/Dockerfile.jvm
 create mode 100644 karavan-bashi/src/main/docker/Dockerfile.legacy-jar
 create mode 100644 karavan-bashi/src/main/docker/Dockerfile.native
 create mode 100644 karavan-bashi/src/main/docker/Dockerfile.native-micro
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/ConductorService.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/Constants.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/RunnerStatusService.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ClientRunnerListener.java
 copy {karavan-app/src/main/java/org/apache/camel/karavan/model => karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan}/GroupedKey.java (79%)
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/InfinispanService.java
 copy {karavan-app/src/main/java/org/apache/camel/karavan/model => karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan}/PodStatus.java (99%)
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ProjectStoreSchema.java
 create mode 100644 karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/RunnerCommand.java
 create mode 100644 karavan-bashi/src/main/resources/application.properties


[camel-karavan] 01/05: Bump version to 3.21.1-SNAPSHOT

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 76eb6d6ee75f571f12916a6c6e2020bdbb5a3cf7
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 6 11:31:00 2023 -0400

    Bump version to 3.21.1-SNAPSHOT
---
 .github/workflows/app.yml                                             | 2 +-
 .github/workflows/builder.yml                                         | 2 +-
 .github/workflows/cli.yml                                             | 2 +-
 .github/workflows/operator.yml                                        | 2 +-
 .github/workflows/runner.yml                                          | 2 +-
 karavan-app/pom.xml                                                   | 2 +-
 karavan-app/src/main/resources/application.properties                 | 2 +-
 karavan-app/src/main/webui/package.json                               | 2 +-
 karavan-cli/pom.xml                                                   | 2 +-
 karavan-core/package.json                                             | 2 +-
 karavan-designer/package.json                                         | 2 +-
 karavan-generator/pom.xml                                             | 2 +-
 karavan-operator/Makefile                                             | 2 +-
 karavan-operator/pom.xml                                              | 2 +-
 .../camel/karavan/operator/spec/KaravanOperatorCSVMetadata.java       | 4 ++--
 karavan-operator/src/main/resources/application.properties            | 4 ++--
 karavan-space/package.json                                            | 2 +-
 karavan-vscode/package.json                                           | 2 +-
 18 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml
index faf5e5e4..51b2f2bd 100644
--- a/.github/workflows/app.yml
+++ b/.github/workflows/app.yml
@@ -8,7 +8,7 @@ on:
     branches: [ main ]
 
 env:
-  TAG: 3.21.0
+  TAG: 3.21.1-SNAPSHOT
 
 jobs:
   build:
diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml
index 9845c5ee..886de714 100644
--- a/.github/workflows/builder.yml
+++ b/.github/workflows/builder.yml
@@ -8,7 +8,7 @@ on:
 env:
   REGISTRY: ghcr.io
   IMAGE_NAME: ${{ github.repository }}-builder
-  TAG: 3.21.0
+  TAG: 3.21.1-SNAPSHOT
 
 jobs:
   build:
diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 3169b68e..010563d7 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -8,7 +8,7 @@ on:
     branches: [ main ]
 
 env:
-  TAG: 3.21.0
+  TAG: 3.21.1-SNAPSHOT
 
 jobs:
   build:
diff --git a/.github/workflows/operator.yml b/.github/workflows/operator.yml
index c86effb2..17f6af8b 100644
--- a/.github/workflows/operator.yml
+++ b/.github/workflows/operator.yml
@@ -12,7 +12,7 @@ env:
   REGISTRY: ghcr.io
   IMAGE_NAME: ${{ github.repository }}-operator
   IMAGE_NAME_BUNDLE: ${{ github.repository }}-bundle
-  TAG: 3.21.0
+  TAG: 3.21.1-SNAPSHOT
 
 jobs:
   build:
diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml
index 8ec351dc..0172f360 100644
--- a/.github/workflows/runner.yml
+++ b/.github/workflows/runner.yml
@@ -8,7 +8,7 @@ on:
 env:
   REGISTRY: ghcr.io
   IMAGE_NAME: ${{ github.repository }}-runner
-  TAG: 3.21.0
+  TAG: 3.21.1-SNAPSHOT
 
 jobs:
   build:
diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml
index 1d471200..eb8d93c7 100644
--- a/karavan-app/pom.xml
+++ b/karavan-app/pom.xml
@@ -18,7 +18,7 @@
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.apache.camel.karavan</groupId>
-    <version>3.21.0</version>
+    <version>3.21.1-SNAPSHOT</version>
     <artifactId>karavan</artifactId>
     <properties>
         <compiler-plugin.version>3.10.1</compiler-plugin.version>
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index e64b9b54..741210d8 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-karavan.version=3.21.0
+karavan.version=3.21.1-SNAPSHOT
 karavan.environment=dev
 karavan.default-runtime=quarkus
 karavan.runtimes=quarkus,spring-boot
diff --git a/karavan-app/src/main/webui/package.json b/karavan-app/src/main/webui/package.json
index b3ac7eb1..bcdde439 100644
--- a/karavan-app/src/main/webui/package.json
+++ b/karavan-app/src/main/webui/package.json
@@ -1,6 +1,6 @@
 {
   "name": "karavan",
-  "version": "3.21.0",
+  "version": "3.21.1-SNAPSHOT",
   "private": true,
   "scripts": {
     "copy-designer": "cp -r ../../../../karavan-designer/src/designer src && cp -r ../../../../karavan-designer/src/kamelets src && cp -r ../../../../karavan-designer/src/components src && cp -r ../../../../karavan-designer/src/eip src",
diff --git a/karavan-cli/pom.xml b/karavan-cli/pom.xml
index 233314fb..8daf369b 100644
--- a/karavan-cli/pom.xml
+++ b/karavan-cli/pom.xml
@@ -6,7 +6,7 @@
     <groupId>org.apache.camel.karavan</groupId>
     <artifactId>karavan-cli</artifactId>
     <name>karavan-cli</name>
-    <version>3.21.0</version>
+    <version>3.21.1-SNAPSHOT</version>
     <packaging>jar</packaging>
     <properties>
         <compiler-plugin.version>3.10.1</compiler-plugin.version>
diff --git a/karavan-core/package.json b/karavan-core/package.json
index 6e375fed..a34ace32 100644
--- a/karavan-core/package.json
+++ b/karavan-core/package.json
@@ -1,7 +1,7 @@
 {
   "name": "karavan-core",
   "publisher": "camel-karavan",
-  "version": "3.21.0",
+  "version": "3.21.1-SNAPSHOT",
   "description": "Apache Camel Karavan Core",
   "scripts": {
     "build": "tsc",
diff --git a/karavan-designer/package.json b/karavan-designer/package.json
index 49b879b3..5e64efee 100644
--- a/karavan-designer/package.json
+++ b/karavan-designer/package.json
@@ -1,6 +1,6 @@
 {
   "name": "karavan-designer",
-  "version": "3.21.0",
+  "version": "3.21.1-SNAPSHOT",
   "license": "Apache-2.0",
   "scripts": {
     "start": "react-scripts start",
diff --git a/karavan-generator/pom.xml b/karavan-generator/pom.xml
index 3e844f52..730abf8b 100644
--- a/karavan-generator/pom.xml
+++ b/karavan-generator/pom.xml
@@ -19,7 +19,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.apache.camel.karavan</groupId>
     <artifactId>karavan-generator</artifactId>
-    <version>3.21.0</version>
+    <version>3.21.1-SNAPSHOT</version>
     <properties>
         <compiler-plugin.version>3.8.1</compiler-plugin.version>
         <maven.compiler.parameters>true</maven.compiler.parameters>
diff --git a/karavan-operator/Makefile b/karavan-operator/Makefile
index a336e163..d3574617 100644
--- a/karavan-operator/Makefile
+++ b/karavan-operator/Makefile
@@ -1,5 +1,5 @@
 
-VERSION ?=3.21.0
+VERSION ?=3.21.1-SNAPSHOT
 
 # CHANNELS define the bundle channels used in the bundle.
 # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
diff --git a/karavan-operator/pom.xml b/karavan-operator/pom.xml
index fc18d0a1..3af81df6 100644
--- a/karavan-operator/pom.xml
+++ b/karavan-operator/pom.xml
@@ -6,7 +6,7 @@
     <groupId>org.apache.camel.karavan</groupId>
     <artifactId>karavan-operator</artifactId>
     <name>karavan-operator</name>
-    <version>3.21.0</version>
+    <version>3.21.1-SNAPSHOT</version>
     <packaging>jar</packaging>
     <properties>
         <compiler-plugin.version>3.8.1</compiler-plugin.version>
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/operator/spec/KaravanOperatorCSVMetadata.java b/karavan-operator/src/main/java/org/apache/camel/karavan/operator/spec/KaravanOperatorCSVMetadata.java
index 752ea850..386f8bf6 100644
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/operator/spec/KaravanOperatorCSVMetadata.java
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/operator/spec/KaravanOperatorCSVMetadata.java
@@ -21,9 +21,9 @@ package org.apache.camel.karavan.operator.spec;
 
 
 @CSVMetadata(
-        name = "camel-karavan-operator.v3.21.0",
+        name = "camel-karavan-operator.v3.21.1-SNAPSHOT",
         annotations = @CSVMetadata.Annotations(
-                containerImage = "ghcr.io/apache/camel-karavan-operator:3.21.0",
+                containerImage = "ghcr.io/apache/camel-karavan-operator:3.21.1-SNAPSHOT",
                 repository = "https://github.com/apache/camel-karavan",
                 capabilities = "Basic Install",
                 categories = "Developer Tools, Integration & Delivery",
diff --git a/karavan-operator/src/main/resources/application.properties b/karavan-operator/src/main/resources/application.properties
index b1369655..67067a56 100644
--- a/karavan-operator/src/main/resources/application.properties
+++ b/karavan-operator/src/main/resources/application.properties
@@ -20,8 +20,8 @@ quarkus.kubernetes.resources.limits.cpu=1000m
 quarkus.container-image.builder=jib
 quarkus.container-image.group=ghcr.io/apache
 quarkus.container-image.name=camel-karavan-operator
-quarkus.container-image.tag=3.21.0
+quarkus.container-image.tag=3.21.1-SNAPSHOT
 
-karavan.version=3.21.0
+karavan.version=3.21.1-SNAPSHOT
 karavan.image=ghcr.io/apache/camel-karavan
 karavan.build-image=ghcr.io/apache/camel-karavan-builder
diff --git a/karavan-space/package.json b/karavan-space/package.json
index e447d481..d85f70bc 100644
--- a/karavan-space/package.json
+++ b/karavan-space/package.json
@@ -1,6 +1,6 @@
 {
   "name": "karavan-space",
-  "version": "3.21.0",
+  "version": "3.21.1-SNAPSHOT",
   "license": "Apache-2.0",
   "scripts": {
     "cp-designer": "cp -r ../karavan-designer/src/designer src",
diff --git a/karavan-vscode/package.json b/karavan-vscode/package.json
index 10d2ec4d..d48816ed 100644
--- a/karavan-vscode/package.json
+++ b/karavan-vscode/package.json
@@ -4,7 +4,7 @@
   "displayName": "Karavan",
   "icon": "icons/karavan.png",
   "description": "Integration Toolkit for Apache Camel",
-  "version": "3.21.0",
+  "version": "3.21.1-SNAPSHOT",
   "license": "Apache-2.0",
   "preview": true,
   "bugs": {


[camel-karavan] 03/05: Infinispan and Karavan up and running #817

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ac306f7fb85e344078f4d4e5c448a4f044305732
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 6 16:29:26 2023 -0400

    Infinispan and Karavan up and running #817
---
 karavan-bashi/pom.xml                              |  8 ++++
 .../apache/camel/karavan/bashi/HealthChecker.java  | 31 ++++++++++++++
 .../apache/camel/karavan/bashi/KaravanBashi.java   | 49 ++++++++++++++++++----
 .../camel/karavan/bashi/KaravanConstants.java      |  4 ++
 .../camel/karavan/bashi/KaravanContainers.java     | 33 +++++++++++++++
 .../karavan/bashi/docker/DockerEventListener.java  | 27 +++++++++++-
 .../camel/karavan/bashi/docker/DockerService.java  | 47 ++++++++++++++++-----
 .../src/main/resources/application.properties      |  6 +--
 8 files changed, 182 insertions(+), 23 deletions(-)

diff --git a/karavan-bashi/pom.xml b/karavan-bashi/pom.xml
index 3f4f0b6b..a5ae14d4 100644
--- a/karavan-bashi/pom.xml
+++ b/karavan-bashi/pom.xml
@@ -33,6 +33,14 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-arc</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-scheduler</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-vertx</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.github.docker-java</groupId>
             <artifactId>docker-java-core</artifactId>
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/HealthChecker.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/HealthChecker.java
new file mode 100644
index 00000000..681fbf19
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/HealthChecker.java
@@ -0,0 +1,31 @@
+package org.apache.camel.karavan.bashi;
+
+import com.github.dockerjava.api.model.Container;
+import io.quarkus.scheduler.Scheduled;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import java.util.concurrent.ConcurrentHashMap;
+
+@ApplicationScoped
+public class HealthChecker {
+
+    private static final Logger LOGGER = Logger.getLogger(HealthChecker.class.getName());
+
+    private static final ConcurrentHashMap<String, Container> containers = new ConcurrentHashMap<>();
+
+//    @Scheduled(every = "{karavan.health-checker-interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
+//    void collectHealthStatuses() {
+//        containers.forEach((s, s2) -> {
+//            LOGGER.infof("HealthCheck for %s", s);
+//        });
+//    }
+
+//    public void addContainer(Container container){
+//        containers.put(container.getId(), container);
+//    }
+//
+//    public void removeContainer(String id){
+//        containers.remove(id);
+//    }
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
index 1f21389b..3058c2a0 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
@@ -1,7 +1,9 @@
 package org.apache.camel.karavan.bashi;
 
+import com.github.dockerjava.api.model.HealthCheck;
 import io.quarkus.runtime.ShutdownEvent;
 import io.quarkus.runtime.StartupEvent;
+import io.quarkus.vertx.ConsumeEvent;
 import org.apache.camel.karavan.bashi.docker.DockerService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
@@ -11,11 +13,24 @@ import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import java.util.List;
 
-import static org.apache.camel.karavan.bashi.KaravanConstants.INFINISPAN_CONTAINER_NAME;
+import static org.apache.camel.karavan.bashi.KaravanConstants.*;
 
 @ApplicationScoped
 public class KaravanBashi {
 
+    @ConfigProperty(name = "karavan.image")
+    String karavanImage;
+    @ConfigProperty(name = "karavan.port")
+    String karavanPort;
+    @ConfigProperty(name = "karavan.git-repository")
+    String gitRepository;
+    @ConfigProperty(name = "karavan.git-username")
+    String gitUsername;
+    @ConfigProperty(name = "karavan.git-password")
+    String gitPassword;
+    @ConfigProperty(name = "karavan.git-branch")
+    String gitBranch;
+
     @ConfigProperty(name = "infinispan.image")
     String infinispanImage;
     @ConfigProperty(name = "infinispan.port")
@@ -32,6 +47,7 @@ public class KaravanBashi {
 
     void onStart(@Observes StartupEvent ev) throws InterruptedException {
         LOGGER.info("Karavan Bashi is starting...");
+        dockerService.checkContainersStatus();
         dockerService.createNetwork();
         dockerService.startListeners();
         startInfinispan();
@@ -39,20 +55,35 @@ public class KaravanBashi {
 
     void startInfinispan() throws InterruptedException {
         LOGGER.info("Infinispan is starting...");
+
+        HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status"))
+                .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
+
         dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
-                List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword), infinispanPort, false
+                List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword),
+                infinispanPort, true, healthCheck
         );
         dockerService.startContainer(INFINISPAN_CONTAINER_NAME);
         LOGGER.info("Infinispan is started");
     }
 
-    void startKaravan() throws InterruptedException {
-        LOGGER.info("Karavan is starting...");
-        dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
-                List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword), infinispanPort, false
-        );
-        dockerService.startContainer(INFINISPAN_CONTAINER_NAME);
-        LOGGER.info("Karavan is started");
+    @ConsumeEvent(value = ADDRESS_INFINISPAN_HEALTH, blocking = true, ordered = false)
+    void startKaravan(String infinispanHealth) throws InterruptedException {
+        if (infinispanHealth.equals("healthy")) {
+            LOGGER.info("Karavan is starting...");
+            dockerService.createContainer(KARAVAN_CONTAINER_NAME, karavanImage,
+                    List.of(
+                            "QUARKUS_INFINISPAN_CLIENT_HOSTS=infinispan:11222",
+                            "KARAVAN_GIT_REPOSITORY=" + gitRepository,
+                            "KARAVAN_GIT_USERNAME=" + gitUsername,
+                            "KARAVAN_GIT_PASSWORD=" + gitPassword,
+                            "KARAVAN_GIT_BRANCH=" + gitBranch
+                    ),
+                    karavanPort, true, new HealthCheck()
+            );
+            dockerService.startContainer(KARAVAN_CONTAINER_NAME);
+            LOGGER.info("Karavan is started");
+        }
     }
 
     void onStop(@Observes ShutdownEvent ev) {
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
index eab76977..8e53e011 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
@@ -4,4 +4,8 @@ public class KaravanConstants {
 
     public static final String NETWORK_NAME = "karavan";
     public static final String INFINISPAN_CONTAINER_NAME = "infinispan";
+
+    public static final String KARAVAN_CONTAINER_NAME = "karavan";
+
+    public static final String ADDRESS_INFINISPAN_HEALTH = "ADDRESS_INFINISPAN_HEALTH";
 }
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanContainers.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanContainers.java
new file mode 100644
index 00000000..7fe14977
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanContainers.java
@@ -0,0 +1,33 @@
+package org.apache.camel.karavan.bashi;
+
+import com.github.dockerjava.api.model.Container;
+import io.vertx.core.eventbus.EventBus;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.apache.camel.karavan.bashi.KaravanConstants.ADDRESS_INFINISPAN_HEALTH;
+
+@ApplicationScoped
+public class KaravanContainers {
+
+    private static final Logger LOGGER = Logger.getLogger(KaravanContainers.class.getName());
+
+    private static final ConcurrentHashMap<String, String> containers = new ConcurrentHashMap<>();
+
+    @Inject
+    EventBus eventBus;
+
+    public void addContainer(Container container, String health){
+        containers.put(container.getId(), health);
+        if (container.getNames()[0].equals("/infinispan")) {
+            eventBus.publish(ADDRESS_INFINISPAN_HEALTH, health);
+        }
+    }
+
+    public void removeContainer(String id){
+        containers.remove(id);
+    }
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
index d0551ba7..9ea88796 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
@@ -1,16 +1,29 @@
 package org.apache.camel.karavan.bashi.docker;
 
 import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.model.Container;
 import com.github.dockerjava.api.model.Event;
+import com.github.dockerjava.api.model.EventType;
+import org.apache.camel.karavan.bashi.HealthChecker;
+import org.apache.camel.karavan.bashi.KaravanContainers;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
 
 @ApplicationScoped
 public class DockerEventListener implements ResultCallback<Event> {
 
+    @Inject
+    KaravanContainers karavanContainers;
+
+    @Inject
+    DockerService dockerService;
+
     private static final Logger LOGGER = Logger.getLogger(DockerEventListener.class.getName());
 
     @Override
@@ -20,7 +33,19 @@ public class DockerEventListener implements ResultCallback<Event> {
 
     @Override
     public void onNext(Event event) {
-        LOGGER.info(event.getType() + " : " + event.getStatus());
+//        LOGGER.info(event.getType() + " : " + event.getStatus());
+        if (Objects.equals(event.getType(), EventType.CONTAINER)){
+            Container c = dockerService.getContainer(event.getId());
+            if (Arrays.asList("stop", "die", "kill", "pause", "destroy").contains(event.getStatus())) {
+                karavanContainers.removeContainer(c.getId());
+            } else if (Arrays.asList("start", "unpause").contains(event.getStatus())) {
+                karavanContainers.addContainer(c, "unknown");
+            } else if (event.getStatus().startsWith("health_status:")) {
+                String health = event.getStatus().replace("health_status: ", "");
+                LOGGER.info(event.getType() + " : " + event.getId() + " : " + health);
+                karavanContainers.addContainer(c, health);
+            }
+        }
     }
 
     @Override
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
index e65af77f..89d54d74 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
@@ -4,12 +4,15 @@ import com.github.dockerjava.api.DockerClient;
 import com.github.dockerjava.api.async.ResultCallback;
 import com.github.dockerjava.api.command.CreateContainerResponse;
 import com.github.dockerjava.api.command.CreateNetworkResponse;
+import com.github.dockerjava.api.command.HealthState;
 import com.github.dockerjava.api.model.*;
 import com.github.dockerjava.core.DefaultDockerClientConfig;
 import com.github.dockerjava.core.DockerClientConfig;
 import com.github.dockerjava.core.DockerClientImpl;
 import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
 import com.github.dockerjava.transport.DockerHttpClient;
+import org.apache.camel.karavan.bashi.HealthChecker;
+import org.apache.camel.karavan.bashi.KaravanContainers;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
@@ -18,6 +21,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import static org.apache.camel.karavan.bashi.KaravanConstants.NETWORK_NAME;
 
@@ -29,6 +33,9 @@ public class DockerService {
     @Inject
     DockerEventListener dockerEventListener;
 
+    @Inject
+    KaravanContainers karavanContainers;
+
     public void startListeners() {
         getDockerClient().eventsCmd().exec(dockerEventListener);
     }
@@ -36,28 +43,45 @@ public class DockerService {
     public void createNetwork() {
         if (!getDockerClient().listNetworksCmd().exec().stream().filter(n -> n.getName().equals(NETWORK_NAME))
                 .findFirst().isPresent()) {
-            CreateNetworkResponse res = getDockerClient().createNetworkCmd().withName(NETWORK_NAME).exec();
+            CreateNetworkResponse res = getDockerClient().createNetworkCmd().withName(NETWORK_NAME).withAttachable(true).exec();
             LOGGER.info("Network created: {}" + res);
         } else {
             LOGGER.info("Network already exists with name: " + NETWORK_NAME);
         }
     }
 
-    public void createContainer(String name, String image, List<String> env, String ports, boolean hostNet) throws InterruptedException {
+    public void checkContainersStatus() {
+        getDockerClient().listContainersCmd().withShowAll(true).exec().stream()
+                .filter(c -> c.getState().equals("running"))
+                .forEach(c -> {
+                    HealthState hs = getDockerClient().inspectContainerCmd(c.getId()).exec().getState().getHealth();
+                    karavanContainers.addContainer(c, hs != null ? hs.getStatus() : "unknown");
+                });
+    }
+
+    public Container getContainer(String id) {
+        List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withIdFilter(List.of(id)).exec();
+        return containers.get(0);
+    }
+
+    public void createContainer(String name, String image, List<String> env, String ports, boolean exposedPort, HealthCheck healthCheck) throws InterruptedException {
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
-        System.out.println(containers);
         if (containers.size() == 0) {
             pullImage(image);
 
+            List<ExposedPort> exposedPorts = getPortsFromString(ports).values().stream().map(i -> ExposedPort.tcp(i)).collect(Collectors.toList());
+
             CreateContainerResponse container = getDockerClient().createContainerCmd(image)
                     .withName(name)
                     .withEnv(env)
+                    .withExposedPorts(exposedPorts)
                     .withHostName(name)
-                    .withHostConfig(getHostConfig(ports, hostNet))
+                    .withHostConfig(getHostConfig(ports))
+                    .withHealthcheck(healthCheck)
                     .exec();
-            LOGGER.info("Container created: " + container.toString());
+            LOGGER.info("Container created: " + container.getId());
         } else {
-            LOGGER.info("Container already exists: " + containers.get(0).toString());
+            LOGGER.info("Container already exists: " + containers.get(0).getId());
         }
     }
 
@@ -87,17 +111,20 @@ public class DockerService {
     }
 
     public void pullImage(String image) throws InterruptedException {
-        ResultCallback.Adapter<PullResponseItem>  pull = getDockerClient().pullImageCmd(image).start().awaitCompletion();
+        List<Image> images = getDockerClient().listImagesCmd().withShowAll(true).exec();
+        if (!images.stream().filter(i -> Arrays.asList(i.getRepoTags()).contains(image)).findFirst().isPresent()) {
+            ResultCallback.Adapter<PullResponseItem>  pull = getDockerClient().pullImageCmd(image).start().awaitCompletion();
+        }
     }
 
-    private HostConfig getHostConfig(String ports, boolean hostNet) {
+    private HostConfig getHostConfig(String ports) {
         Ports portBindings = new Ports();
         getPortsFromString(ports).forEach((hostPort, containerPort) -> {
-            portBindings.bind(ExposedPort.tcp(containerPort), Ports.Binding.bindPort(hostPort));
+            portBindings.bind(ExposedPort.tcp(containerPort), Ports.Binding.bindIp("0.0.0.0").bindPort(hostPort));
         });
         return new HostConfig()
                 .withPortBindings(portBindings)
-                .withNetworkMode(hostNet ? "host" : NETWORK_NAME);
+                .withNetworkMode(NETWORK_NAME);
     }
 
     private Map<Integer,Integer> getPortsFromString(String ports){
diff --git a/karavan-bashi/src/main/resources/application.properties b/karavan-bashi/src/main/resources/application.properties
index d9eb55bb..8649065e 100644
--- a/karavan-bashi/src/main/resources/application.properties
+++ b/karavan-bashi/src/main/resources/application.properties
@@ -1,10 +1,10 @@
-infinispan.image=quay.io/infinispan/server:14.0
+infinispan.image=quay.io/infinispan/server:14.0.6.Final
 infinispan.port=11222:11222
 infinispan.username=admin
 infinispan.password=karavan
 
-karavan.image=ghcr.io/apache/camel-karavan
-
+karavan.image=marat/karavan:3.21.1-SNAPSHOT
+karavan.port=8080:8080
 karavan.environment=dev
 karavan.default-runtime=quarkus
 karavan.runtimes=quarkus,spring-boot


[camel-karavan] 05/05: Runner experiment #817

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b383fbc04ba7fb51ca53e624e1af438247c284ae
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Fri Jul 7 19:18:49 2023 -0400

    Runner experiment #817
---
 .../apache/camel/karavan/api/RunnerResource.java   |  13 +-
 .../karavan/listener/ClientRunnerListener.java     |  46 +++++
 .../karavan/listener/LocalRunnerListener.java      |  48 +++++
 .../camel/karavan/listener/RunnerListener.java     |  35 ++++
 .../org/apache/camel/karavan/model/GroupedKey.java |  12 ++
 .../apache/camel/karavan/model/RunnerCommand.java  |  13 ++
 .../camel/karavan/service/InfinispanService.java   | 105 ++++++-----
 .../camel/karavan/service/KubernetesService.java   |  64 ++++---
 .../camel/karavan/service/RunnerService.java       |  22 ++-
 .../src/main/resources/application.properties      |   2 -
 karavan-bashi/pom.xml                              |   4 +
 .../{KaravanBashi.java => ConductorService.java}   |  50 +++--
 .../{KaravanConstants.java => Constants.java}      |   5 +-
 .../apache/camel/karavan/bashi/HealthChecker.java  |  31 ---
 .../apache/camel/karavan/bashi/KaravanBashi.java   |  65 +------
 .../camel/karavan/bashi/KaravanContainers.java     |  33 ----
 .../camel/karavan/bashi/RunnerStatusService.java   |  83 ++++++++
 .../karavan/bashi/docker/DockerEventListener.java  |  47 +++--
 .../camel/karavan/bashi/docker/DockerService.java  |  74 ++++++--
 .../bashi/infinispan/ClientRunnerListener.java     |  49 +++++
 .../karavan/bashi/infinispan}/GroupedKey.java      |  14 +-
 .../bashi/infinispan/InfinispanService.java        | 122 ++++++++++++
 .../camel/karavan/bashi/infinispan/PodStatus.java  | 209 +++++++++++++++++++++
 .../bashi/infinispan/ProjectStoreSchema.java       |   8 +
 .../karavan/bashi/infinispan/RunnerCommand.java    |  13 ++
 .../src/main/resources/application.properties      |  10 +
 26 files changed, 902 insertions(+), 275 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java
index b728f7f4..712be7d1 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java
@@ -18,9 +18,9 @@ package org.apache.camel.karavan.api;
 
 import org.apache.camel.karavan.model.PodStatus;
 import org.apache.camel.karavan.model.Project;
+import org.apache.camel.karavan.model.RunnerCommand;
 import org.apache.camel.karavan.model.RunnerStatus;
 import org.apache.camel.karavan.service.InfinispanService;
-import org.apache.camel.karavan.service.KubernetesService;
 import org.apache.camel.karavan.service.RunnerService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 
@@ -48,9 +48,6 @@ public class RunnerResource {
     @Inject
     RunnerService runnerServices;
 
-    @Inject
-    KubernetesService kubernetesService;
-
     @Inject
     InfinispanService infinispanService;
 
@@ -62,9 +59,9 @@ public class RunnerResource {
         String runnerName = project.getProjectId() + "-" + RUNNER_SUFFIX;
         String status = infinispanService.getRunnerStatus(runnerName, RunnerStatus.NAME.context);
         if (status == null) {
-            Project p = infinispanService.getProject(project.getProjectId());
             infinispanService.saveRunnerStatus(runnerName, STATUS_NEED_INITIAL_LOAD, STATUS_NEED_INITIAL_LOAD);
-            return Response.ok(kubernetesService.tryCreateRunner(p, runnerName, jBangOptions)).build();
+            infinispanService.sendRunnerCommand(project.getProjectId(), RunnerCommand.NAME.run);
+            return Response.ok(runnerName).build();
         }
         return Response.notModified().build();
     }
@@ -89,9 +86,7 @@ public class RunnerResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/{projectId}/{deletePVC}")
     public Response deleteRunner(@PathParam("projectId") String projectId, @PathParam("deletePVC") boolean deletePVC) {
-        String runnerName = projectId + "-" + RUNNER_SUFFIX;
-        kubernetesService.deleteRunner(runnerName, deletePVC);
-        infinispanService.deleteRunnerStatuses(runnerName);
+        infinispanService.sendRunnerCommand(projectId, RunnerCommand.NAME.delete);
         return Response.accepted().build();
     }
 
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/listener/ClientRunnerListener.java b/karavan-app/src/main/java/org/apache/camel/karavan/listener/ClientRunnerListener.java
new file mode 100644
index 00000000..b1aabaa6
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/listener/ClientRunnerListener.java
@@ -0,0 +1,46 @@
+package org.apache.camel.karavan.listener;
+
+import org.apache.camel.karavan.model.GroupedKey;
+import org.apache.camel.karavan.model.RunnerCommand;
+import org.apache.camel.karavan.service.InfinispanService;
+import org.apache.camel.karavan.service.KubernetesService;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified;
+import org.infinispan.client.hotrod.annotation.ClientListener;
+import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
+import org.infinispan.client.hotrod.event.ClientCacheEntryModifiedEvent;
+
+import java.util.Objects;
+
+@ClientListener
+public class ClientRunnerListener extends RunnerListener {
+
+    public ClientRunnerListener(InfinispanService infinispanService, KubernetesService kubernetesService) {
+        super(infinispanService, kubernetesService);
+    }
+
+    @ClientCacheEntryCreated
+    public void entryCreated(ClientCacheEntryCreatedEvent<GroupedKey> event) {
+        System.out.println("entryCreated");
+        String command = event.getKey().getKey();
+        String projectId = event.getKey().getGroup();
+        if (Objects.equals(command, RunnerCommand.NAME.run.name())) {
+            startRunner(projectId);
+        } else if (Objects.equals(command, RunnerCommand.NAME.delete.name())) {
+            stopRunner(projectId);
+        }
+    }
+
+    @ClientCacheEntryModified
+    public void entryModified(ClientCacheEntryModifiedEvent<GroupedKey> event) {
+        System.out.println("entryModified");
+        String command = event.getKey().getKey();
+        String projectId = event.getKey().getGroup();
+        if (Objects.equals(command, RunnerCommand.NAME.run.name())) {
+            startRunner(projectId);
+        } else if (Objects.equals(command, RunnerCommand.NAME.delete.name())) {
+            stopRunner(projectId);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/listener/LocalRunnerListener.java b/karavan-app/src/main/java/org/apache/camel/karavan/listener/LocalRunnerListener.java
new file mode 100644
index 00000000..d7db9d7e
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/listener/LocalRunnerListener.java
@@ -0,0 +1,48 @@
+package org.apache.camel.karavan.listener;
+
+import org.apache.camel.karavan.model.GroupedKey;
+import org.apache.camel.karavan.model.RunnerCommand;
+import org.apache.camel.karavan.service.InfinispanService;
+import org.apache.camel.karavan.service.KubernetesService;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent;
+
+import java.util.Objects;
+
+
+@Listener(primaryOnly = true)
+public class LocalRunnerListener extends RunnerListener {
+
+    public LocalRunnerListener(InfinispanService infinispanService, KubernetesService kubernetesService) {
+        super(infinispanService, kubernetesService);
+    }
+
+    @CacheEntryCreated
+    public void entryCreated(CacheEntryCreatedEvent<GroupedKey, String> event) {
+        if (!event.isPre()) {
+            String command = event.getKey().getKey();
+            String projectId = event.getKey().getGroup();
+            if (Objects.equals(command, RunnerCommand.NAME.run.name())) {
+                startRunner(projectId);
+            } else if (Objects.equals(command, RunnerCommand.NAME.delete.name())) {
+                stopRunner(projectId);
+            }
+        }
+    }
+
+    @CacheEntryModified
+    public void entryModified(CacheEntryModifiedEvent<GroupedKey, String> event) {
+        if (!event.isPre()) {
+            String command = event.getKey().getKey();
+            String projectId = event.getKey().getGroup();
+            if (Objects.equals(command, RunnerCommand.NAME.run.name())) {
+                startRunner(projectId);
+            } else if (Objects.equals(command, RunnerCommand.NAME.delete.name())) {
+                stopRunner(projectId);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/listener/RunnerListener.java b/karavan-app/src/main/java/org/apache/camel/karavan/listener/RunnerListener.java
new file mode 100644
index 00000000..15699939
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/listener/RunnerListener.java
@@ -0,0 +1,35 @@
+package org.apache.camel.karavan.listener;
+
+import org.apache.camel.karavan.model.Project;
+import org.apache.camel.karavan.service.InfinispanService;
+import org.apache.camel.karavan.service.KubernetesService;
+
+import static org.apache.camel.karavan.service.RunnerService.RUNNER_SUFFIX;
+
+public class RunnerListener {
+
+    protected final InfinispanService infinispanService;
+
+    protected final KubernetesService kubernetesService;
+
+    public RunnerListener(InfinispanService infinispanService, KubernetesService kubernetesService) {
+        this.infinispanService = infinispanService;
+        this.kubernetesService = kubernetesService;
+    }
+
+    protected void startRunner(String projectId) {
+        String runnerName = projectId + "-" + RUNNER_SUFFIX;
+        if (kubernetesService.inKubernetes()) {
+            Project p = infinispanService.getProject(projectId);
+            kubernetesService.tryCreateRunner(p, runnerName, "");
+        }
+    }
+
+    protected void stopRunner(String projectId) {
+        String runnerName = projectId + "-" + RUNNER_SUFFIX;
+        if (kubernetesService.inKubernetes()) {
+            kubernetesService.deleteRunner(runnerName, false);
+            infinispanService.deleteRunnerStatuses(runnerName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java
index a50e5ced..3ab02eaf 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java
@@ -22,6 +22,18 @@ public class GroupedKey {
     }
 
 
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
     public String getGroup() {
         return group;
     }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerCommand.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerCommand.java
new file mode 100644
index 00000000..ccd2ce62
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerCommand.java
@@ -0,0 +1,13 @@
+package org.apache.camel.karavan.model;
+
+public class RunnerCommand {
+
+    public enum NAME {
+        run,
+        delete,
+        reload
+    }
+
+    public static final String CACHE = "runner_commands";
+
+}
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
index 75f47e17..08738cdb 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
@@ -17,16 +17,9 @@
 package org.apache.camel.karavan.service;
 
 import io.smallrye.mutiny.tuples.Tuple2;
-import org.apache.camel.karavan.model.CamelStatus;
-import org.apache.camel.karavan.model.DeploymentStatus;
-import org.apache.camel.karavan.model.Environment;
-import org.apache.camel.karavan.model.GroupedKey;
-import org.apache.camel.karavan.model.PipelineStatus;
-import org.apache.camel.karavan.model.PodStatus;
-import org.apache.camel.karavan.model.Project;
-import org.apache.camel.karavan.model.ProjectFile;
-import org.apache.camel.karavan.model.RunnerStatus;
-import org.apache.camel.karavan.model.ServiceStatus;
+import org.apache.camel.karavan.listener.ClientRunnerListener;
+import org.apache.camel.karavan.listener.LocalRunnerListener;
+import org.apache.camel.karavan.model.*;
 import org.eclipse.microprofile.health.HealthCheck;
 import org.eclipse.microprofile.health.HealthCheckResponse;
 import org.eclipse.microprofile.health.Readiness;
@@ -38,7 +31,6 @@ import org.infinispan.commons.api.CacheContainerAdmin;
 import org.infinispan.commons.configuration.StringConfiguration;
 import org.infinispan.configuration.cache.CacheMode;
 import org.infinispan.configuration.cache.ConfigurationBuilder;
-import org.infinispan.configuration.cache.SingleFileStoreConfigurationBuilder;
 import org.infinispan.configuration.global.GlobalConfigurationBuilder;
 import org.infinispan.manager.DefaultCacheManager;
 import org.infinispan.query.dsl.QueryFactory;
@@ -48,11 +40,7 @@ import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Default;
 import javax.inject.Inject;
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
@@ -74,10 +62,13 @@ public class InfinispanService implements HealthCheck  {
     private BasicCache<String, Environment> environments;
     private BasicCache<String, String> commits;
     private BasicCache<GroupedKey, String> runnerStatuses;
+    private BasicCache<GroupedKey, String> runnerCommands;
     private final AtomicBoolean ready = new AtomicBoolean(false);
 
     @Inject
-    RemoteCacheManager cacheManager;
+    RemoteCacheManager remoteCacheManager;
+
+    DefaultCacheManager localCacheManager;
 
     @Inject
     CodeService codeService;
@@ -87,44 +78,54 @@ public class InfinispanService implements HealthCheck  {
             + " <groups enabled=\"true\"/>"
             + "</distributed-cache>";
 
-    private static final Logger LOGGER = Logger.getLogger(KaravanService.class.getName());
+    private static final Logger LOGGER = Logger.getLogger(InfinispanService.class.getName());
 
     void start() {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             LOGGER.info("InfinispanService is starting in local mode");
             GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
-            DefaultCacheManager cacheManager = new DefaultCacheManager(global.build());
+            localCacheManager = new DefaultCacheManager(global.build());
             ConfigurationBuilder builder = new ConfigurationBuilder();
             builder.clustering().cacheMode(CacheMode.LOCAL);
-            environments = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Environment.CACHE, builder.build());
-            projects = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Project.CACHE, builder.build());
-            files = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ProjectFile.CACHE, builder.build());
-            pipelineStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PipelineStatus.CACHE, builder.build());
-            deploymentStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(DeploymentStatus.CACHE, builder.build());
-            podStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PodStatus.CACHE, builder.build());
-            serviceStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ServiceStatus.CACHE, builder.build());
-            camelStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(CamelStatus.CACHE, builder.build());
-            commits = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache("commits", builder.build());
-            runnerStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache("runner_statuses", builder.build());
+            environments = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Environment.CACHE, builder.build());
+            projects = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Project.CACHE, builder.build());
+            files = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ProjectFile.CACHE, builder.build());
+            pipelineStatuses = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PipelineStatus.CACHE, builder.build());
+            deploymentStatuses = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(DeploymentStatus.CACHE, builder.build());
+            podStatuses = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PodStatus.CACHE, builder.build());
+            serviceStatuses = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ServiceStatus.CACHE, builder.build());
+            camelStatuses = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(CamelStatus.CACHE, builder.build());
+            commits = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache("commits", builder.build());
+            runnerStatuses = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache("runner_statuses", builder.build());
+            runnerCommands = localCacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(RunnerCommand.CACHE, builder.build());
             cleanData();
         } else {
             LOGGER.info("InfinispanService is starting in remote mode");
-            environments = cacheManager.administration().getOrCreateCache(Environment.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, Environment.CACHE)));
-            projects = cacheManager.administration().getOrCreateCache(Project.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, Project.CACHE)));
-            files = cacheManager.administration().getOrCreateCache(ProjectFile.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, ProjectFile.CACHE)));
-            pipelineStatuses = cacheManager.administration().getOrCreateCache(PipelineStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, PipelineStatus.CACHE)));
-            deploymentStatuses = cacheManager.administration().getOrCreateCache(DeploymentStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, DeploymentStatus.CACHE)));
-            podStatuses = cacheManager.administration().getOrCreateCache(PodStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, PodStatus.CACHE)));
-            serviceStatuses = cacheManager.administration().getOrCreateCache(ServiceStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, ServiceStatus.CACHE)));
-            camelStatuses = cacheManager.administration().getOrCreateCache(CamelStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, CamelStatus.CACHE)));
-            commits = cacheManager.administration().getOrCreateCache("commits", new StringConfiguration(String.format(CACHE_CONFIG, "commits")));
-            runnerStatuses = cacheManager.administration().getOrCreateCache("runner_statuses", new StringConfiguration(String.format(CACHE_CONFIG, "runner_statuses")));
+            environments = remoteCacheManager.administration().getOrCreateCache(Environment.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, Environment.CACHE)));
+            projects = remoteCacheManager.administration().getOrCreateCache(Project.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, Project.CACHE)));
+            files = remoteCacheManager.administration().getOrCreateCache(ProjectFile.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, ProjectFile.CACHE)));
+            pipelineStatuses = remoteCacheManager.administration().getOrCreateCache(PipelineStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, PipelineStatus.CACHE)));
+            deploymentStatuses = remoteCacheManager.administration().getOrCreateCache(DeploymentStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, DeploymentStatus.CACHE)));
+            podStatuses = remoteCacheManager.administration().getOrCreateCache(PodStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, PodStatus.CACHE)));
+            serviceStatuses = remoteCacheManager.administration().getOrCreateCache(ServiceStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, ServiceStatus.CACHE)));
+            camelStatuses = remoteCacheManager.administration().getOrCreateCache(CamelStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, CamelStatus.CACHE)));
+            commits = remoteCacheManager.administration().getOrCreateCache("commits", new StringConfiguration(String.format(CACHE_CONFIG, "commits")));
+            runnerStatuses = remoteCacheManager.administration().getOrCreateCache("runner_statuses", new StringConfiguration(String.format(CACHE_CONFIG, "runner_statuses")));
+            runnerCommands = remoteCacheManager.administration().getOrCreateCache(RunnerCommand.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, RunnerCommand.CACHE)));
         }
+        addListeners();
         ready.set(true);
     }
 
-    public RemoteCacheManager getRemoteCacheManager() {
-        return cacheManager;
+    @Inject
+    KubernetesService kubernetesService;
+
+    private void addListeners() {
+        if (remoteCacheManager != null) {
+            remoteCacheManager.getCache(RunnerCommand.CACHE).addClientListener(new ClientRunnerListener(this, kubernetesService));
+        } else {
+            localCacheManager.getCache(RunnerCommand.CACHE).addListener(new LocalRunnerListener(this, kubernetesService));
+        }
     }
 
     private void cleanData() {
@@ -152,7 +153,7 @@ public class InfinispanService implements HealthCheck  {
     }
 
     public List<ProjectFile> getProjectFiles(String projectId) {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             return files.values().stream()
                     .filter(f -> f.getProjectId().equals(projectId))
                     .collect(Collectors.toList());
@@ -165,7 +166,7 @@ public class InfinispanService implements HealthCheck  {
     }
 
     public ProjectFile getProjectFile(String projectId, String filename) {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             return files.values().stream()
                     .filter(f -> f.getProjectId().equals(projectId) && f.getName().equals(filename))
                     .findFirst().orElse(new ProjectFile());
@@ -232,7 +233,7 @@ public class InfinispanService implements HealthCheck  {
     }
 
     public List<DeploymentStatus> getDeploymentStatuses(String env) {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             return  deploymentStatuses.values().stream()
                     .filter(s -> s.getEnv().equals(env))
                     .collect(Collectors.toList());
@@ -257,7 +258,7 @@ public class InfinispanService implements HealthCheck  {
     }
 
     public List<PodStatus> getPodStatuses(String projectId, String env) {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             return podStatuses.values().stream()
                     .filter(s -> s.getEnv().equals(env) && s.getProject().equals(projectId))
                     .collect(Collectors.toList());
@@ -271,7 +272,7 @@ public class InfinispanService implements HealthCheck  {
     }
 
     public List<PodStatus> getPodStatuses(String env) {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             return podStatuses.values().stream()
                     .filter(s -> s.getEnv().equals(env))
                     .collect(Collectors.toList());
@@ -296,7 +297,7 @@ public class InfinispanService implements HealthCheck  {
     }
 
     public List<CamelStatus> getCamelStatuses(String env) {
-        if (cacheManager == null) {
+        if (remoteCacheManager == null) {
             return camelStatuses.values().stream()
                     .filter(s -> s.getEnv().equals(env))
                     .collect(Collectors.toList());
@@ -368,13 +369,17 @@ public class InfinispanService implements HealthCheck  {
         return commits.get(commitId) != null;
     }
 
+    public void sendRunnerCommand(String projectId, RunnerCommand.NAME command) {
+        runnerCommands.put(GroupedKey.create(projectId, command.name()), UUID.randomUUID().toString());
+    }
+
     @Override
     public HealthCheckResponse call() {
-        if (cacheManager == null && ready.get()){
+        if (remoteCacheManager == null && ready.get()){
             return HealthCheckResponse.up("Infinispan Service is running in local mode.");
         }
         else {
-            if (cacheManager != null && cacheManager.isStarted() && ready.get()) {
+            if (remoteCacheManager != null && remoteCacheManager.isStarted() && ready.get()) {
                 return HealthCheckResponse.up("Infinispan Service is running in cluster mode.");
             }
             else {
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
index cc538db7..69203f3b 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
@@ -51,7 +51,7 @@ import static org.apache.camel.karavan.service.ServiceUtil.APPLICATION_PROPERTIE
 @Default
 @Readiness
 @ApplicationScoped
-public class KubernetesService implements HealthCheck{
+public class KubernetesService implements HealthCheck {
 
     private static final Logger LOGGER = Logger.getLogger(KubernetesService.class.getName());
     public static final String START_INFORMERS = "start-informers";
@@ -103,22 +103,22 @@ public class KubernetesService implements HealthCheck{
 
             SharedIndexInformer<Deployment> deploymentInformer = kubernetesClient().apps().deployments().inNamespace(getNamespace())
                     .withLabels(getRuntimeLabels()).inform();
-            deploymentInformer.addEventHandlerWithResyncPeriod(new DeploymentEventHandler(infinispanService, this),30 * 1000L);
+            deploymentInformer.addEventHandlerWithResyncPeriod(new DeploymentEventHandler(infinispanService, this), 30 * 1000L);
             informers.add(deploymentInformer);
 
             SharedIndexInformer<Service> serviceInformer = kubernetesClient().services().inNamespace(getNamespace())
                     .withLabels(getRuntimeLabels()).inform();
-            serviceInformer.addEventHandlerWithResyncPeriod(new ServiceEventHandler(infinispanService, this),30 * 1000L);
+            serviceInformer.addEventHandlerWithResyncPeriod(new ServiceEventHandler(infinispanService, this), 30 * 1000L);
             informers.add(serviceInformer);
 
             SharedIndexInformer<PipelineRun> pipelineRunInformer = tektonClient().v1beta1().pipelineRuns().inNamespace(getNamespace())
                     .withLabels(getRuntimeLabels()).inform();
-            pipelineRunInformer.addEventHandlerWithResyncPeriod(new PipelineRunEventHandler(infinispanService, this),30 * 1000L);
+            pipelineRunInformer.addEventHandlerWithResyncPeriod(new PipelineRunEventHandler(infinispanService, this), 30 * 1000L);
             informers.add(pipelineRunInformer);
 
             SharedIndexInformer<Pod> podRunInformer = kubernetesClient().pods().inNamespace(getNamespace())
                     .withLabels(getRuntimeLabels()).inform();
-            podRunInformer.addEventHandlerWithResyncPeriod(new PodEventHandler(infinispanService, this),30 * 1000L);
+            podRunInformer.addEventHandlerWithResyncPeriod(new PodEventHandler(infinispanService, this), 30 * 1000L);
             informers.add(podRunInformer);
 
             LOGGER.info("Started Kubernetes Informers");
@@ -127,14 +127,17 @@ public class KubernetesService implements HealthCheck{
         }
     }
 
-    
+
     @Override
     public HealthCheckResponse call() {
-        if(informers.size() == INFORMERS) {
-            return HealthCheckResponse.up("All Kubernetes informers are running.");
-        }
-        else {
-            return HealthCheckResponse.down("kubernetes Informers are not running.");
+        if (inKubernetes()) {
+            if (informers.size() == INFORMERS) {
+                return HealthCheckResponse.up("All Kubernetes informers are running.");
+            } else {
+                return HealthCheckResponse.down("kubernetes Informers are not running.");
+            }
+        } else {
+            return HealthCheckResponse.up("Running Kubernetesless.");
         }
     }
 
@@ -155,7 +158,7 @@ public class KubernetesService implements HealthCheck{
 
         Map<String, String> labels = getRuntimeLabels(
                 Map.of("karavan-project-id", project.getProjectId(),
-                "tekton.dev/pipeline", pipeline)
+                        "tekton.dev/pipeline", pipeline)
         );
 
         ObjectMeta meta = new ObjectMetaBuilder()
@@ -250,7 +253,7 @@ public class KubernetesService implements HealthCheck{
         }
     }
 
-    private List<Condition> getCancelConditions(String reason){
+    private List<Condition> getCancelConditions(String reason) {
         List<Condition> cancelConditions = new ArrayList<>();
         Condition taskRunCancelCondition = new Condition();
         taskRunCancelCondition.setType("Succeeded");
@@ -260,7 +263,7 @@ public class KubernetesService implements HealthCheck{
         cancelConditions.add(taskRunCancelCondition);
         return cancelConditions;
     }
-    
+
     public void rolloutDeployment(String name, String namespace) {
         try {
             kubernetesClient().apps().deployments().inNamespace(namespace).withName(name).rolling().restart();
@@ -328,12 +331,12 @@ public class KubernetesService implements HealthCheck{
     public List<String> getConfigMaps(String namespace) {
         List<String> result = new ArrayList<>();
         try {
-        kubernetesClient().configMaps().inNamespace(namespace).list().getItems().forEach(configMap -> {
-            String name = configMap.getMetadata().getName();
-            if (configMap.getData() != null) {
-                configMap.getData().keySet().forEach(data -> result.add(name + "/" + data));
-            }
-        });
+            kubernetesClient().configMaps().inNamespace(namespace).list().getItems().forEach(configMap -> {
+                String name = configMap.getMetadata().getName();
+                if (configMap.getData() != null) {
+                    configMap.getData().keySet().forEach(data -> result.add(name + "/" + data));
+                }
+            });
         } catch (Exception e) {
             LOGGER.error(e);
         }
@@ -358,11 +361,11 @@ public class KubernetesService implements HealthCheck{
     public List<String> getServices(String namespace) {
         List<String> result = new ArrayList<>();
         try {
-        kubernetesClient().services().inNamespace(namespace).list().getItems().forEach(service -> {
-            String name = service.getMetadata().getName();
-            String host = name + "." + namespace + ".svc.cluster.local";
-            service.getSpec().getPorts().forEach(port -> result.add(name + "|" + host + ":" + port.getPort()));
-        });
+            kubernetesClient().services().inNamespace(namespace).list().getItems().forEach(service -> {
+                String name = service.getMetadata().getName();
+                String host = name + "." + namespace + ".svc.cluster.local";
+                service.getSpec().getPorts().forEach(port -> result.add(name + "|" + host + ":" + port.getPort()));
+            });
         } catch (Exception e) {
             LOGGER.error(e);
         }
@@ -391,7 +394,7 @@ public class KubernetesService implements HealthCheck{
         Pod old = kubernetesClient().pods().inNamespace(getNamespace()).withName(runnerName).get();
         if (old == null) {
             ProjectFile properties = infinispanService.getProjectFile(project.getProjectId(), APPLICATION_PROPERTIES_FILENAME);
-            Map<String,String> containerResources = ServiceUtil
+            Map<String, String> containerResources = ServiceUtil
                     .getRunnerContainerResourcesMap(properties, isOpenshift(), project.getRuntime().equals("quarkus"));
             Pod pod = getRunnerPod(project.getProjectId(), runnerName, jBangOptions, containerResources);
             Pod result = kubernetesClient().resource(pod).createOrReplace();
@@ -414,7 +417,7 @@ public class KubernetesService implements HealthCheck{
         }
     }
 
-    public ResourceRequirements getResourceRequirements(Map<String,String> containerResources) {
+    public ResourceRequirements getResourceRequirements(Map<String, String> containerResources) {
         return new ResourceRequirementsBuilder()
                 .addToRequests("cpu", new Quantity(containerResources.get("requests.cpu")))
                 .addToRequests("memory", new Quantity(containerResources.get("requests.memory")))
@@ -423,8 +426,8 @@ public class KubernetesService implements HealthCheck{
                 .build();
     }
 
-    private Pod getRunnerPod(String projectId, String name, String jbangOptions, Map<String,String> containerResources) {
-        Map<String,String> labels = new HashMap<>();
+    private Pod getRunnerPod(String projectId, String name, String jbangOptions, Map<String, String> containerResources) {
+        Map<String, String> labels = new HashMap<>();
         labels.putAll(getRuntimeLabels());
         labels.putAll(getKaravanRunnerLabels(name));
         labels.put("karavan/projectId", projectId);
@@ -534,7 +537,7 @@ public class KubernetesService implements HealthCheck{
     }
 
     public static Map<String, String> getKaravanRunnerLabels(String name) {
-        return Map.of("karavan/type" , "runner",
+        return Map.of("karavan/type", "runner",
                 "app.kubernetes.io/name", name);
     }
 
@@ -545,6 +548,7 @@ public class KubernetesService implements HealthCheck{
     public String getCluster() {
         return kubernetesClient().getMasterUrl().getHost();
     }
+
     public String getNamespace() {
         return currentNamespace;
     }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java
index 0c5861f9..a0eefe52 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.karavan.service;
 
+import io.fabric8.kubernetes.api.model.Pod;
 import io.quarkus.scheduler.Scheduled;
 import io.quarkus.vertx.ConsumeEvent;
 import io.vertx.core.json.JsonObject;
@@ -25,6 +26,8 @@ import io.vertx.mutiny.core.eventbus.EventBus;
 import io.vertx.mutiny.ext.web.client.HttpResponse;
 import io.vertx.mutiny.ext.web.client.WebClient;
 import org.apache.camel.karavan.model.PodStatus;
+import org.apache.camel.karavan.model.Project;
+import org.apache.camel.karavan.model.ProjectFile;
 import org.apache.camel.karavan.model.RunnerStatus;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
@@ -33,9 +36,12 @@ import org.jboss.logging.Logger;
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
+import static org.apache.camel.karavan.service.ServiceUtil.APPLICATION_PROPERTIES_FILENAME;
+
 @ApplicationScoped
 public class RunnerService {
 
@@ -83,7 +89,7 @@ public class RunnerService {
     @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 1000)
     public boolean putRequest(String runnerName, String fileName, String body, int timeout) {
         try {
-            String url = "http://" + runnerName + "." + kubernetesService.getNamespace() + ".svc.cluster.local/q/upload/" + fileName;
+            String url = getRunnerAddress(runnerName) + "/q/upload/" + fileName;
             HttpResponse<Buffer> result = getWebClient().putAbs(url)
                     .timeout(timeout).sendBuffer(Buffer.buffer(body)).subscribeAsCompletionStage().toCompletableFuture().get();
             return result.statusCode() == 200;
@@ -94,7 +100,7 @@ public class RunnerService {
     }
 
     public String reloadRequest(String runnerName) {
-        String url = "http://" + runnerName + "." + kubernetesService.getNamespace() + ".svc.cluster.local/q/dev/reload?reload=true";
+        String url = getRunnerAddress(runnerName) + "/q/dev/reload?reload=true";
         try {
             return result(url, 1000);
         } catch (InterruptedException | ExecutionException e) {
@@ -103,8 +109,17 @@ public class RunnerService {
         return null;
     }
 
+    public String getRunnerAddress(String runnerName) {
+        if (kubernetesService.inKubernetes()) {
+            return "http://" + runnerName + "." + kubernetesService.getNamespace() + ".svc.cluster.local";
+        } else {
+            return "http://" + runnerName + ":8080";
+        }
+    }
+
     @Scheduled(every = "{karavan.runner-status-interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
     void collectRunnerStatus() {
+        System.out.println("collectRunnerStatus");
         if (infinispanService.call().getStatus().name().equals("UP")) {
             infinispanService.getPodStatuses(environment).stream().filter(PodStatus::getRunner).forEach(podStatus -> {
                 eventBus.publish(CMD_COLLECT_RUNNER_STATUS, podStatus.getName());
@@ -151,8 +166,7 @@ public class RunnerService {
     }
 
     public String getRunnerStatus(String podName, RunnerStatus.NAME statusName) {
-        String url = "http://" + podName + "." + kubernetesService.getNamespace() + ".svc.cluster.local/q/dev/" + statusName.name();
-//        String url = "http://0.0.0.0:8888/q/dev/" + statusName.name();
+        String url = getRunnerAddress(podName) + "/q/dev/" + statusName.name();
         try {
             return result(url, 1000);
         } catch (InterruptedException | ExecutionException e) {
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index 741210d8..414ffac6 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -19,8 +19,6 @@ quarkus.infinispan-client.username=admin
 quarkus.infinispan-client.password=karavan
 
 quarkus.infinispan-client.devservices.enabled=false
-quarkus.infinispan-client.devservices.port=12345
-quarkus.infinispan-client.devservices.service-name=karavan
 quarkus.infinispan-client.health.enabled=false
 
 # Infinispan client intelligence
diff --git a/karavan-bashi/pom.xml b/karavan-bashi/pom.xml
index a5ae14d4..f13a3ce5 100644
--- a/karavan-bashi/pom.xml
+++ b/karavan-bashi/pom.xml
@@ -41,6 +41,10 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-vertx</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-infinispan-client</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.github.docker-java</groupId>
             <artifactId>docker-java-core</artifactId>
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/ConductorService.java
similarity index 61%
copy from karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
copy to karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/ConductorService.java
index 3058c2a0..12ebdb8b 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/ConductorService.java
@@ -1,22 +1,21 @@
 package org.apache.camel.karavan.bashi;
 
 import com.github.dockerjava.api.model.HealthCheck;
-import io.quarkus.runtime.ShutdownEvent;
-import io.quarkus.runtime.StartupEvent;
 import io.quarkus.vertx.ConsumeEvent;
+import io.vertx.core.json.JsonObject;
 import org.apache.camel.karavan.bashi.docker.DockerService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import java.util.List;
+import java.util.Map;
 
-import static org.apache.camel.karavan.bashi.KaravanConstants.*;
+import static org.apache.camel.karavan.bashi.Constants.*;
 
 @ApplicationScoped
-public class KaravanBashi {
+public class ConductorService {
 
     @ConfigProperty(name = "karavan.image")
     String karavanImage;
@@ -30,6 +29,8 @@ public class KaravanBashi {
     String gitPassword;
     @ConfigProperty(name = "karavan.git-branch")
     String gitBranch;
+    @ConfigProperty(name = "karavan.runner-image")
+    String runnerImage;
 
     @ConfigProperty(name = "infinispan.image")
     String infinispanImage;
@@ -43,17 +44,14 @@ public class KaravanBashi {
     @Inject
     DockerService dockerService;
 
-    private static final Logger LOGGER = Logger.getLogger(KaravanBashi.class.getName());
+    private static final Logger LOGGER = Logger.getLogger(ConductorService.class.getName());
 
-    void onStart(@Observes StartupEvent ev) throws InterruptedException {
-        LOGGER.info("Karavan Bashi is starting...");
-        dockerService.checkContainersStatus();
-        dockerService.createNetwork();
-        dockerService.startListeners();
-        startInfinispan();
-    }
+    public static final String ADDRESS_INFINISPAN_START = "ADDRESS_INFINISPAN_START";
+    public static final String ADDRESS_INFINISPAN_HEALTH = "ADDRESS_INFINISPAN_HEALTH";
+    public static final String ADDRESS_RUNNER = "ADDRESS_RUNNER";
 
-    void startInfinispan() throws InterruptedException {
+    @ConsumeEvent(value = ADDRESS_INFINISPAN_START, blocking = true, ordered = true)
+    void startInfinispan(String data) throws InterruptedException {
         LOGGER.info("Infinispan is starting...");
 
         HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status"))
@@ -61,13 +59,13 @@ public class KaravanBashi {
 
         dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
                 List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword),
-                infinispanPort, true, healthCheck
+                infinispanPort, true, healthCheck, Map.of()
         );
         dockerService.startContainer(INFINISPAN_CONTAINER_NAME);
         LOGGER.info("Infinispan is started");
     }
 
-    @ConsumeEvent(value = ADDRESS_INFINISPAN_HEALTH, blocking = true, ordered = false)
+    @ConsumeEvent(value = ADDRESS_INFINISPAN_HEALTH, blocking = true, ordered = true)
     void startKaravan(String infinispanHealth) throws InterruptedException {
         if (infinispanHealth.equals("healthy")) {
             LOGGER.info("Karavan is starting...");
@@ -79,14 +77,28 @@ public class KaravanBashi {
                             "KARAVAN_GIT_PASSWORD=" + gitPassword,
                             "KARAVAN_GIT_BRANCH=" + gitBranch
                     ),
-                    karavanPort, true, new HealthCheck()
+                    karavanPort, true, new HealthCheck(), Map.of()
             );
             dockerService.startContainer(KARAVAN_CONTAINER_NAME);
             LOGGER.info("Karavan is started");
         }
     }
 
-    void onStop(@Observes ShutdownEvent ev) {
-        LOGGER.info("Karavan Bashi is stopping...");
+    @ConsumeEvent(value = ADDRESS_RUNNER, blocking = true, ordered = true)
+    void manageRunner(JsonObject params) throws InterruptedException {
+        String projectId = params.getString("projectId");
+        String command = params.getString("command");
+        String runnerName = projectId + "-" + RUNNER_SUFFIX;
+        if (command.equals("run")) {
+            LOGGER.infof("Runner starting for %s", projectId);
+            dockerService.createContainer(runnerName, runnerImage,
+                    List.of(), "", false, new HealthCheck(), Map.of("type", "runner")
+            );
+            dockerService.startContainer(runnerName);
+            LOGGER.infof("Runner started for %s", projectId);
+        } else {
+            dockerService.stopContainer(runnerName);
+            dockerService.deleteContainer(runnerName);
+        }
     }
 }
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/Constants.java
similarity index 66%
rename from karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
rename to karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/Constants.java
index 8e53e011..e9115f67 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/Constants.java
@@ -1,11 +1,10 @@
 package org.apache.camel.karavan.bashi;
 
-public class KaravanConstants {
+public class Constants {
 
     public static final String NETWORK_NAME = "karavan";
     public static final String INFINISPAN_CONTAINER_NAME = "infinispan";
 
     public static final String KARAVAN_CONTAINER_NAME = "karavan";
-
-    public static final String ADDRESS_INFINISPAN_HEALTH = "ADDRESS_INFINISPAN_HEALTH";
+    public static final String RUNNER_SUFFIX = "runner";
 }
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/HealthChecker.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/HealthChecker.java
deleted file mode 100644
index 681fbf19..00000000
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/HealthChecker.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.apache.camel.karavan.bashi;
-
-import com.github.dockerjava.api.model.Container;
-import io.quarkus.scheduler.Scheduled;
-import org.jboss.logging.Logger;
-
-import javax.enterprise.context.ApplicationScoped;
-import java.util.concurrent.ConcurrentHashMap;
-
-@ApplicationScoped
-public class HealthChecker {
-
-    private static final Logger LOGGER = Logger.getLogger(HealthChecker.class.getName());
-
-    private static final ConcurrentHashMap<String, Container> containers = new ConcurrentHashMap<>();
-
-//    @Scheduled(every = "{karavan.health-checker-interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
-//    void collectHealthStatuses() {
-//        containers.forEach((s, s2) -> {
-//            LOGGER.infof("HealthCheck for %s", s);
-//        });
-//    }
-
-//    public void addContainer(Container container){
-//        containers.put(container.getId(), container);
-//    }
-//
-//    public void removeContainer(String id){
-//        containers.remove(id);
-//    }
-}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
index 3058c2a0..7b3ed5d7 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
@@ -1,44 +1,20 @@
 package org.apache.camel.karavan.bashi;
 
-import com.github.dockerjava.api.model.HealthCheck;
 import io.quarkus.runtime.ShutdownEvent;
 import io.quarkus.runtime.StartupEvent;
-import io.quarkus.vertx.ConsumeEvent;
+import io.vertx.core.eventbus.EventBus;
 import org.apache.camel.karavan.bashi.docker.DockerService;
-import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
-import java.util.List;
-
-import static org.apache.camel.karavan.bashi.KaravanConstants.*;
 
 @ApplicationScoped
 public class KaravanBashi {
 
-    @ConfigProperty(name = "karavan.image")
-    String karavanImage;
-    @ConfigProperty(name = "karavan.port")
-    String karavanPort;
-    @ConfigProperty(name = "karavan.git-repository")
-    String gitRepository;
-    @ConfigProperty(name = "karavan.git-username")
-    String gitUsername;
-    @ConfigProperty(name = "karavan.git-password")
-    String gitPassword;
-    @ConfigProperty(name = "karavan.git-branch")
-    String gitBranch;
-
-    @ConfigProperty(name = "infinispan.image")
-    String infinispanImage;
-    @ConfigProperty(name = "infinispan.port")
-    String infinispanPort;
-    @ConfigProperty(name = "infinispan.username")
-    String infinispanUsername;
-    @ConfigProperty(name = "infinispan.password")
-    String infinispanPassword;
+    @Inject
+    EventBus eventBus;
 
     @Inject
     DockerService dockerService;
@@ -50,40 +26,7 @@ public class KaravanBashi {
         dockerService.checkContainersStatus();
         dockerService.createNetwork();
         dockerService.startListeners();
-        startInfinispan();
-    }
-
-    void startInfinispan() throws InterruptedException {
-        LOGGER.info("Infinispan is starting...");
-
-        HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status"))
-                .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
-
-        dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
-                List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword),
-                infinispanPort, true, healthCheck
-        );
-        dockerService.startContainer(INFINISPAN_CONTAINER_NAME);
-        LOGGER.info("Infinispan is started");
-    }
-
-    @ConsumeEvent(value = ADDRESS_INFINISPAN_HEALTH, blocking = true, ordered = false)
-    void startKaravan(String infinispanHealth) throws InterruptedException {
-        if (infinispanHealth.equals("healthy")) {
-            LOGGER.info("Karavan is starting...");
-            dockerService.createContainer(KARAVAN_CONTAINER_NAME, karavanImage,
-                    List.of(
-                            "QUARKUS_INFINISPAN_CLIENT_HOSTS=infinispan:11222",
-                            "KARAVAN_GIT_REPOSITORY=" + gitRepository,
-                            "KARAVAN_GIT_USERNAME=" + gitUsername,
-                            "KARAVAN_GIT_PASSWORD=" + gitPassword,
-                            "KARAVAN_GIT_BRANCH=" + gitBranch
-                    ),
-                    karavanPort, true, new HealthCheck()
-            );
-            dockerService.startContainer(KARAVAN_CONTAINER_NAME);
-            LOGGER.info("Karavan is started");
-        }
+        eventBus.publish(ConductorService.ADDRESS_INFINISPAN_START, "");
     }
 
     void onStop(@Observes ShutdownEvent ev) {
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanContainers.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanContainers.java
deleted file mode 100644
index 7fe14977..00000000
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanContainers.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.apache.camel.karavan.bashi;
-
-import com.github.dockerjava.api.model.Container;
-import io.vertx.core.eventbus.EventBus;
-import org.jboss.logging.Logger;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.inject.Inject;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static org.apache.camel.karavan.bashi.KaravanConstants.ADDRESS_INFINISPAN_HEALTH;
-
-@ApplicationScoped
-public class KaravanContainers {
-
-    private static final Logger LOGGER = Logger.getLogger(KaravanContainers.class.getName());
-
-    private static final ConcurrentHashMap<String, String> containers = new ConcurrentHashMap<>();
-
-    @Inject
-    EventBus eventBus;
-
-    public void addContainer(Container container, String health){
-        containers.put(container.getId(), health);
-        if (container.getNames()[0].equals("/infinispan")) {
-            eventBus.publish(ADDRESS_INFINISPAN_HEALTH, health);
-        }
-    }
-
-    public void removeContainer(String id){
-        containers.remove(id);
-    }
-}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/RunnerStatusService.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/RunnerStatusService.java
new file mode 100644
index 00000000..038d40c5
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/RunnerStatusService.java
@@ -0,0 +1,83 @@
+package org.apache.camel.karavan.bashi;
+
+import com.github.dockerjava.api.model.HealthCheck;
+import com.github.dockerjava.api.model.Statistics;
+import io.quarkus.scheduler.Scheduled;
+import io.quarkus.vertx.ConsumeEvent;
+import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.bashi.docker.DockerService;
+import org.apache.camel.karavan.bashi.infinispan.InfinispanService;
+import org.apache.camel.karavan.bashi.infinispan.PodStatus;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.util.*;
+
+import static org.apache.camel.karavan.bashi.Constants.*;
+
+@ApplicationScoped
+public class RunnerStatusService {
+
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
+    @Inject
+    DockerService dockerService;
+
+    @Inject
+    InfinispanService infinispanService;
+
+    private static final Logger LOGGER = Logger.getLogger(RunnerStatusService.class.getName());
+
+    @Scheduled(every = "{karavan.runner-status-interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
+    void collectRunnerStatus() {
+        System.out.println("collectRunnerStatus");
+        dockerService.getRunnerContainer().forEach(container -> {
+            String name = container.getNames()[0].replace("/", "");
+            String projectId = name.replace("-" + Constants.RUNNER_SUFFIX, "");
+            PodStatus ps = getPodStatus(container.getId(), name, projectId, container.getState(), container.getCreated());
+            infinispanService.savePodStatus(ps);
+        });
+    }
+
+    public PodStatus getPodStatus(String id, String name, String projectId, String state, Long created) {
+        try {
+            boolean initialized = Arrays.asList("running", "restarting").contains(state);
+            boolean ready = Arrays.asList("running", "restarting").contains(state);
+            boolean terminating = Arrays.asList("paused", "exited").contains(state);
+            String creationTimestamp = new Date(created).toString();
+
+            Statistics stats = dockerService.getContainerStats(id);
+
+            String requestMemory = Objects.requireNonNull(stats.getMemoryStats().getUsage()).toString();
+            String requestCpu = "N/A";
+            String limitMemory = Objects.requireNonNull(stats.getMemoryStats().getLimit()).toString();
+            String limitCpu = "N/A";
+            return new PodStatus(
+                    name,
+                    state,
+                    initialized,
+                    ready,
+                    terminating,
+                    "",
+                    name,
+                    projectId,
+                    environment,
+                    true,
+                    requestMemory,
+                    requestCpu,
+                    limitMemory,
+                    limitCpu,
+                    creationTimestamp
+            );
+        } catch (Exception ex) {
+            LOGGER.error(ex.getMessage(), ex.getCause());
+            return new PodStatus(
+                    name,
+                    projectId,
+                    environment);
+        }
+    }
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
index 9ea88796..2ca02b23 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
@@ -4,8 +4,13 @@ import com.github.dockerjava.api.async.ResultCallback;
 import com.github.dockerjava.api.model.Container;
 import com.github.dockerjava.api.model.Event;
 import com.github.dockerjava.api.model.EventType;
-import org.apache.camel.karavan.bashi.HealthChecker;
-import org.apache.camel.karavan.bashi.KaravanContainers;
+import com.github.dockerjava.api.model.Statistics;
+import io.vertx.core.eventbus.EventBus;
+import org.apache.camel.karavan.bashi.ConductorService;
+import org.apache.camel.karavan.bashi.Constants;
+import org.apache.camel.karavan.bashi.infinispan.InfinispanService;
+import org.apache.camel.karavan.bashi.infinispan.PodStatus;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
@@ -13,17 +18,24 @@ import javax.inject.Inject;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.Objects;
 
 @ApplicationScoped
 public class DockerEventListener implements ResultCallback<Event> {
 
-    @Inject
-    KaravanContainers karavanContainers;
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
 
     @Inject
     DockerService dockerService;
 
+    @Inject
+    EventBus eventBus;
+
+    @Inject
+    InfinispanService infinispanService;
+
     private static final Logger LOGGER = Logger.getLogger(DockerEventListener.class.getName());
 
     @Override
@@ -34,16 +46,21 @@ public class DockerEventListener implements ResultCallback<Event> {
     @Override
     public void onNext(Event event) {
 //        LOGGER.info(event.getType() + " : " + event.getStatus());
-        if (Objects.equals(event.getType(), EventType.CONTAINER)){
-            Container c = dockerService.getContainer(event.getId());
-            if (Arrays.asList("stop", "die", "kill", "pause", "destroy").contains(event.getStatus())) {
-                karavanContainers.removeContainer(c.getId());
-            } else if (Arrays.asList("start", "unpause").contains(event.getStatus())) {
-                karavanContainers.addContainer(c, "unknown");
-            } else if (event.getStatus().startsWith("health_status:")) {
-                String health = event.getStatus().replace("health_status: ", "");
-                LOGGER.info(event.getType() + " : " + event.getId() + " : " + health);
-                karavanContainers.addContainer(c, health);
+        if (Objects.equals(event.getType(), EventType.CONTAINER)) {
+            Container container = dockerService.getContainer(event.getId());
+            String status = event.getStatus();
+            if (container.getNames()[0].equals("/infinispan") && status.startsWith("health_status:")) {
+                String health = status.replace("health_status: ", "");
+                LOGGER.infof("Container %s health status: %s", container.getNames()[0], health);
+                eventBus.publish(ConductorService.ADDRESS_INFINISPAN_HEALTH, health);
+            } else if (container.getNames()[0].endsWith(Constants.RUNNER_SUFFIX)) {
+                if (Arrays.asList("stop", "die", "kill", "pause", "destroy").contains(event.getStatus())) {
+                    String name = container.getNames()[0].replace("/", "");
+                    String projectId = name.replace("-" + Constants.RUNNER_SUFFIX, "");
+                    infinispanService.deletePodStatus(new PodStatus(name, projectId, environment));
+                } else if (Arrays.asList("start", "unpause").contains(event.getStatus())) {
+
+                }
             }
         }
     }
@@ -62,4 +79,6 @@ public class DockerEventListener implements ResultCallback<Event> {
     public void close() throws IOException {
         LOGGER.error("DockerEventListener close");
     }
+
+
 }
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
index 89d54d74..44fa6824 100644
--- a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
@@ -9,21 +9,23 @@ import com.github.dockerjava.api.model.*;
 import com.github.dockerjava.core.DefaultDockerClientConfig;
 import com.github.dockerjava.core.DockerClientConfig;
 import com.github.dockerjava.core.DockerClientImpl;
+import com.github.dockerjava.core.InvocationBuilder;
 import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
 import com.github.dockerjava.transport.DockerHttpClient;
-import org.apache.camel.karavan.bashi.HealthChecker;
-import org.apache.camel.karavan.bashi.KaravanContainers;
+import io.vertx.core.eventbus.EventBus;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import static org.apache.camel.karavan.bashi.KaravanConstants.NETWORK_NAME;
+import static org.apache.camel.karavan.bashi.ConductorService.ADDRESS_INFINISPAN_HEALTH;
+import static org.apache.camel.karavan.bashi.Constants.NETWORK_NAME;
 
 @ApplicationScoped
 public class DockerService {
@@ -34,14 +36,15 @@ public class DockerService {
     DockerEventListener dockerEventListener;
 
     @Inject
-    KaravanContainers karavanContainers;
+    EventBus eventBus;
 
     public void startListeners() {
         getDockerClient().eventsCmd().exec(dockerEventListener);
     }
 
     public void createNetwork() {
-        if (!getDockerClient().listNetworksCmd().exec().stream().filter(n -> n.getName().equals(NETWORK_NAME))
+        if (!getDockerClient().listNetworksCmd().exec().stream()
+                .filter(n -> n.getName().equals(NETWORK_NAME))
                 .findFirst().isPresent()) {
             CreateNetworkResponse res = getDockerClient().createNetworkCmd().withName(NETWORK_NAME).withAttachable(true).exec();
             LOGGER.info("Network created: {}" + res);
@@ -55,7 +58,9 @@ public class DockerService {
                 .filter(c -> c.getState().equals("running"))
                 .forEach(c -> {
                     HealthState hs = getDockerClient().inspectContainerCmd(c.getId()).exec().getState().getHealth();
-                    karavanContainers.addContainer(c, hs != null ? hs.getStatus() : "unknown");
+                    if (c.getNames()[0].equals("/infinispan")) {
+                        eventBus.publish(ADDRESS_INFINISPAN_HEALTH, hs.getStatus());
+                    }
                 });
     }
 
@@ -64,7 +69,26 @@ public class DockerService {
         return containers.get(0);
     }
 
-    public void createContainer(String name, String image, List<String> env, String ports, boolean exposedPort, HealthCheck healthCheck) throws InterruptedException {
+    public List<Container> getRunnerContainer() {
+        return getDockerClient().listContainersCmd()
+                .withShowAll(true).withLabelFilter(Map.of("type", "runner")).exec();
+    }
+
+    public Statistics getContainerStats(String id) {
+        InvocationBuilder.AsyncResultCallback<Statistics> callback = new InvocationBuilder.AsyncResultCallback<>();
+        getDockerClient().statsCmd(id).withContainerId(id).exec(callback);
+        Statistics stats = null;
+        try {
+            stats = callback.awaitResult();
+            callback.close();
+        } catch (RuntimeException | IOException e) {
+            // you may want to throw an exception here
+        }
+        return stats;
+    }
+
+    public void createContainer(String name, String image, List<String> env, String ports,
+                                boolean exposedPort, HealthCheck healthCheck, Map<String, String> labels) throws InterruptedException {
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 0) {
             pullImage(image);
@@ -73,10 +97,11 @@ public class DockerService {
 
             CreateContainerResponse container = getDockerClient().createContainerCmd(image)
                     .withName(name)
+                    .withLabels(labels)
                     .withEnv(env)
                     .withExposedPorts(exposedPorts)
                     .withHostName(name)
-                    .withHostConfig(getHostConfig(ports))
+                    .withHostConfig(getHostConfig(ports, exposedPort))
                     .withHealthcheck(healthCheck)
                     .exec();
             LOGGER.info("Container created: " + container.getId());
@@ -100,7 +125,7 @@ public class DockerService {
         startContainer(name);
     }
 
-    public void stopContainer(String name) throws InterruptedException {
+    public void stopContainer(String name) {
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 1) {
             Container container = containers.get(0);
@@ -110,29 +135,42 @@ public class DockerService {
         }
     }
 
+    public void deleteContainer(String name) {
+        List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+        if (containers.size() == 1) {
+            Container container = containers.get(0);
+            getDockerClient().removeContainerCmd(container.getId()).exec();
+        }
+    }
+
     public void pullImage(String image) throws InterruptedException {
         List<Image> images = getDockerClient().listImagesCmd().withShowAll(true).exec();
         if (!images.stream().filter(i -> Arrays.asList(i.getRepoTags()).contains(image)).findFirst().isPresent()) {
-            ResultCallback.Adapter<PullResponseItem>  pull = getDockerClient().pullImageCmd(image).start().awaitCompletion();
+            ResultCallback.Adapter<PullResponseItem> pull = getDockerClient().pullImageCmd(image).start().awaitCompletion();
         }
     }
 
-    private HostConfig getHostConfig(String ports) {
+    private HostConfig getHostConfig(String ports, boolean exposedPort) {
         Ports portBindings = new Ports();
         getPortsFromString(ports).forEach((hostPort, containerPort) -> {
-            portBindings.bind(ExposedPort.tcp(containerPort), Ports.Binding.bindIp("0.0.0.0").bindPort(hostPort));
+            portBindings.bind(
+                    ExposedPort.tcp(containerPort),
+                    exposedPort ? Ports.Binding.bindIp("0.0.0.0").bindPort(hostPort) : Ports.Binding.bindPort(hostPort)
+            );
         });
         return new HostConfig()
                 .withPortBindings(portBindings)
                 .withNetworkMode(NETWORK_NAME);
     }
 
-    private Map<Integer,Integer> getPortsFromString(String ports){
-        Map<Integer,Integer> p = new HashMap<>();
-        Arrays.stream(ports.split(",")).forEach(s -> {
-            String[] values = s.split(":");
-            p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
-        });
+    private Map<Integer, Integer> getPortsFromString(String ports) {
+        Map<Integer, Integer> p = new HashMap<>();
+        if (!ports.isEmpty()) {
+            Arrays.stream(ports.split(",")).forEach(s -> {
+                String[] values = s.split(":");
+                p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
+            });
+        }
         return p;
     }
 
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ClientRunnerListener.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ClientRunnerListener.java
new file mode 100644
index 00000000..1eae6fe3
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ClientRunnerListener.java
@@ -0,0 +1,49 @@
+package org.apache.camel.karavan.bashi.infinispan;
+
+import io.vertx.core.eventbus.EventBus;
+import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.bashi.ConductorService;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified;
+import org.infinispan.client.hotrod.annotation.ClientListener;
+import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
+import org.infinispan.client.hotrod.event.ClientCacheEntryModifiedEvent;
+
+import java.util.Objects;
+
+@ClientListener
+public class ClientRunnerListener {
+
+    private final InfinispanService infinispanService;
+    private final EventBus eventBus;
+
+    public ClientRunnerListener(InfinispanService infinispanService, EventBus eventBus) {
+        this.infinispanService = infinispanService;
+        this.eventBus = eventBus;
+    }
+
+    @ClientCacheEntryCreated
+    public void entryCreated(ClientCacheEntryCreatedEvent<GroupedKey> event) {
+        System.out.println("entryCreated");
+        String command = event.getKey().getKey();
+        String projectId = event.getKey().getGroup();
+        if (Objects.equals(command, RunnerCommand.NAME.run.name())) {
+            eventBus.publish(ConductorService.ADDRESS_RUNNER, JsonObject.of("projectId", projectId, "command", command, "isRunner", true));
+        } else if (Objects.equals(command, RunnerCommand.NAME.delete.name())) {
+            eventBus.publish(ConductorService.ADDRESS_RUNNER, JsonObject.of("projectId", projectId, "command", command, "isRunner", true));
+        }
+    }
+
+    @ClientCacheEntryModified
+    public void entryModified(ClientCacheEntryModifiedEvent<GroupedKey> event) {
+        System.out.println("entryCreated");
+        String command = event.getKey().getKey();
+        String projectId = event.getKey().getGroup();
+        if (Objects.equals(command, RunnerCommand.NAME.run.name())) {
+            eventBus.publish(ConductorService.ADDRESS_RUNNER, JsonObject.of("projectId", projectId, "command", command, "isRunner", true));
+        } else if (Objects.equals(command, RunnerCommand.NAME.delete.name())) {
+            eventBus.publish(ConductorService.ADDRESS_RUNNER, JsonObject.of("projectId", projectId, "command", command, "isRunner", true));
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/GroupedKey.java
similarity index 79%
copy from karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java
copy to karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/GroupedKey.java
index a50e5ced..550a1d54 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/GroupedKey.java
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/GroupedKey.java
@@ -1,4 +1,4 @@
-package org.apache.camel.karavan.model;
+package org.apache.camel.karavan.bashi.infinispan;
 
 import org.infinispan.protostream.annotations.ProtoFactory;
 import org.infinispan.protostream.annotations.ProtoField;
@@ -22,6 +22,18 @@ public class GroupedKey {
     }
 
 
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
     public String getGroup() {
         return group;
     }
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/InfinispanService.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/InfinispanService.java
new file mode 100644
index 00000000..a2223ae1
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/InfinispanService.java
@@ -0,0 +1,122 @@
+/*
+ * 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.karavan.bashi.infinispan;
+
+import io.quarkus.vertx.ConsumeEvent;
+import io.vertx.core.eventbus.EventBus;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.infinispan.client.hotrod.RemoteCache;
+import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.Search;
+import org.infinispan.client.hotrod.configuration.ClientIntelligence;
+import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
+import org.infinispan.commons.api.BasicCache;
+import org.infinispan.commons.configuration.StringConfiguration;
+import org.infinispan.commons.marshall.ProtoStreamMarshaller;
+import org.infinispan.query.dsl.QueryFactory;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Default;
+import javax.inject.Inject;
+
+import java.util.List;
+
+import static org.apache.camel.karavan.bashi.ConductorService.ADDRESS_INFINISPAN_HEALTH;
+
+@Default
+@ApplicationScoped
+public class InfinispanService  {
+
+    @ConfigProperty(name ="quarkus.infinispan-client.hosts")
+    String infinispanHosts;
+    @ConfigProperty(name ="quarkus.infinispan-client.username")
+    String infinispanUsername;
+    @ConfigProperty(name ="quarkus.infinispan-client.password")
+    String infinispanPassword;
+
+    @Inject
+    EventBus eventBus;
+
+    private BasicCache<GroupedKey, PodStatus> podStatuses;
+    private BasicCache<GroupedKey, String> runnerCommands;
+
+    private static final String CACHE_CONFIG = "<distributed-cache name=\"%s\">"
+            + " <encoding media-type=\"application/x-protostream\"/>"
+            + " <groups enabled=\"true\"/>"
+            + "</distributed-cache>";
+
+    private static final Logger LOGGER = Logger.getLogger(InfinispanService.class.getName());
+
+    @ConsumeEvent(value = ADDRESS_INFINISPAN_HEALTH, blocking = true, ordered = true)
+    void startService(String infinispanHealth) {
+        if (infinispanHealth.equals("healthy")) {
+            LOGGER.info("InfinispanService is starting in remote mode");
+
+            ProtoStreamMarshaller marshaller = new ProtoStreamMarshaller();
+            marshaller.register(new ProjectStoreSchemaImpl());
+
+            ConfigurationBuilder builder = new ConfigurationBuilder();
+            builder.socketTimeout(1000)
+                    .connectionTimeout(10000)
+                    .addServers(infinispanHosts)
+                    .security()
+                        .authentication().enable()
+                        .username(infinispanUsername)
+                        .password(infinispanPassword)
+                    .clientIntelligence(ClientIntelligence.BASIC)
+                    .marshaller(marshaller);
+
+            RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
+            runnerCommands = cacheManager.administration().getOrCreateCache(RunnerCommand.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, RunnerCommand.CACHE)));
+            podStatuses = cacheManager.administration().getOrCreateCache(PodStatus.CACHE, new StringConfiguration(String.format(CACHE_CONFIG, PodStatus.CACHE)));
+            cacheManager.getCache(RunnerCommand.CACHE).addClientListener(new ClientRunnerListener(this, eventBus));
+        }
+    }
+
+    public void sendRunnerCommand(String projectId, String runnerName, RunnerCommand.NAME command) {
+        runnerCommands.put(GroupedKey.create(projectId, runnerName), command.name());
+    }
+
+    public String getRunnerCommand(GroupedKey key) {
+        return runnerCommands.get(key);
+    }
+
+    public List<PodStatus> getPodStatuses(String projectId, String env) {
+        QueryFactory queryFactory = Search.getQueryFactory((RemoteCache<?, ?>) podStatuses);
+        return queryFactory.<PodStatus>create("FROM karavan.PodStatus WHERE project = :project AND env = :env")
+                .setParameter("project", projectId)
+                .setParameter("env", env)
+                .execute().list();
+    }
+
+    public List<PodStatus> getPodStatuses(String env) {
+        QueryFactory queryFactory = Search.getQueryFactory((RemoteCache<?, ?>) podStatuses);
+        return queryFactory.<PodStatus>create("FROM karavan.PodStatus WHERE env = :env")
+                .setParameter("env", env)
+                .execute().list();
+    }
+
+    public void savePodStatus(PodStatus status) {
+        podStatuses.put(GroupedKey.create(status.getProject(), status.getName()), status);
+    }
+
+    public void deletePodStatus(PodStatus status) {
+        podStatuses.remove(GroupedKey.create(status.getProject(), status.getName()));
+    }
+
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/PodStatus.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/PodStatus.java
new file mode 100644
index 00000000..31ee43a8
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/PodStatus.java
@@ -0,0 +1,209 @@
+package org.apache.camel.karavan.bashi.infinispan;
+
+import org.infinispan.protostream.annotations.ProtoFactory;
+import org.infinispan.protostream.annotations.ProtoField;
+
+public class PodStatus {
+    public static final String CACHE = "pod_statuses";
+    @ProtoField(number = 1)
+    String name;
+    @ProtoField(number = 2)
+    String phase;
+    @ProtoField(number = 3)
+    Boolean initialized;
+    @ProtoField(number = 4)
+    Boolean ready;
+    @ProtoField(number = 5)
+    Boolean terminating;
+    @ProtoField(number = 6)
+    String reason;
+    @ProtoField(number = 7)
+    String deployment;
+    @ProtoField(number = 8)
+    String project;
+    @ProtoField(number = 9)
+    String env;
+    @ProtoField(number = 10)
+    Boolean runner;
+    @ProtoField(number = 11)
+    String requestMemory;
+    @ProtoField(number = 12)
+    String requestCpu;
+    @ProtoField(number = 13)
+    String limitMemory;
+    @ProtoField(number = 14)
+    String limitCpu;
+    @ProtoField(number = 15)
+    String creationTimestamp;
+
+    public PodStatus(String name, String project, String env) {
+        this.name = name;
+        this.phase = "";
+        this.initialized = false;
+        this.ready = false;
+        this.terminating = false;
+        this.reason = "";
+        this.project = project;
+        this.env = env;
+    }
+
+    @ProtoFactory
+    public PodStatus(String name, String phase, Boolean initialized, Boolean ready, Boolean terminating, String reason, String deployment, String project, String env, Boolean runner, String requestMemory, String requestCpu, String limitMemory, String limitCpu, String creationTimestamp) {
+        this.name = name;
+        this.phase = phase;
+        this.initialized = initialized;
+        this.ready = ready;
+        this.terminating = terminating;
+        this.reason = reason;
+        this.deployment = deployment;
+        this.project = project;
+        this.env = env;
+        this.runner = runner;
+        this.requestMemory = requestMemory;
+        this.requestCpu = requestCpu;
+        this.limitMemory = limitMemory;
+        this.limitCpu = limitCpu;
+        this.creationTimestamp = creationTimestamp;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPhase() {
+        return phase;
+    }
+
+    public void setPhase(String phase) {
+        this.phase = phase;
+    }
+
+    public Boolean getInitialized() {
+        return initialized;
+    }
+
+    public void setInitialized(Boolean initialized) {
+        this.initialized = initialized;
+    }
+
+    public Boolean getReady() {
+        return ready;
+    }
+
+    public void setReady(Boolean ready) {
+        this.ready = ready;
+    }
+
+    public Boolean getTerminating() {
+        return terminating;
+    }
+
+    public void setTerminating(Boolean terminating) {
+        this.terminating = terminating;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    public String getDeployment() {
+        return deployment;
+    }
+
+    public void setDeployment(String deployment) {
+        this.deployment = deployment;
+    }
+
+    public String getProject() {
+        return project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getEnv() {
+        return env;
+    }
+
+    public void setEnv(String env) {
+        this.env = env;
+    }
+
+    public Boolean getRunner() {
+        return runner;
+    }
+
+    public void setRunner(Boolean runner) {
+        this.runner = runner;
+    }
+
+    public String getRequestMemory() {
+        return requestMemory;
+    }
+
+    public void setRequestMemory(String requestMemory) {
+        this.requestMemory = requestMemory;
+    }
+
+    public String getRequestCpu() {
+        return requestCpu;
+    }
+
+    public void setRequestCpu(String requestCpu) {
+        this.requestCpu = requestCpu;
+    }
+
+    public String getLimitMemory() {
+        return limitMemory;
+    }
+
+    public void setLimitMemory(String limitMemory) {
+        this.limitMemory = limitMemory;
+    }
+
+    public String getLimitCpu() {
+        return limitCpu;
+    }
+
+    public void setLimitCpu(String limitCpu) {
+        this.limitCpu = limitCpu;
+    }
+
+    public String getCreationTimestamp() {
+        return creationTimestamp;
+    }
+
+    public void setCreationTimestamp(String creationTimestamp) {
+        this.creationTimestamp = creationTimestamp;
+    }
+
+    @Override
+    public String toString() {
+        return "PodStatus{" +
+                "name='" + name + '\'' +
+                ", phase='" + phase + '\'' +
+                ", initialized=" + initialized +
+                ", ready=" + ready +
+                ", terminating=" + terminating +
+                ", reason='" + reason + '\'' +
+                ", deployment='" + deployment + '\'' +
+                ", project='" + project + '\'' +
+                ", env='" + env + '\'' +
+                ", runner=" + runner +
+                ", requestMemory='" + requestMemory + '\'' +
+                ", requestCpu='" + requestCpu + '\'' +
+                ", limitMemory='" + limitMemory + '\'' +
+                ", limitCpu='" + limitCpu + '\'' +
+                ", creationTimestamp='" + creationTimestamp + '\'' +
+                '}';
+    }
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ProjectStoreSchema.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ProjectStoreSchema.java
new file mode 100644
index 00000000..bf3ea015
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/ProjectStoreSchema.java
@@ -0,0 +1,8 @@
+package org.apache.camel.karavan.bashi.infinispan;
+
+import org.infinispan.protostream.GeneratedSchema;
+import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
+
+@AutoProtoSchemaBuilder(includeClasses = {GroupedKey.class, PodStatus.class}, schemaPackageName = "karavan")
+public interface ProjectStoreSchema extends GeneratedSchema {
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/RunnerCommand.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/RunnerCommand.java
new file mode 100644
index 00000000..1514920d
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/infinispan/RunnerCommand.java
@@ -0,0 +1,13 @@
+package org.apache.camel.karavan.bashi.infinispan;
+
+public class RunnerCommand {
+
+    public enum NAME {
+        run,
+        delete,
+        reload
+    }
+
+    public static final String CACHE = "runner_commands";
+
+}
diff --git a/karavan-bashi/src/main/resources/application.properties b/karavan-bashi/src/main/resources/application.properties
index 8649065e..932ff649 100644
--- a/karavan-bashi/src/main/resources/application.properties
+++ b/karavan-bashi/src/main/resources/application.properties
@@ -1,16 +1,26 @@
+# Infinispan container config in Docker
 infinispan.image=quay.io/infinispan/server:14.0.6.Final
 infinispan.port=11222:11222
 infinispan.username=admin
 infinispan.password=karavan
 
+# Karavan container config in Docker
 karavan.image=marat/karavan:3.21.1-SNAPSHOT
+karavan.runner-image=ghcr.io/apache/camel-karavan-runner:3.21.0
 karavan.port=8080:8080
 karavan.environment=dev
 karavan.default-runtime=quarkus
 karavan.runtimes=quarkus,spring-boot
+karavan.runner-status-interval=2s
 
 # Git repository Configuration
 karavan.git-repository=${GIT_REPOSITORY}
 karavan.git-username=${GIT_USERNAME}
 karavan.git-password=${GIT_TOKEN}
 karavan.git-branch=main
+
+# Infinispan Client configuration
+quarkus.infinispan-client.hosts=localhost:11222
+quarkus.infinispan-client.username=admin
+quarkus.infinispan-client.password=karavan
+quarkus.infinispan-client.devservices.enabled=false
\ No newline at end of file


[camel-karavan] 02/05: First experiment for #817

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c7c9aaf3dab99548d8f401510c443cb4485db99b
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 6 11:31:05 2023 -0400

    First experiment for #817
---
 karavan-bashi/.dockerignore                        |   5 +
 karavan-bashi/.gitignore                           |  43 +++
 karavan-bashi/.mvn/wrapper/.gitignore              |   1 +
 .../.mvn/wrapper/MavenWrapperDownloader.java       |  98 +++++++
 .../.mvn/wrapper/maven-wrapper.properties          |  18 ++
 karavan-bashi/mvnw                                 | 308 +++++++++++++++++++++
 karavan-bashi/mvnw.cmd                             | 205 ++++++++++++++
 karavan-bashi/pom.xml                              | 128 +++++++++
 karavan-bashi/src/main/docker/Dockerfile.jvm       |  95 +++++++
 .../src/main/docker/Dockerfile.legacy-jar          |  91 ++++++
 karavan-bashi/src/main/docker/Dockerfile.native    |  27 ++
 .../src/main/docker/Dockerfile.native-micro        |  30 ++
 .../apache/camel/karavan/bashi/KaravanBashi.java   |  61 ++++
 .../camel/karavan/bashi/KaravanConstants.java      |   7 +
 .../karavan/bashi/docker/DockerEventListener.java  |  40 +++
 .../camel/karavan/bashi/docker/DockerService.java  | 129 +++++++++
 .../src/main/resources/application.properties      |  16 ++
 17 files changed, 1302 insertions(+)

diff --git a/karavan-bashi/.dockerignore b/karavan-bashi/.dockerignore
new file mode 100644
index 00000000..94810d00
--- /dev/null
+++ b/karavan-bashi/.dockerignore
@@ -0,0 +1,5 @@
+*
+!target/*-runner
+!target/*-runner.jar
+!target/lib/*
+!target/quarkus-app/*
\ No newline at end of file
diff --git a/karavan-bashi/.gitignore b/karavan-bashi/.gitignore
new file mode 100644
index 00000000..8c7863e7
--- /dev/null
+++ b/karavan-bashi/.gitignore
@@ -0,0 +1,43 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+
+# Plugin directory
+/.quarkus/cli/plugins/
diff --git a/karavan-bashi/.mvn/wrapper/.gitignore b/karavan-bashi/.mvn/wrapper/.gitignore
new file mode 100644
index 00000000..e72f5e8b
--- /dev/null
+++ b/karavan-bashi/.mvn/wrapper/.gitignore
@@ -0,0 +1 @@
+maven-wrapper.jar
diff --git a/karavan-bashi/.mvn/wrapper/MavenWrapperDownloader.java b/karavan-bashi/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 00000000..84d1e60d
--- /dev/null
+++ b/karavan-bashi/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+public final class MavenWrapperDownloader
+{
+    private static final String WRAPPER_VERSION = "3.2.0";
+
+    private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) );
+
+    public static void main( String[] args )
+    {
+        log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION );
+
+        if ( args.length != 2 )
+        {
+            System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" );
+            System.exit( 1 );
+        }
+
+        try
+        {
+            log( " - Downloader started" );
+            final URL wrapperUrl = new URL( args[0] );
+            final String jarPath = args[1].replace( "..", "" ); // Sanitize path
+            final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize();
+            downloadFileFromURL( wrapperUrl, wrapperJarPath );
+            log( "Done" );
+        }
+        catch ( IOException e )
+        {
+            System.err.println( "- Error downloading: " + e.getMessage() );
+            if ( VERBOSE )
+            {
+                e.printStackTrace();
+            }
+            System.exit( 1 );
+        }
+    }
+
+    private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath )
+        throws IOException
+    {
+        log( " - Downloading to: " + wrapperJarPath );
+        if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null )
+        {
+            final String username = System.getenv( "MVNW_USERNAME" );
+            final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray();
+            Authenticator.setDefault( new Authenticator()
+            {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication()
+                {
+                    return new PasswordAuthentication( username, password );
+                }
+            } );
+        }
+        try ( InputStream inStream = wrapperUrl.openStream() )
+        {
+            Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING );
+        }
+        log( " - Downloader complete" );
+    }
+
+    private static void log( String msg )
+    {
+        if ( VERBOSE )
+        {
+            System.out.println( msg );
+        }
+    }
+
+}
diff --git a/karavan-bashi/.mvn/wrapper/maven-wrapper.properties b/karavan-bashi/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..70f4f50f
--- /dev/null
+++ b/karavan-bashi/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# 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.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/karavan-bashi/mvnw b/karavan-bashi/mvnw
new file mode 100755
index 00000000..8d937f4c
--- /dev/null
+++ b/karavan-bashi/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+      else
+        JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=$(java-config --jre-home)
+  fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+    JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="$(which javac)"
+  if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=$(which readlink)
+    if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+      if $darwin ; then
+        javaHome="$(dirname "\"$javaExecutable\"")"
+        javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+      else
+        javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+      fi
+      javaHome="$(dirname "\"$javaExecutable\"")"
+      javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=$(cd "$wdir/.." || exit 1; pwd)
+    fi
+    # end of workaround
+  done
+  printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    # Remove \r in case we run on Windows within Git Bash
+    # and check out the repository with auto CRLF management
+    # enabled. Otherwise, we may read lines that are delimited with
+    # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+    # splitting rules.
+    tr -s '\r\n' ' ' < "$1"
+  fi
+}
+
+log() {
+  if [ "$MVNW_VERBOSE" = true ]; then
+    printf '%s\n' "$1"
+  fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+    log "Found $wrapperJarPath"
+else
+    log "Couldn't find $wrapperJarPath, downloading it ..."
+
+    if [ -n "$MVNW_REPOURL" ]; then
+      wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    else
+      wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    fi
+    while IFS="=" read -r key value; do
+      # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+      safeValue=$(echo "$value" | tr -d '\r')
+      case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+      esac
+    done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+    log "Downloading from: $wrapperUrl"
+
+    if $cygwin; then
+      wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+    fi
+
+    if command -v wget > /dev/null; then
+        log "Found wget ... using wget"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        log "Found curl ... using curl"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        else
+            curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        fi
+    else
+        log "Falling back to using Java to download"
+        javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaSource=$(cygpath --path --windows "$javaSource")
+          javaClass=$(cygpath --path --windows "$javaClass")
+        fi
+        if [ -e "$javaSource" ]; then
+            if [ ! -e "$javaClass" ]; then
+                log " - Compiling MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/javac" "$javaSource")
+            fi
+            if [ -e "$javaClass" ]; then
+                log " - Running MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+  case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+  esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+  wrapperSha256Result=false
+  if command -v sha256sum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  elif command -v shasum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+    echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+    exit 1
+  fi
+  if [ $wrapperSha256Result = false ]; then
+    echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+    echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+    echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/karavan-bashi/mvnw.cmd b/karavan-bashi/mvnw.cmd
new file mode 100755
index 00000000..c4586b56
--- /dev/null
+++ b/karavan-bashi/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %WRAPPER_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+    powershell -Command "&{"^
+       "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+       "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+       "  Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+       "  Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+       "  Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+       "  exit 1;"^
+       "}"^
+       "}"
+    if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+  %JVM_CONFIG_MAVEN_PROPS% ^
+  %MAVEN_OPTS% ^
+  %MAVEN_DEBUG_OPTS% ^
+  -classpath %WRAPPER_JAR% ^
+  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/karavan-bashi/pom.xml b/karavan-bashi/pom.xml
new file mode 100644
index 00000000..3f4f0b6b
--- /dev/null
+++ b/karavan-bashi/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.camel.karavan</groupId>
+    <artifactId>karavan-bashi</artifactId>
+    <version>3.21.1-SNAPSHOT</version>
+    <properties>
+        <compiler-plugin.version>3.11.0</compiler-plugin.version>
+        <maven.compiler.release>11</maven.compiler.release>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
+        <quarkus.platform.version>2.16.7.Final</quarkus.platform.version>
+        <skipITs>true</skipITs>
+        <surefire-plugin.version>3.0.0</surefire-plugin.version>
+        <docker-java.version>3.2.7</docker-java.version>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>${quarkus.platform.artifact-id}</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-arc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-core</artifactId>
+            <version>${docker-java.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-transport-httpclient5</artifactId>
+            <version>${docker-java.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>quarkus-maven-plugin</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                            <goal>generate-code</goal>
+                            <goal>generate-code-tests</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>${compiler-plugin.version}</version>
+                <configuration>
+                    <compilerArgs>
+                        <arg>-parameters</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${surefire-plugin.version}</version>
+                <configuration>
+                    <systemPropertyVariables>
+                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                        <maven.home>${maven.home}</maven.home>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>${surefire-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
+                                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                                <maven.home>${maven.home}</maven.home>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>native</id>
+            <activation>
+                <property>
+                    <name>native</name>
+                </property>
+            </activation>
+            <properties>
+                <skipITs>false</skipITs>
+                <quarkus.package.type>native</quarkus.package.type>
+            </properties>
+        </profile>
+    </profiles>
+</project>
diff --git a/karavan-bashi/src/main/docker/Dockerfile.jvm b/karavan-bashi/src/main/docker/Dockerfile.jvm
new file mode 100644
index 00000000..304872d0
--- /dev/null
+++ b/karavan-bashi/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,95 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/karavan-bashi-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/karavan-bashi-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this :  EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/karavan-bashi-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+#   in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+#   used to calculate a default maximal heap memory based on a containers restriction.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+#   of the container available memory as set here. The default is `50` which means 50%
+#   of the available memory is used as an upper boundary. You can skip this mechanism by
+#   setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+#   is used to calculate a default initial heap memory based on the maximum heap memory.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+#   is used as the initial heap size. You can skip this mechanism by setting this value
+#   to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+#   This is used to calculate the maximum value of the initial heap memory. If used in
+#   a container without any memory constraints for the container then this option has
+#   no effect. If there is a memory constraint then `-Xms` is limited to the value set
+#   here. The default is 4096MB which means the calculated value of `-Xms` never will
+#   be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+#   when things are happening. This option, if set to true, will set
+#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+#    true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+#   (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+#   (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+#   (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+#   previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+#   contain the necessary JRE command-line options to specify the required GC, which
+#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+#   accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.15
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
diff --git a/karavan-bashi/src/main/docker/Dockerfile.legacy-jar b/karavan-bashi/src/main/docker/Dockerfile.legacy-jar
new file mode 100644
index 00000000..5ed8043f
--- /dev/null
+++ b/karavan-bashi/src/main/docker/Dockerfile.legacy-jar
@@ -0,0 +1,91 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dquarkus.package.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/karavan-bashi-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/karavan-bashi-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this :  EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/karavan-bashi-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+#   in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+#   used to calculate a default maximal heap memory based on a containers restriction.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+#   of the container available memory as set here. The default is `50` which means 50%
+#   of the available memory is used as an upper boundary. You can skip this mechanism by
+#   setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+#   is used to calculate a default initial heap memory based on the maximum heap memory.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+#   is used as the initial heap size. You can skip this mechanism by setting this value
+#   to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+#   This is used to calculate the maximum value of the initial heap memory. If used in
+#   a container without any memory constraints for the container then this option has
+#   no effect. If there is a memory constraint then `-Xms` is limited to the value set
+#   here. The default is 4096MB which means the calculated value of `-Xms` never will
+#   be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+#   when things are happening. This option, if set to true, will set
+#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+#    true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+#   (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+#   (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+#   (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+#   previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+#   contain the necessary JRE command-line options to specify the required GC, which
+#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+#   accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.15
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
diff --git a/karavan-bashi/src/main/docker/Dockerfile.native b/karavan-bashi/src/main/docker/Dockerfile.native
new file mode 100644
index 00000000..45ba5a97
--- /dev/null
+++ b/karavan-bashi/src/main/docker/Dockerfile.native
@@ -0,0 +1,27 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/karavan-bashi .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/karavan-bashi
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/karavan-bashi/src/main/docker/Dockerfile.native-micro b/karavan-bashi/src/main/docker/Dockerfile.native-micro
new file mode 100644
index 00000000..05c02902
--- /dev/null
+++ b/karavan-bashi/src/main/docker/Dockerfile.native-micro
@@ -0,0 +1,30 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/karavan-bashi .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/karavan-bashi
+#
+###
+FROM quay.io/quarkus/quarkus-micro-image:2.0
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
new file mode 100644
index 00000000..1f21389b
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanBashi.java
@@ -0,0 +1,61 @@
+package org.apache.camel.karavan.bashi;
+
+import io.quarkus.runtime.ShutdownEvent;
+import io.quarkus.runtime.StartupEvent;
+import org.apache.camel.karavan.bashi.docker.DockerService;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+import java.util.List;
+
+import static org.apache.camel.karavan.bashi.KaravanConstants.INFINISPAN_CONTAINER_NAME;
+
+@ApplicationScoped
+public class KaravanBashi {
+
+    @ConfigProperty(name = "infinispan.image")
+    String infinispanImage;
+    @ConfigProperty(name = "infinispan.port")
+    String infinispanPort;
+    @ConfigProperty(name = "infinispan.username")
+    String infinispanUsername;
+    @ConfigProperty(name = "infinispan.password")
+    String infinispanPassword;
+
+    @Inject
+    DockerService dockerService;
+
+    private static final Logger LOGGER = Logger.getLogger(KaravanBashi.class.getName());
+
+    void onStart(@Observes StartupEvent ev) throws InterruptedException {
+        LOGGER.info("Karavan Bashi is starting...");
+        dockerService.createNetwork();
+        dockerService.startListeners();
+        startInfinispan();
+    }
+
+    void startInfinispan() throws InterruptedException {
+        LOGGER.info("Infinispan is starting...");
+        dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
+                List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword), infinispanPort, false
+        );
+        dockerService.startContainer(INFINISPAN_CONTAINER_NAME);
+        LOGGER.info("Infinispan is started");
+    }
+
+    void startKaravan() throws InterruptedException {
+        LOGGER.info("Karavan is starting...");
+        dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
+                List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword), infinispanPort, false
+        );
+        dockerService.startContainer(INFINISPAN_CONTAINER_NAME);
+        LOGGER.info("Karavan is started");
+    }
+
+    void onStop(@Observes ShutdownEvent ev) {
+        LOGGER.info("Karavan Bashi is stopping...");
+    }
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
new file mode 100644
index 00000000..eab76977
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/KaravanConstants.java
@@ -0,0 +1,7 @@
+package org.apache.camel.karavan.bashi;
+
+public class KaravanConstants {
+
+    public static final String NETWORK_NAME = "karavan";
+    public static final String INFINISPAN_CONTAINER_NAME = "infinispan";
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
new file mode 100644
index 00000000..d0551ba7
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerEventListener.java
@@ -0,0 +1,40 @@
+package org.apache.camel.karavan.bashi.docker;
+
+import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.model.Event;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import java.io.Closeable;
+import java.io.IOException;
+
+@ApplicationScoped
+public class DockerEventListener implements ResultCallback<Event> {
+
+    private static final Logger LOGGER = Logger.getLogger(DockerEventListener.class.getName());
+
+    @Override
+    public void onStart(Closeable closeable) {
+        LOGGER.info("DockerEventListener started");
+    }
+
+    @Override
+    public void onNext(Event event) {
+        LOGGER.info(event.getType() + " : " + event.getStatus());
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        LOGGER.error(throwable.getMessage());
+    }
+
+    @Override
+    public void onComplete() {
+        LOGGER.error("DockerEventListener complete");
+    }
+
+    @Override
+    public void close() throws IOException {
+        LOGGER.error("DockerEventListener close");
+    }
+}
diff --git a/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
new file mode 100644
index 00000000..e65af77f
--- /dev/null
+++ b/karavan-bashi/src/main/java/org/apache/camel/karavan/bashi/docker/DockerService.java
@@ -0,0 +1,129 @@
+package org.apache.camel.karavan.bashi.docker;
+
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.CreateNetworkResponse;
+import com.github.dockerjava.api.model.*;
+import com.github.dockerjava.core.DefaultDockerClientConfig;
+import com.github.dockerjava.core.DockerClientConfig;
+import com.github.dockerjava.core.DockerClientImpl;
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+import com.github.dockerjava.transport.DockerHttpClient;
+import org.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.camel.karavan.bashi.KaravanConstants.NETWORK_NAME;
+
+@ApplicationScoped
+public class DockerService {
+
+    private static final Logger LOGGER = Logger.getLogger(DockerService.class.getName());
+
+    @Inject
+    DockerEventListener dockerEventListener;
+
+    public void startListeners() {
+        getDockerClient().eventsCmd().exec(dockerEventListener);
+    }
+
+    public void createNetwork() {
+        if (!getDockerClient().listNetworksCmd().exec().stream().filter(n -> n.getName().equals(NETWORK_NAME))
+                .findFirst().isPresent()) {
+            CreateNetworkResponse res = getDockerClient().createNetworkCmd().withName(NETWORK_NAME).exec();
+            LOGGER.info("Network created: {}" + res);
+        } else {
+            LOGGER.info("Network already exists with name: " + NETWORK_NAME);
+        }
+    }
+
+    public void createContainer(String name, String image, List<String> env, String ports, boolean hostNet) throws InterruptedException {
+        List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+        System.out.println(containers);
+        if (containers.size() == 0) {
+            pullImage(image);
+
+            CreateContainerResponse container = getDockerClient().createContainerCmd(image)
+                    .withName(name)
+                    .withEnv(env)
+                    .withHostName(name)
+                    .withHostConfig(getHostConfig(ports, hostNet))
+                    .exec();
+            LOGGER.info("Container created: " + container.toString());
+        } else {
+            LOGGER.info("Container already exists: " + containers.get(0).toString());
+        }
+    }
+
+    public void startContainer(String name) throws InterruptedException {
+        List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+        if (containers.size() == 1) {
+            Container container = containers.get(0);
+            if (!container.getState().equals("running")) {
+                getDockerClient().startContainerCmd(container.getId()).exec();
+            }
+        }
+    }
+
+    public void restartContainer(String name) throws InterruptedException {
+        stopContainer(name);
+        startContainer(name);
+    }
+
+    public void stopContainer(String name) throws InterruptedException {
+        List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+        if (containers.size() == 1) {
+            Container container = containers.get(0);
+            if (container.getState().equals("running")) {
+                getDockerClient().stopContainerCmd(container.getId()).exec();
+            }
+        }
+    }
+
+    public void pullImage(String image) throws InterruptedException {
+        ResultCallback.Adapter<PullResponseItem>  pull = getDockerClient().pullImageCmd(image).start().awaitCompletion();
+    }
+
+    private HostConfig getHostConfig(String ports, boolean hostNet) {
+        Ports portBindings = new Ports();
+        getPortsFromString(ports).forEach((hostPort, containerPort) -> {
+            portBindings.bind(ExposedPort.tcp(containerPort), Ports.Binding.bindPort(hostPort));
+        });
+        return new HostConfig()
+                .withPortBindings(portBindings)
+                .withNetworkMode(hostNet ? "host" : NETWORK_NAME);
+    }
+
+    private Map<Integer,Integer> getPortsFromString(String ports){
+        Map<Integer,Integer> p = new HashMap<>();
+        Arrays.stream(ports.split(",")).forEach(s -> {
+            String[] values = s.split(":");
+            p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
+        });
+        return p;
+    }
+
+    private DockerClientConfig getDockerClientConfig() {
+        return DefaultDockerClientConfig.createDefaultConfigBuilder().build();
+    }
+
+    private DockerHttpClient getDockerHttpClient() {
+        DockerClientConfig config = getDockerClientConfig();
+
+        return new ApacheDockerHttpClient.Builder()
+                .dockerHost(config.getDockerHost())
+                .sslConfig(config.getSSLConfig())
+                .maxConnections(100)
+                .build();
+    }
+
+    private DockerClient getDockerClient() {
+        return DockerClientImpl.getInstance(getDockerClientConfig(), getDockerHttpClient());
+    }
+}
diff --git a/karavan-bashi/src/main/resources/application.properties b/karavan-bashi/src/main/resources/application.properties
new file mode 100644
index 00000000..d9eb55bb
--- /dev/null
+++ b/karavan-bashi/src/main/resources/application.properties
@@ -0,0 +1,16 @@
+infinispan.image=quay.io/infinispan/server:14.0
+infinispan.port=11222:11222
+infinispan.username=admin
+infinispan.password=karavan
+
+karavan.image=ghcr.io/apache/camel-karavan
+
+karavan.environment=dev
+karavan.default-runtime=quarkus
+karavan.runtimes=quarkus,spring-boot
+
+# Git repository Configuration
+karavan.git-repository=${GIT_REPOSITORY}
+karavan.git-username=${GIT_USERNAME}
+karavan.git-password=${GIT_TOKEN}
+karavan.git-branch=main


[camel-karavan] 04/05: Infinispan and Karavan up and running #817

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a7fb224445a292fb4eee6a779c7d130130d2c49c
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 6 16:29:38 2023 -0400

    Infinispan and Karavan up and running #817
---
 .../java/org/apache/camel/karavan/service/InfinispanService.java  | 8 +-------
 .../java/org/apache/camel/karavan/service/KaravanService.java     | 6 +++++-
 karavan-app/src/main/webui/src/designer/karavan.css               | 8 ++++++++
 3 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
index a834521f..75f47e17 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
@@ -93,15 +93,9 @@ public class InfinispanService implements HealthCheck  {
         if (cacheManager == null) {
             LOGGER.info("InfinispanService is starting in local mode");
             GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
-            global.globalState().enable().persistentLocation("karavan-data");
             DefaultCacheManager cacheManager = new DefaultCacheManager(global.build());
             ConfigurationBuilder builder = new ConfigurationBuilder();
-            builder.clustering()
-                    .cacheMode(CacheMode.LOCAL)
-                    .persistence().passivation(false)
-                    .addStore(SingleFileStoreConfigurationBuilder.class)
-                    .shared(false)
-                    .preload(true);
+            builder.clustering().cacheMode(CacheMode.LOCAL);
             environments = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Environment.CACHE, builder.build());
             projects = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Project.CACHE, builder.build());
             files = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ProjectFile.CACHE, builder.build());
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
index 88915aca..bd0feafd 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
@@ -69,6 +69,10 @@ public class KaravanService {
     }
 
     void startInformers() {
-        bus.publish(KubernetesService.START_INFORMERS, "");
+        if (kubernetesService.inKubernetes()) {
+            bus.publish(KubernetesService.START_INFORMERS, "");
+        } else {
+
+        }
     }
 }
diff --git a/karavan-app/src/main/webui/src/designer/karavan.css b/karavan-app/src/main/webui/src/designer/karavan.css
index f90cc42b..b62b241a 100644
--- a/karavan-app/src/main/webui/src/designer/karavan.css
+++ b/karavan-app/src/main/webui/src/designer/karavan.css
@@ -912,8 +912,16 @@
 
 .dsl-modal .pf-c-card__body {
     padding-bottom: 0;
+    height: 54px;
 }
 
+
+.dsl-modal .pf-c-card__body p {
+    overflow: hidden;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+}
 .dsl-modal .pf-c-card__footer {
     padding-bottom: 1em;
 }