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 2022/10/13 17:18:50 UTC

[camel-karavan] 02/02: First operator that works

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 eeca4b631a54bbb90d377d13045a042020e83696
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Oct 13 13:18:36 2022 -0400

    First operator that works
---
 change_version.sh                                  |   3 +
 karavan-cloud/README.md                            |   4 +-
 karavan-cloud/karavan-namespace.yaml               |   4 +
 karavan-cloud/minikube/karavan-acl.yaml            |   2 +-
 karavan-cloud/openshift/karavan-acl.yaml           |   2 +-
 karavan-operator/Makefile                          |  47 +++++++++
 karavan-operator/PROJECT                           |   9 +-
 karavan-operator/pom.xml                           |  28 +++++-
 karavan-operator/resources/deployments-role.yaml   |  29 +++---
 karavan-operator/resources/karavan.yaml            |   2 +
 .../apache/camel/karavan/AbstractResources.java    |  54 ----------
 .../java/org/apache/camel/karavan/Constants.java   |  21 ++--
 .../java/org/apache/camel/karavan/Karavan.java     |  13 ++-
 .../apache/camel/karavan/KaravanController.java    |  38 -------
 .../apache/camel/karavan/KaravanDeployment.java    | 101 +++++++++++++++++++
 .../org/apache/camel/karavan/KaravanPvcData.java   |  53 ++++++++++
 .../org/apache/camel/karavan/KaravanPvcJbang.java  |  51 ++++++++++
 .../apache/camel/karavan/KaravanPvcM2Cache.java    |  52 ++++++++++
 .../apache/camel/karavan/KaravanReconciler.java    |  77 +++++++++++++++
 .../org/apache/camel/karavan/KaravanResources.java | 109 ---------------------
 .../java/org/apache/camel/karavan/KaravanRole.java |  53 ++++++++++
 .../apache/camel/karavan/KaravanRoleBinding.java   |  48 +++++++++
 .../camel/karavan/KaravanRoleBindingView.java      |  48 +++++++++
 .../org/apache/camel/karavan/KaravanService.java   |  44 +++++++++
 .../camel/karavan/KaravanServiceAccount.java       |  44 +++++++++
 .../java/org/apache/camel/karavan/KaravanSpec.java |  19 +++-
 .../org/apache/camel/karavan/KaravanStatus.java    |  13 +++
 .../camel/karavan/KaravanTektonPipeline.java       |  75 ++++++++++++++
 .../apache/camel/karavan/KaravanTektonTask.java    |  95 ++++++++++++++++++
 .../src/main/resources/application.properties      |  10 +-
 .../resources/karavan-quarkus-builder-script.sh    |  50 ++++++++++
 31 files changed, 945 insertions(+), 253 deletions(-)

diff --git a/change_version.sh b/change_version.sh
index 08d4ef9..43562c3 100755
--- a/change_version.sh
+++ b/change_version.sh
@@ -6,6 +6,9 @@ mvn versions:set -DnewVersion=$1 -f karavan-generator
 # echo "Set Application pom.xml version: $1";
 mvn versions:set -DnewVersion=$1 -f karavan-app
 
+# echo "Set Operator pom.xml version: $1";
+mvn versions:set -DnewVersion=$1 -f karavan-operator
+
 # echo "Set Core package.json extension version: $1";
 yarn version --new-version $1 --no-commit --no-git-tag-version --cwd karavan-core
 
diff --git a/karavan-cloud/README.md b/karavan-cloud/README.md
index b70b285..7b6d0e3 100644
--- a/karavan-cloud/README.md
+++ b/karavan-cloud/README.md
@@ -15,7 +15,7 @@
     ```
 2. Create namespace
     ```
-    oc apply -f openshift/karavan-namespace.yaml
+    oc apply -f karavan-namespace.yaml
     oc project karavan
     ```
 
@@ -47,7 +47,7 @@
     ```
 2. Create namespace
     ```
-    kubectl apply -f base/karavan-namespace.yaml
+    kubectl apply -f karavan-namespace.yaml
     ```
 3. Enable Registry
     ```
diff --git a/karavan-cloud/karavan-namespace.yaml b/karavan-cloud/karavan-namespace.yaml
new file mode 100644
index 0000000..768a8b5
--- /dev/null
+++ b/karavan-cloud/karavan-namespace.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: karavan
\ No newline at end of file
diff --git a/karavan-cloud/minikube/karavan-acl.yaml b/karavan-cloud/minikube/karavan-acl.yaml
index a102715..e060c80 100644
--- a/karavan-cloud/minikube/karavan-acl.yaml
+++ b/karavan-cloud/minikube/karavan-acl.yaml
@@ -78,7 +78,7 @@ subjects:
     name: karavan
     namespace: karavan
 ---
-# Pipeline shoulf have access to create rolebindings
+# Pipeline should have access to create rolebindings
 kind: Role
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
diff --git a/karavan-cloud/openshift/karavan-acl.yaml b/karavan-cloud/openshift/karavan-acl.yaml
index a102715..e060c80 100644
--- a/karavan-cloud/openshift/karavan-acl.yaml
+++ b/karavan-cloud/openshift/karavan-acl.yaml
@@ -78,7 +78,7 @@ subjects:
     name: karavan
     namespace: karavan
 ---
-# Pipeline shoulf have access to create rolebindings
+# Pipeline should have access to create rolebindings
 kind: Role
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
diff --git a/karavan-operator/Makefile b/karavan-operator/Makefile
index 584aa45..ebcc4ff 100644
--- a/karavan-operator/Makefile
+++ b/karavan-operator/Makefile
@@ -1,4 +1,36 @@
 
+VERSION ?= 3.18.5
+
+# 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")
+# To re-generate a bundle for other specific channels without changing the standard setup, you can:
+# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)
+# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable")
+ifneq ($(origin CHANNELS), undefined)
+BUNDLE_CHANNELS := --channels=$(CHANNELS)
+endif
+
+# DEFAULT_CHANNEL defines the default channel used in the bundle.
+# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
+# To re-generate a bundle for any other default channel without changing the default setup, you can:
+# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
+# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
+ifneq ($(origin DEFAULT_CHANNEL), undefined)
+BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
+endif
+BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
+
+# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
+# This variable is used to construct full image tags for bundle and catalog images.
+#
+# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
+# karavan.camel.apache.org/operator-test-bundle:$VERSION and karavan.camel.apache.org/operator-test-catalog:$VERSION.
+IMAGE_TAG_BASE ?= ghcr.io/apache/camel-karavan-operator
+
+# BUNDLE_IMG defines the image:tag used for the bundle.
+# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
+BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
+
 # Image URL to use all building/pushing image targets
 IMG ?= controller:latest
 
@@ -41,3 +73,18 @@ deploy: ## Deploy controller to the K8s cluster specified in ~/.kube/config.
 
 undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
 	kubectl delete -f target/kubernetes/kubernetes.yml
+
+##@Bundle
+.PHONY: bundle
+bundle:  ## Generate bundle manifests and metadata, then validate generated files.
+## marker
+	cat target/kubernetes/karavans.app.karavan.camel.apache.org-v1.yml target/kubernetes/kubernetes.yml | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
+	operator-sdk bundle validate ./bundle
+
+.PHONY: bundle-build
+bundle-build: ## Build the bundle image.
+	docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
+
+.PHONY: bundle-push
+bundle-push: ## Push the bundle image.
+	docker push $(BUNDLE_IMG)
diff --git a/karavan-operator/PROJECT b/karavan-operator/PROJECT
index 0a79c8b..e6aa751 100644
--- a/karavan-operator/PROJECT
+++ b/karavan-operator/PROJECT
@@ -1,13 +1,10 @@
-domain: karavan.camel.apache.org
+domain: apache.org
 layout:
 - quarkus.javaoperatorsdk.io/v1-alpha
-projectName: karavan-operator
+projectName: camel-karavan-operator
 resources:
 - api:
-    crdVersion: v1
-    namespaced: true
-  domain: karavan.camel.apache.org
-  group: app
+  group: camel
   kind: Karavan
   version: v1
 version: "3"
diff --git a/karavan-operator/pom.xml b/karavan-operator/pom.xml
index 86cdf7e..5feb7ae 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>0.0.1-SNAPSHOT</version>
+    <version>3.18.5</version>
     <packaging>jar</packaging>
     <properties>
         <compiler-plugin.version>3.8.1</compiler-plugin.version>
@@ -15,8 +15,8 @@
         <maven.compiler.target>11</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <quarkus-sdk.version>2.0.1</quarkus-sdk.version>
-        <quarkus.version>2.7.0.Final</quarkus.version>
+        <quarkus-sdk.version>4.0.3</quarkus-sdk.version>
+        <quarkus.version>2.13.0.Final</quarkus.version>
     </properties>
 
     <dependencyManagement>
@@ -28,6 +28,13 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
+            <dependency>
+                <groupId>io.quarkiverse.operatorsdk</groupId>
+                <artifactId>quarkus-operator-sdk-bom</artifactId>
+                <version>${quarkus-sdk.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
     <dependencies>
@@ -35,10 +42,23 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-arc</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>tekton-client</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.quarkiverse.operatorsdk</groupId>
             <artifactId>quarkus-operator-sdk</artifactId>
-            <version>${quarkus-sdk.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkiverse.operatorsdk</groupId>
+            <artifactId>quarkus-operator-sdk-bundle-generator</artifactId>
+        </dependency>
+        <!--  This dependency is needed only to ensure proper building order so that this module is build after the bundle generator extension -->
+        <dependency>
+            <groupId>io.quarkiverse.operatorsdk</groupId>
+            <artifactId>quarkus-operator-sdk-bundle-generator-deployment</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
diff --git a/karavan-operator/resources/deployments-role.yaml b/karavan-operator/resources/deployments-role.yaml
index af8e7e8..2ae9851 100644
--- a/karavan-operator/resources/deployments-role.yaml
+++ b/karavan-operator/resources/deployments-role.yaml
@@ -1,33 +1,28 @@
 apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
-  name: karavancontroller-deployment-role
+  name: karavan-operator-role
 rules:
-  - apiGroups:
-      - apps
-      - extensions
-    resources:
-      - deployments
-    verbs:
-      - get
-      - list
-      - watch
-      - create
-      - delete
-      - patch
-      - update
+  - apiGroups: [ "apps", "extensions" ]
+    resources: [ "serviceaccounts", "deployments", "services", "routes", "persistentvolumes", "persistentvolumeclaims"]
+    verbs: [ "*" ]
+  - apiGroups: [ "tekton.dev" ]
+    resources: [ "pipelinetasks", "pipelines" ]
+    verbs: [ "*" ]
+  - apiGroups: [ "rbac.authorization.k8s.io" ]
+    resources: [ "roles", "rolebindings" ]
+    verbs: [ "*" ]
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
     app.kubernetes.io/name: karavan-operator
-    app.kubernetes.io/version: 0.0.11
-  name: karavan-operator-deployment-controller
+  name: karavan-operator
 roleRef:
   kind: Role
   apiGroup: rbac.authorization.k8s.io
-  name: karavancontroller-deployment-role
+  name: karavan-operator-role
 subjects:
   - kind: ServiceAccount
     name: karavan-operator
\ No newline at end of file
diff --git a/karavan-operator/resources/karavan.yaml b/karavan-operator/resources/karavan.yaml
index 678ac1d..93abfbf 100644
--- a/karavan-operator/resources/karavan.yaml
+++ b/karavan-operator/resources/karavan.yaml
@@ -2,6 +2,7 @@ apiVersion: camel.apache.org/v1
 kind: Karavan
 metadata:
   name: karavan
+  namespace: karavan
   annotations:
     camel.apache.org/support.level: Preview
     camel.apache.org/version: 3.18.5
@@ -9,3 +10,4 @@ metadata:
 spec:
   instances: 1
   auth: public
+  nodePort: 30666
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/AbstractResources.java b/karavan-operator/src/main/java/org/apache/camel/karavan/AbstractResources.java
deleted file mode 100644
index d67a696..0000000
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/AbstractResources.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.apache.camel.karavan;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-
-import io.fabric8.kubernetes.api.model.OwnerReference;
-import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder;
-import io.fabric8.kubernetes.api.model.Service;
-import io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.fabric8.kubernetes.client.KubernetesClient;
-
-public abstract class AbstractResources {
-
-    private Karavan karavan;
-
-    @Inject
-    KubernetesClient client;
-
-    protected Optional<Deployment> checkDeploymentExists(String name){
-        return Optional.ofNullable(client.apps().deployments().inNamespace(client.getNamespace()).withName(name).get());
-    }
-
-    protected Optional<Service> checkServiceExists(String name) {
-        return Optional.ofNullable(client.services().inNamespace(client.getNamespace()).withName(name).get());
-    }
-
-    protected void deleteDeployment(String name){
-        client.apps().deployments().inNamespace(client.getNamespace()).withName(name).delete();
-    }
-
-    protected void deleteService(String name){
-        client.services().inNamespace(client.getNamespace()).withName(name).delete();
-    }
-
-    protected OwnerReference createOwnerReference(Karavan resource) {
-        final var metadata = resource.getMetadata();
-        return new OwnerReferenceBuilder()
-                .withUid(metadata.getUid())
-                .withApiVersion(resource.getApiVersion())
-                .withName(metadata.getName())
-                .withKind(resource.getKind())
-                .build();
-    }
-
-    public Karavan getKaravan() {
-        return karavan;
-    }
-
-    public void setKaravan(Karavan karavan) {
-        this.karavan = karavan;
-    }
-
-}
\ No newline at end of file
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/Constants.java b/karavan-operator/src/main/java/org/apache/camel/karavan/Constants.java
index a709ebe..fef10c5 100644
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/Constants.java
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/Constants.java
@@ -1,23 +1,20 @@
 package org.apache.camel.karavan;
 
-import java.util.Map;
-
 public final class Constants {
     public static final String CRD_GROUP = "camel.apache.org";
     public static final String CRD_VERSION = "v1";
     public static final String SHORT_NAME = "karavan";
     public static final String NAME = "karavan";
     public static final String PLURAL_NAME = "karavans";
-    public static final String MANAGED_BY_LABEL = "app.kubernetes.io/managed-by";
-    public static final String MANAGED_BY_VALUE = "karavan-operator";
-
-    public static final String KARAVAN_MODE = "KARAVAN_MODE";
 
-    public static final Map<String, String> DEFAULT_LABELS = Map.of(
-            "app.kubernetes.io/name", NAME,
-            "app.kubernetes.io/version", "latest",
-            "app.kubernetes.io/part-of",  NAME
-    );
+    public static final String SERVICEACCOUNT_KARAVAN = "karavan";
+    public static final String ROLE_KARAVAN = "karavan";
+    public static final String ROLEBINDING_KARAVAN = "karavan";
+    public static final String ROLEBINDING_KARAVAN_VIEW = "karavan-view";
+    public static final String PVC_DATA = "karavan-data";
+    public static final String PVC_M2_CACHE = "karavan-m2-cache";
+    public static final String PVC_JBANG = "karavan-jbang";
 
-    public static final String KARAVAN_IMAGE = "ghcr.io/apache/camel-karavan:latest";
+    public static final String PIPELINE_BUILD_QUARKUS = "karavan-pipeline-build-quarkus";
+    public static final String TASK_BUILD_QUARKUS = "karavan-task-build-quarkus";
 }
\ No newline at end of file
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/Karavan.java b/karavan-operator/src/main/java/org/apache/camel/karavan/Karavan.java
index 70df643..ac95084 100644
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/Karavan.java
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/Karavan.java
@@ -8,5 +8,16 @@ import io.fabric8.kubernetes.model.annotation.*;
 @Version(Constants.CRD_VERSION)
 @ShortNames(Constants.SHORT_NAME)
 @Plural(Constants.PLURAL_NAME)
-public class Karavan extends CustomResource<KaravanSpec, KaravanStatus> implements Namespaced {}
+public class Karavan extends CustomResource<KaravanSpec, KaravanStatus> implements Namespaced {
+
+    @Override
+    protected KaravanSpec initSpec() {
+        return new KaravanSpec();
+    }
+
+    @Override
+    protected KaravanStatus initStatus() {
+        return new KaravanStatus();
+    }
+}
 
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanController.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanController.java
deleted file mode 100644
index d9f337f..0000000
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanController.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.apache.camel.karavan;
-
-import javax.inject.Inject;
-
-import io.fabric8.kubernetes.client.KubernetesClient;
-import io.javaoperatorsdk.operator.api.Context;
-import io.javaoperatorsdk.operator.api.Controller;
-import io.javaoperatorsdk.operator.api.ResourceController;
-import io.javaoperatorsdk.operator.api.UpdateControl;
-import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
-
-@Controller(namespaces = Controller.WATCH_CURRENT_NAMESPACE)
-public class KaravanController implements ResourceController<Karavan> {
-
-    @Inject
-    KaravanResources karavanResources;
-
-    @Inject
-    KubernetesClient client;
-
-    public KaravanController(KubernetesClient client) {
-        this.client = client;
-    }
-
-    @Override
-    public void init(EventSourceManager eventSourceManager) {
-    }
-
-    @Override
-    public UpdateControl<Karavan> createOrUpdateResource(Karavan resource, Context<Karavan> context) {
-        karavanResources.createResources(resource);
-
-        resource.setStatus(new KaravanStatus());
-
-        return UpdateControl.updateStatusSubResource(resource);
-    }
-}
-
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanDeployment.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanDeployment.java
new file mode 100644
index 0000000..979775d
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanDeployment.java
@@ -0,0 +1,101 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
+import io.fabric8.kubernetes.api.model.ObjectFieldSelectorBuilder;
+import io.fabric8.kubernetes.api.model.OwnerReference;
+import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
+import io.fabric8.kubernetes.api.model.Quantity;
+import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder;
+import io.fabric8.kubernetes.api.model.VolumeBuilder;
+import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+public class KaravanDeployment extends CRUDKubernetesDependentResource<Deployment, Karavan> {
+
+    @ConfigProperty(name = "karavan.version")
+    String version;
+
+    @ConfigProperty(name = "karavan.image")
+    String image;
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanDeployment() {
+        super(Deployment.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Deployment desired(Karavan karavan, Context<Karavan> context) {
+        return new DeploymentBuilder()
+                .withNewMetadata()
+                .withName(Constants.NAME)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.NAME, Map.of("app.kubernetes.io/runtime", "quarkus")))
+                .withOwnerReferences(this.createOwnerReference(karavan))
+                .endMetadata()
+
+                .withNewSpec()
+                .withReplicas(karavan.getSpec().getInstances())
+                .withNewSelector()
+                .addToMatchLabels(Map.of("app", Constants.NAME))
+                .endSelector()
+
+                .withNewTemplate()
+                .withNewMetadata()
+                .addToLabels(Map.of("app", Constants.NAME))
+                .endMetadata()
+
+                .withNewSpec()
+                    .addNewContainer()
+                        .withName(Constants.NAME)
+//                        .withImage(getImageName(karavan))
+                        .withImage("ghcr.io/apache/camel-karavan:3.18.4") // TODO: set correct version after
+                        .withImagePullPolicy("Always")
+                        .addNewEnv()
+                            .withName("KUBERNETES_NAMESPACE")
+                            .withValueFrom(new EnvVarSourceBuilder().withFieldRef(new ObjectFieldSelectorBuilder().withFieldPath("metadata.namespace").build()).build())
+                        .endEnv()
+                        .addNewPort()
+                            .withContainerPort(8080)
+                            .withName(Constants.NAME)
+                        .endPort()
+                        .withResources(new ResourceRequirementsBuilder().withRequests(Map.of("memory", new Quantity("2048Mi"))).build())
+                        .withVolumeMounts(new VolumeMountBuilder().withName("karavan-data").withMountPath("/deployments/karavan-data").build())
+                    .endContainer()
+                .withServiceAccount(Constants.NAME)
+                .withVolumes(new VolumeBuilder().withName("karavan-data").withPersistentVolumeClaim(new PersistentVolumeClaimVolumeSource("karavan-data", false)).build())
+                .endSpec()
+                .endTemplate()
+                .endSpec()
+                .build();
+    }
+
+    private String getImageName(Karavan karavan) {
+        String auth = karavan.getSpec().getAuth();
+        switch (auth){
+            case "oidc": return image + "-oidc:" + version;
+            case "basic": return image + "-basic:" + version;
+            default: return image + ":" + version;
+        }
+    }
+
+    private OwnerReference createOwnerReference(Karavan resource) {
+        final var metadata = resource.getMetadata();
+        return new OwnerReferenceBuilder()
+                .withUid(metadata.getUid())
+                .withApiVersion(resource.getApiVersion())
+                .withName(metadata.getName())
+                .withKind(resource.getKind())
+                .build();
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcData.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcData.java
new file mode 100644
index 0000000..c66c56b
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcData.java
@@ -0,0 +1,53 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
+import io.fabric8.kubernetes.api.model.Quantity;
+import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder;
+import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+
+public class KaravanPvcData extends CRUDKubernetesDependentResource<PersistentVolumeClaim, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanPvcData() {
+        super(PersistentVolumeClaim.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public PersistentVolumeClaim desired(Karavan karavan, Context<Karavan> context) {
+        return new PersistentVolumeClaimBuilder()
+                .withNewMetadata()
+                .withName(Constants.PVC_DATA)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.PVC_DATA, Map.of()))
+                .endMetadata()
+                .withNewSpec()
+                .withResources(new ResourceRequirementsBuilder().withRequests(Map.of("storage", new Quantity("10Gi"))).build())
+                .withVolumeMode("Filesystem")
+                .withAccessModes("ReadWriteOnce")
+                .endSpec()
+                .build();
+    }
+
+    @Override
+    public ReconcileResult<PersistentVolumeClaim> reconcile(Karavan karavan, Context<Karavan> context) {
+        PersistentVolumeClaim pvc = getKubernetesClient().persistentVolumeClaims().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.PVC_DATA).get();
+        if (pvc == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(pvc);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcJbang.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcJbang.java
new file mode 100644
index 0000000..16d79dd
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcJbang.java
@@ -0,0 +1,51 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
+import io.fabric8.kubernetes.api.model.Quantity;
+import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+
+public class KaravanPvcJbang extends CRUDKubernetesDependentResource<PersistentVolumeClaim, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanPvcJbang() {
+        super(PersistentVolumeClaim.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public PersistentVolumeClaim desired(Karavan karavan, Context<Karavan> context) {
+        return new PersistentVolumeClaimBuilder()
+                .withNewMetadata()
+                .withName(Constants.PVC_JBANG)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.PVC_JBANG, Map.of()))
+                .endMetadata()
+                .withNewSpec()
+                .withResources(new ResourceRequirementsBuilder().withRequests(Map.of("storage", new Quantity("2Gi"))).build())
+                .withVolumeMode("Filesystem")
+                .withAccessModes("ReadWriteOnce")
+                .endSpec()
+                .build();
+    }
+
+    public ReconcileResult<PersistentVolumeClaim> reconcile(Karavan karavan, Context<Karavan> context) {
+        PersistentVolumeClaim pvc = getKubernetesClient().persistentVolumeClaims().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.PVC_JBANG).get();
+        if (pvc == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(pvc);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcM2Cache.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcM2Cache.java
new file mode 100644
index 0000000..c2d6c08
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanPvcM2Cache.java
@@ -0,0 +1,52 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
+import io.fabric8.kubernetes.api.model.Quantity;
+import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+
+public class KaravanPvcM2Cache extends CRUDKubernetesDependentResource<PersistentVolumeClaim, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanPvcM2Cache() {
+        super(PersistentVolumeClaim.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public PersistentVolumeClaim desired(Karavan karavan, Context<Karavan> context) {
+        return new PersistentVolumeClaimBuilder()
+                .withNewMetadata()
+                .withName(Constants.PVC_M2_CACHE)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.PVC_M2_CACHE, Map.of()))
+                .endMetadata()
+                .withNewSpec()
+                .withResources(new ResourceRequirementsBuilder().withRequests(Map.of("storage", new Quantity("10Gi"))).build())
+                .withVolumeMode("Filesystem")
+                .withAccessModes("ReadWriteOnce")
+                .endSpec()
+                .build();
+    }
+
+    @Override
+    public ReconcileResult<PersistentVolumeClaim> reconcile(Karavan karavan, Context<Karavan> context) {
+        PersistentVolumeClaim pvc = getKubernetesClient().persistentVolumeClaims().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.PVC_M2_CACHE).get();
+        if (pvc == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(pvc);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanReconciler.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanReconciler.java
new file mode 100644
index 0000000..50e85c0
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanReconciler.java
@@ -0,0 +1,77 @@
+package org.apache.camel.karavan;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.ContextInitializer;
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_ALL_NAMESPACES;
+
+@ControllerConfiguration(namespaces = WATCH_ALL_NAMESPACES, name = "karavan", dependents = {
+        @Dependent(type = KaravanServiceAccount.class),
+        @Dependent(type = KaravanRole.class),
+        @Dependent(type = KaravanRoleBinding.class),
+        @Dependent(type = KaravanRoleBindingView.class),
+        @Dependent(type = KaravanPvcData.class, name = Constants.PVC_DATA),
+        @Dependent(type = KaravanPvcM2Cache.class, name = Constants.PVC_M2_CACHE),
+        @Dependent(type = KaravanPvcJbang.class, name = Constants.PVC_JBANG),
+        @Dependent(type = KaravanTektonTask.class),
+        @Dependent(type = KaravanTektonPipeline.class),
+        @Dependent(type = KaravanDeployment.class),
+        @Dependent(name = "service", type = KaravanService.class),
+//        @Dependent(type = IngressDependent.class, dependsOn = "service", readyPostcondition = IngressDependent.class)
+})
+public class KaravanReconciler implements Reconciler<Karavan>, ContextInitializer<Karavan> {
+
+    static final Logger log = LoggerFactory.getLogger(KaravanReconciler.class);
+
+    @ConfigProperty(name = "karavan.version")
+    String version;
+
+    @Override
+    public void initContext(Karavan karavan, Context<Karavan> context) {
+
+    }
+
+    @Override
+    public UpdateControl<Karavan> reconcile(Karavan karavan, Context<Karavan> context) throws Exception {
+        final var name = karavan.getMetadata().getName();
+        final var namespace = karavan.getMetadata().getNamespace();
+        // retrieve the workflow reconciliation result and re-schedule if we have dependents that are not yet ready
+//        return context.managedDependentResourceContext().getWorkflowReconcileResult()
+//                .map(wrs -> {
+//                    if (wrs.allDependentResourcesReady()) {
+////                        final var url = IngressDependent.getExposedURL(
+////                                context.getSecondaryResource(Ingress.class).orElseThrow());
+//                        log.info("App {} is exposed and ready to be used at {}", name, namespace);
+                        karavan.setStatus(new KaravanStatus(KaravanStatus.State.READY));
+                        return UpdateControl.updateStatus(karavan);
+//                    } else {
+//                        final var duration = Duration.ofSeconds(5);
+//                        log.info("App {} is not ready yet, rescheduling reconciliation after {}s", name, duration.toSeconds());
+//                        return UpdateControl.<Karavan> noUpdate().rescheduleAfter(duration);
+//                    }
+//                }).orElseThrow();
+    }
+
+    protected Map<String, String> getLabels(String name, Map<String, String> labels) {
+        Map<String, String> result = new HashMap<>(Map.of(
+                "app", name,
+                "app.kubernetes.io/name", name,
+                "app.kubernetes.io/version", version,
+                "app.kubernetes.io/part-of",  Constants.NAME
+        ));
+        result.putAll(labels);
+        return result;
+    }
+}
+
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanResources.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanResources.java
deleted file mode 100644
index f0ae627..0000000
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanResources.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.apache.camel.karavan;
-
-import java.util.Map;
-import java.util.Optional;
-
-import javax.enterprise.context.ApplicationScoped;
-
-import io.fabric8.kubernetes.api.model.IntOrString;
-import io.fabric8.kubernetes.api.model.Service;
-import io.fabric8.kubernetes.api.model.ServiceBuilder;
-import io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
-
-import static org.apache.camel.karavan.Constants.KARAVAN_IMAGE;
-
-@ApplicationScoped
-public class KaravanResources extends AbstractResources {
-
-
-    public void createResources(Karavan karavan) {
-        this.setKaravan(karavan);
-        createFrontendDeployment(karavan);
-        createFrontendService();
-    }
-
-    private void createFrontendDeployment(Karavan karavan) {
-        Optional<Deployment> potentialDeployment = checkDeploymentExists(Constants.NAME);
-
-        if (potentialDeployment.isEmpty()) {
-            Deployment deployment1 = new DeploymentBuilder()
-                    .withNewMetadata()
-                    .withName(Constants.NAME)
-                    .withLabels(Constants.DEFAULT_LABELS)
-                    .withOwnerReferences(this.createOwnerReference(this.getKaravan()))
-                    .endMetadata()
-
-                    .withNewSpec()
-                    .withReplicas(karavan.getSpec().getInstances())
-                    .withNewSelector()
-                    .addToMatchLabels(Constants.DEFAULT_LABELS)
-                    .endSelector()
-
-                    .withNewTemplate()
-                    .withNewMetadata()
-                    .addToLabels(Constants.DEFAULT_LABELS)
-                    .endMetadata()
-
-                    .withNewSpec()
-                        .addNewContainer()
-                            .withName(Constants.NAME)
-                            .withImage(KARAVAN_IMAGE)
-                            .withImagePullPolicy("Always")
-                            .addNewEnv()
-                                .withName(Constants.KARAVAN_MODE)
-                                .withValue(karavan.getSpec().getMode())
-                            .endEnv()
-                            .addNewPort()
-                                .withContainerPort(8080)
-                                .withName(Constants.NAME)
-                            .endPort()
-                        .endContainer()
-//                    .withServiceAccount(Constants.NAME)
-                    .endSpec()
-                    .endTemplate()
-                    .endSpec()
-                    .build();
-            client.apps().deployments().inNamespace(client.getNamespace()).create(deployment1);
-        } else { //We are maybe dealing with an update
-            //EnvVar envar = potentialDeployment.get().getSpec().getTemplate().getSpec().getContainers().get(0).
-            client.apps().deployments().inNamespace(client.getNamespace())
-                    .withName(Constants.NAME).edit(d -> new DeploymentBuilder(d)
-                            .editSpec()
-                            .editTemplate().editSpec()
-                            .editFirstContainer()
-                            .editFirstEnv()
-                            .withValue(karavan.getSpec().getMode())
-                            .endEnv()
-                            .endContainer()
-                            .endSpec()
-                            .endTemplate()
-                            .endSpec()
-                            .build());
-        }
-    }
-
-
-    private void createFrontendService() {
-        if (checkServiceExists(Constants.NAME).isEmpty()) {
-            Service service = new ServiceBuilder()
-                    .withNewMetadata()
-                        .withName(Constants.NAME)
-                        .withLabels(Constants.DEFAULT_LABELS)
-                    .endMetadata()
-                    .withNewSpec()
-                        .withType("NodePort")
-                        .addNewPort()
-                            .withName(Constants.NAME)
-                            .withPort(80)
-                            .withTargetPort(new IntOrString(8080))
-                            .withNodePort(31171)
-                            .withProtocol("TCP")
-                        .endPort()
-                        .withSelector(Constants.DEFAULT_LABELS)
-                    .endSpec()
-                    .build();
-            client.services().inNamespace(client.getNamespace()).create(service);
-        }
-    }
-}
\ No newline at end of file
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRole.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRole.java
new file mode 100644
index 0000000..7280052
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRole.java
@@ -0,0 +1,53 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.rbac.PolicyRuleBuilder;
+import io.fabric8.kubernetes.api.model.rbac.Role;
+import io.fabric8.kubernetes.api.model.rbac.RoleBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Map;
+
+
+public class KaravanRole extends CRUDKubernetesDependentResource<Role, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanRole() {
+        super(Role.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Role desired(Karavan karavan, Context<Karavan> context) {
+        return new RoleBuilder()
+                .withNewMetadata()
+                .withName(Constants.ROLE_KARAVAN)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.ROLE_KARAVAN, Map.of()))
+                .endMetadata()
+                .withRules(
+                        new PolicyRuleBuilder().withApiGroups("").withResources("secrets", "configmaps").withVerbs("get", "list").build(),
+                        new PolicyRuleBuilder().withApiGroups("").withResources("persistentvolumes", "persistentvolumeclaims").withVerbs("get", "list", "watch").build(),
+                        new PolicyRuleBuilder().withApiGroups("tekton.dev").withResources("pipelineruns").withVerbs("*").build(),
+                        new PolicyRuleBuilder().withApiGroups("", "apps").withResources("deployments", "services", "routes", "replicationcontrollers").withVerbs("*").build()
+                        )
+                .build();
+    }
+
+    @Override
+    public ReconcileResult<Role> reconcile(Karavan karavan, Context<Karavan> context) {
+        Role role = getKubernetesClient().rbac().roles().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.ROLE_KARAVAN).get();
+        if (role == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(role);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRoleBinding.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRoleBinding.java
new file mode 100644
index 0000000..b7bcafa
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRoleBinding.java
@@ -0,0 +1,48 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
+import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
+import io.fabric8.kubernetes.api.model.rbac.Subject;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+
+public class KaravanRoleBinding extends CRUDKubernetesDependentResource<RoleBinding, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanRoleBinding() {
+        super(RoleBinding.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public RoleBinding desired(Karavan karavan, Context<Karavan> context) {
+        return new RoleBindingBuilder()
+                .withNewMetadata()
+                .withName(Constants.ROLEBINDING_KARAVAN)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.ROLEBINDING_KARAVAN, Map.of()))
+                .endMetadata()
+                .withNewRoleRef("rbac.authorization.k8s.io", "Role", Constants.ROLE_KARAVAN)
+                .withSubjects(new Subject("", "ServiceAccount", Constants.SERVICEACCOUNT_KARAVAN, karavan.getMetadata().getNamespace()))
+                .build();
+    }
+
+    @Override
+    public ReconcileResult<RoleBinding> reconcile(Karavan karavan, Context<Karavan> context) {
+        RoleBinding role = getKubernetesClient().rbac().roleBindings().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.ROLEBINDING_KARAVAN).get();
+        if (role == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(role);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRoleBindingView.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRoleBindingView.java
new file mode 100644
index 0000000..e4147f5
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanRoleBindingView.java
@@ -0,0 +1,48 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
+import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
+import io.fabric8.kubernetes.api.model.rbac.Subject;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+
+public class KaravanRoleBindingView extends CRUDKubernetesDependentResource<RoleBinding, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanRoleBindingView() {
+        super(RoleBinding.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public RoleBinding desired(Karavan karavan, Context<Karavan> context) {
+        return new RoleBindingBuilder()
+                .withNewMetadata()
+                .withName(Constants.ROLEBINDING_KARAVAN_VIEW)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.ROLEBINDING_KARAVAN_VIEW, Map.of()))
+                .endMetadata()
+                .withNewRoleRef("rbac.authorization.k8s.io", "ClusterRole", "view")
+                .withSubjects(new Subject("", "ServiceAccount", Constants.SERVICEACCOUNT_KARAVAN, karavan.getMetadata().getNamespace()))
+                .build();
+    }
+
+    @Override
+    public ReconcileResult<RoleBinding> reconcile(Karavan karavan, Context<Karavan> context) {
+        RoleBinding role = getKubernetesClient().rbac().roleBindings().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.ROLEBINDING_KARAVAN_VIEW).get();
+        if (role == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(role);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanService.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanService.java
new file mode 100644
index 0000000..0214a8e
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanService.java
@@ -0,0 +1,44 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.IntOrString;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+
+public class KaravanService extends CRUDKubernetesDependentResource<Service, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanService() {
+        super(Service.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Service desired(Karavan karavan, Context<Karavan> context) {
+        return new ServiceBuilder()
+                .withNewMetadata()
+                .withName(Constants.NAME)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.NAME, Map.of()))
+                .endMetadata()
+                .withNewSpec()
+                .withType("NodePort")
+                .addNewPort()
+                .withName(Constants.NAME)
+                .withPort(80)
+                .withTargetPort(new IntOrString(8080))
+                .withNodePort(karavan.getSpec().getNodePort())
+                .withProtocol("TCP")
+                .endPort()
+                .withSelector(Map.of("app", Constants.NAME))
+                .endSpec()
+                .build();
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanServiceAccount.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanServiceAccount.java
new file mode 100644
index 0000000..a5ed361
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanServiceAccount.java
@@ -0,0 +1,44 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.ServiceAccount;
+import io.fabric8.kubernetes.api.model.ServiceAccountBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+public class KaravanServiceAccount extends CRUDKubernetesDependentResource<ServiceAccount, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    public KaravanServiceAccount() {
+        super(ServiceAccount.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public ServiceAccount desired(Karavan karavan, Context<Karavan> context) {
+        return new ServiceAccountBuilder()
+                .withNewMetadata()
+                .withName(Constants.SERVICEACCOUNT_KARAVAN)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.SERVICEACCOUNT_KARAVAN, Map.of()))
+                .endMetadata()
+                .build();
+    }
+
+    @Override
+    public ReconcileResult<ServiceAccount> reconcile(Karavan karavan, Context<Karavan> context) {
+        ServiceAccount sa = getKubernetesClient().serviceAccounts().inNamespace(karavan.getMetadata().getNamespace()).withName(Constants.SERVICEACCOUNT_KARAVAN).get();
+        if (sa == null) {
+            var desired = desired(karavan, context);
+            var createdResource = handleCreate(desired, karavan, context);
+            return ReconcileResult.resourceCreated(createdResource);
+        } else {
+            return ReconcileResult.noOperation(sa);
+        }
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanSpec.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanSpec.java
index 970aeee..8277a91 100644
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanSpec.java
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanSpec.java
@@ -3,7 +3,8 @@ package org.apache.camel.karavan;
 public class KaravanSpec {
 
     private int instances;
-    private String mode;
+    private String auth;
+    private int nodePort;
 
     public int getInstances() {
         return instances;
@@ -13,11 +14,19 @@ public class KaravanSpec {
         this.instances = instances;
     }
 
-    public String getMode() {
-        return mode;
+    public String getAuth() {
+        return auth;
     }
 
-    public void setMode(String mode) {
-        this.mode = mode;
+    public void setAuth(String auth) {
+        this.auth = auth;
+    }
+
+    public int getNodePort() {
+        return nodePort;
+    }
+
+    public void setNodePort(int nodePort) {
+        this.nodePort = nodePort;
     }
 }
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanStatus.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanStatus.java
index e287550..980dd57 100644
--- a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanStatus.java
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanStatus.java
@@ -12,6 +12,19 @@ public class KaravanStatus {
     private boolean error;
     private String message;
 
+    public KaravanStatus() {
+    }
+
+    public KaravanStatus(State state) {
+        this.state = state;
+    }
+
+    public KaravanStatus(State state, boolean error, String message) {
+        this.state = state;
+        this.error = error;
+        this.message = message;
+    }
+
     public State getState() {
         return state;
     }
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanTektonPipeline.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanTektonPipeline.java
new file mode 100644
index 0000000..739653b
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanTektonPipeline.java
@@ -0,0 +1,75 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.EnvVarBuilder;
+import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.Param;
+import io.fabric8.tekton.pipeline.v1beta1.ParamBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.ParamSpecBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.Pipeline;
+import io.fabric8.tekton.pipeline.v1beta1.PipelineBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.PipelineTask;
+import io.fabric8.tekton.pipeline.v1beta1.PipelineTaskBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.PipelineWorkspaceDeclaration;
+import io.fabric8.tekton.pipeline.v1beta1.StepBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.Task;
+import io.fabric8.tekton.pipeline.v1beta1.TaskBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.TaskRef;
+import io.fabric8.tekton.pipeline.v1beta1.TaskRefBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.WorkspaceDeclaration;
+import io.fabric8.tekton.pipeline.v1beta1.WorkspacePipelineTaskBinding;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import javax.inject.Inject;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+public class KaravanTektonPipeline extends CRUDKubernetesDependentResource<Pipeline, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    @ConfigProperty(name = "karavan.version")
+    String version;
+
+    @ConfigProperty(name = "karavan.quarkus-build-image")
+    String image;
+
+    public KaravanTektonPipeline() {
+        super(Pipeline.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Pipeline desired(Karavan karavan, Context<Karavan> context) {
+        return new PipelineBuilder()
+                .withNewMetadata()
+                .withName(Constants.PIPELINE_BUILD_QUARKUS)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.PIPELINE_BUILD_QUARKUS, Map.of()))
+                .endMetadata()
+                .withNewSpec()
+                .withParams(new ParamSpecBuilder().withName("PROJECT_ID").withType("string").withDescription("ProjectId").build())
+                .withTasks(
+                        new PipelineTaskBuilder().withName(Constants.TASK_BUILD_QUARKUS)
+                                .withParams(new ParamBuilder().withName("project").withNewValue("$(params.PROJECT_ID)").build())
+                                .withTaskRef(new TaskRefBuilder().withKind("Task").withName(Constants.TASK_BUILD_QUARKUS).build())
+                                .withWorkspaces(
+                                        new WorkspacePipelineTaskBinding("m2-cache", "", "m2-cache"),
+                                        new WorkspacePipelineTaskBinding("jbang-cache", "", "jbang-cache")
+                                )
+                                .build()
+                )
+                .withWorkspaces(
+                        new PipelineWorkspaceDeclaration("Maven Cache", "m2-cache", false),
+                        new PipelineWorkspaceDeclaration("JBang Cache", "jbang-cache", false)
+                )
+                .endSpec()
+                .build();
+    }
+}
diff --git a/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanTektonTask.java b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanTektonTask.java
new file mode 100644
index 0000000..232c360
--- /dev/null
+++ b/karavan-operator/src/main/java/org/apache/camel/karavan/KaravanTektonTask.java
@@ -0,0 +1,95 @@
+package org.apache.camel.karavan;
+
+import io.fabric8.kubernetes.api.model.EnvVarBuilder;
+import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.ArrayOrString;
+import io.fabric8.tekton.pipeline.v1beta1.ParamSpec;
+import io.fabric8.tekton.pipeline.v1beta1.ParamSpecBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.StepBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.Task;
+import io.fabric8.tekton.pipeline.v1beta1.TaskBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.WorkspaceDeclaration;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import javax.inject.Inject;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+public class KaravanTektonTask extends CRUDKubernetesDependentResource<Task, Karavan> {
+
+    @Inject
+    KaravanReconciler karavanReconciler;
+
+    @ConfigProperty(name = "karavan.version")
+    String version;
+
+    @ConfigProperty(name = "karavan.quarkus-build-image")
+    String image;
+
+    public KaravanTektonTask() {
+        super(Task.class);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Task desired(Karavan karavan, Context<Karavan> context) {
+        String script = getScript();
+        return new TaskBuilder()
+                .withNewMetadata()
+                .withName(Constants.TASK_BUILD_QUARKUS)
+                .withNamespace(karavan.getMetadata().getNamespace())
+                .withLabels(karavanReconciler.getLabels(Constants.TASK_BUILD_QUARKUS, Map.of()))
+                .endMetadata()
+                .withNewSpec()
+                .withParams(new ParamSpecBuilder().withName("project").withType("string").withDescription("ProjectId").build())
+                .withSteps(
+                        new StepBuilder().withName("karavan-build-deploy")
+                                .withScript(script)
+                                .withImage(image + ":" + version)
+                                .withEnv(
+                                        new EnvVarBuilder().withName("PROJECTS_GIT_REPOSITORY").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("projects-git-repository").and().build()).build(),
+                                        new EnvVarBuilder().withName("PROJECTS_GIT_USERNAME").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("projects-git-username").and().build()).build(),
+                                        new EnvVarBuilder().withName("PROJECTS_GIT_PASSWORD").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("projects-git-password").and().build()).build(),
+                                        new EnvVarBuilder().withName("PROJECTS_GIT_MAIN").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("projects-git-main").and().build()).build(),
+                                        new EnvVarBuilder().withName("KAMELETS_GIT_REPOSITORY").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("kamelets-git-repository").and().build()).build(),
+                                        new EnvVarBuilder().withName("KAMELETS_GIT_USERNAME").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("kamelets-git-username").and().build()).build(),
+                                        new EnvVarBuilder().withName("KAMELETS_GIT_PASSWORD").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("kamelets-git-password").and().build()).build(),
+                                        new EnvVarBuilder().withName("KAMELETS_GIT_MAIN").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("kamelets-git-main").and().build()).build(),
+                                        new EnvVarBuilder().withName("IMAGE_REGISTRY").withValueFrom(
+                                                new EnvVarSourceBuilder().withNewSecretKeyRef().withName("karavan").withKey("image-registry").and().build()).build()
+                                )
+                                .build()
+                )
+                .withWorkspaces(
+                        new WorkspaceDeclaration("Maven Cache", "/root/.m2", "m2-cache", false, false),
+                        new WorkspaceDeclaration("JBang Cache", "/jbang/.jbang/cache", "jbang-cache", false, false)
+                )
+                .endSpec()
+                .build();
+    }
+
+    protected String getScript() {
+        try {
+            InputStream inputStream = KaravanTektonTask.class.getResourceAsStream("/karavan-quarkus-builder-script.sh");
+            String data = new BufferedReader(new InputStreamReader(inputStream))
+                    .lines().collect(Collectors.joining(System.getProperty("line.separator")));
+            return data;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/karavan-operator/src/main/resources/application.properties b/karavan-operator/src/main/resources/application.properties
index 2f80612..4d1ad1f 100644
--- a/karavan-operator/src/main/resources/application.properties
+++ b/karavan-operator/src/main/resources/application.properties
@@ -1,9 +1,13 @@
-quarkus.container-image.build=true
 # set to true to automatically apply CRDs to the cluster when they get regenerated
 quarkus.operator-sdk.crd.apply=true
 quarkus.operator-sdk.crd.validate=false
 
+quarkus.container-image.build=true
 quarkus.container-image.builder=jib
 quarkus.container-image.group=apache
-quarkus.container-image.name=camel-karavan
-quarkus.container-image.tag=latest
\ No newline at end of file
+quarkus.container-image.name=camel-karavan-operator
+quarkus.container-image.tag=3.18.5
+
+karavan.version=3.18.5
+karavan.image=ghcr.io/apache/camel-karavan
+karavan.quarkus-build-image=ghcr.io/apache/camel-karavan-builder
diff --git a/karavan-operator/src/main/resources/karavan-quarkus-builder-script.sh b/karavan-operator/src/main/resources/karavan-quarkus-builder-script.sh
new file mode 100644
index 0000000..4b15ea5
--- /dev/null
+++ b/karavan-operator/src/main/resources/karavan-quarkus-builder-script.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+KAMELETS_DIR="/kamelets"
+
+if  [[ $KAMELETS_GIT_REPOSITORY == https* ]] ;
+then
+    replacer=https://$KAMELETS_GIT_PASSWORD@
+    prefix=https://
+    url="${KAMELETS_GIT_REPOSITORY/$prefix/$replacer}"
+    git clone --depth 1 --branch ${KAMELETS_GIT_MAIN} $url ${KAMELETS_DIR}
+else
+    git clone --depth 1 --branch ${KAMELETS_GIT_MAIN} ${KAMELETS_GIT_REPOSITORY} ${KAMELETS_DIR}
+fi
+
+CHECKOUT_DIR="/scripts"
+
+if  [[ $PROJECTS_GIT_REPOSITORY == https* ]] ;
+then
+    replacer=https://$PROJECTS_GIT_PASSWORD@
+    prefix=https://
+    url="${PROJECTS_GIT_REPOSITORY/$prefix/$replacer}"
+    git clone --depth 1 --branch ${PROJECTS_GIT_MAIN} $url ${CHECKOUT_DIR}
+else
+    git clone --depth 1 --branch ${PROJECTS_GIT_MAIN} ${PROJECTS_GIT_REPOSITORY} ${CHECKOUT_DIR}
+fi
+
+cd ${CHECKOUT_DIR}/$(inputs.params.project)
+
+entrypoint -Dcamel.jbang.version=3.18.2 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+
+export LAST_COMMIT=$(git rev-parse --short HEAD)
+export DATE=$(date '+%Y%m%d%H%M%S')
+export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
+export NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
+
+/opt/mvnd/bin/mvnd package \
+  -Dquarkus.container-image.build=true \
+  -Dquarkus.container-image.push=true \
+  -Dquarkus.container-image.insecure=true \
+  -Dquarkus.container-image.username=sa \
+  -Dquarkus.container-image.password=${TOKEN} \
+  -Dquarkus.container-image.registry=${IMAGE_REGISTRY} \
+  -Dquarkus.container-image.builder=jib \
+  -Dquarkus.kubernetes-client.master-url=kubernetes.default.svc \
+  -Dquarkus.kubernetes-client.token=${TOKEN} \
+  -Dquarkus.kubernetes.deploy=true \
+  -Dquarkus.openshift.deployment-kind=Deployment \
+  -Dquarkus.openshift.add-version-to-label-selectors=false \
+  -Dquarkus.openshift.labels.\"app.openshift.io/runtime\"=camel \
+  -Dquarkus.container-image.group=${NAMESPACE} \
+  -Dquarkus.container-image.tag=${DATE}
\ No newline at end of file