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

treblereel pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new 7f51166d788 kie-issues#1586: [kn-plugin-workflow] Evaluate if we 
should install operator via cli
7f51166d788 is described below

commit 7f51166d7881a79023b61cd66d91fe510ff4551e
Author: Dmitrii Tikhomirov <[email protected]>
AuthorDate: Tue Mar 18 09:07:25 2025 -0700

    kie-issues#1586: [kn-plugin-workflow] Evaluate if we should install 
operator via cli
---
 packages/kn-plugin-workflow/Makefile               |   9 +-
 packages/kn-plugin-workflow/e2e-tests/main_test.go |   4 +-
 .../e2e-tests/operator_helper.go                   | 124 +++++++
 .../pkg/command/deploy_undeploy_common.go          |   2 +-
 .../kn-plugin-workflow/pkg/command/gen_manifest.go |   2 +-
 .../pkg/command/operator/install.go                |  72 ++++
 .../pkg/command/operator/operator.go               |  54 +++
 .../pkg/command/operator/status.go                 |  67 ++++
 .../pkg/command/operator/uninstall.go              |  83 +++++
 .../pkg/command/quarkus/deploy_test.go             |   8 +-
 .../pkg/common/k8sclient/fake.go                   |   2 +-
 .../pkg/common/k8sclient/goapi.go                  | 189 +++++++++--
 packages/kn-plugin-workflow/pkg/common/kubectl.go  |  61 +++-
 packages/kn-plugin-workflow/pkg/common/operator.go | 365 ++++++++++++++++++++-
 .../kn-plugin-workflow/pkg/metadata/constants.go   |   8 +-
 .../kn-plugin-workflow/pkg/openshift/operation.go  |  51 +++
 packages/kn-plugin-workflow/pkg/root/root.go       |   2 +
 packages/kn-plugin-workflow/pkg/root/root_test.go  |   1 +
 18 files changed, 1050 insertions(+), 54 deletions(-)

diff --git a/packages/kn-plugin-workflow/Makefile 
b/packages/kn-plugin-workflow/Makefile
index d2ea6cce538..69a8a377243 100644
--- a/packages/kn-plugin-workflow/Makefile
+++ b/packages/kn-plugin-workflow/Makefile
@@ -34,6 +34,7 @@ SET_KOGITO_VERSION            := 
$(METADATA_PATH).KogitoVersion=$(KOGITO_VERSION
 LDFLAGS                       := "-X $(SET_QUARKUS_PLATFORM_GROUP_ID) -X 
$(SET_QUARKUS_VERSION) -X $(SET_VERSION) -X $(SET_DEV_MODE_IMAGE) -X 
$(SET_KOGITO_VERSION)"
 
 KIND_VERSION ?= v0.20.0
+OLM_VERSION = v0.31.0
 
 ARCH := $(shell uname -m)
 ifeq ($(ARCH),arm64)
@@ -69,7 +70,7 @@ clean:
 test-e2e:
        @$(MAKE) install-kind
        @$(MAKE) create-cluster
-       @$(MAKE) install-operator
+       @$(MAKE) install-operator-framework
        @$(MAKE) go-test-e2e
        @$(MAKE) go-test-e2e-report
 
@@ -82,9 +83,9 @@ install-kind:
 create-cluster: install-kind
        kind create cluster
 
-.PHONY: install-operator
-install-operator:
-       kubectl create -f ../sonataflow-operator/operator.yaml
+.PHONY: install-operator-framework
+install-operator-framework:
+       curl -sL 
https://github.com/operator-framework/operator-lifecycle-manager/releases/download/$(OLM_VERSION)/install.sh
 | bash -s $(OLM_VERSION)
 
 .PHONY: go-test-e2e
 go-test-e2e:
diff --git a/packages/kn-plugin-workflow/e2e-tests/main_test.go 
b/packages/kn-plugin-workflow/e2e-tests/main_test.go
index 0fa8bb5abd5..d271db57d2f 100644
--- a/packages/kn-plugin-workflow/e2e-tests/main_test.go
+++ b/packages/kn-plugin-workflow/e2e-tests/main_test.go
@@ -38,7 +38,6 @@ var KnExecutable string
 
 var TestPrintCmdOutput = flag.Bool("logs", true, "Print command output during 
tests")
 
-var operatorCRD = "operator.yaml"
 
 func TestMain(m *testing.M) {
 
@@ -56,9 +55,12 @@ func TestMain(m *testing.M) {
 
        checkAndBuildExecutable()
 
+       InstallOperator()
        // Run tests
        exitCode := m.Run()
 
+       UninstallOperator()
+
        // Cleanup after tests
        cleanUpTemp(workingPath, tempDirName)
 
diff --git a/packages/kn-plugin-workflow/e2e-tests/operator_helper.go 
b/packages/kn-plugin-workflow/e2e-tests/operator_helper.go
new file mode 100644
index 00000000000..35e792d94e9
--- /dev/null
+++ b/packages/kn-plugin-workflow/e2e-tests/operator_helper.go
@@ -0,0 +1,124 @@
+//go:build e2e_tests
+
+/*
+ * 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 e2e_tests
+
+import (
+       "fmt"
+       "os"
+       "time"
+
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/operator"
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+)
+
+var operatorManager = common.NewOperatorManager("")
+
+func InstallOperator() {
+       installOperator()
+       waitForOperatorReady()
+       checkOperatorInstalled()
+}
+
+func UninstallOperator() {
+       uninstallOperator()
+}
+
+func installOperator() {
+       var install = operator.NewInstallOperatorCommand()
+       err := install.Execute()
+       if err != nil {
+               fmt.Println("Failed to install operator:", err)
+               os.Exit(1)
+       }
+}
+
+func waitForOperatorReady() {
+       deployed := make(chan bool)
+       defer close(deployed)
+       timeoutCh := time.After(5 * time.Minute)
+
+       go func() {
+               for {
+                       select {
+                       case <-timeoutCh:
+                               fmt.Println("Timeout waiting for operator to be 
ready")
+                               os.Exit(1)
+                       default:
+                               resources, err := 
operatorManager.ListOperatorResources();
+                               if err != nil {
+                                       fmt.Println("Failed to list operator 
resources:", err)
+                                       os.Exit(1)
+                               }
+
+                               if(len(resources) == 0) {
+                                       continue
+                               }
+
+                               var ready = true
+                               for _, resource := range resources {
+                                       phase, found, err := 
unstructured.NestedString(resource.Object, "status", "phase")
+                                       if !found {
+                                               ready = false
+                                       }
+                                       if err != nil {
+                                               fmt.Println("Failed to get 
resource status:", err)
+                                               os.Exit(1)
+                                       }
+                                       if phase != "Succeeded" {
+                                               ready = false
+                                       }
+                               }
+
+                               if ready {
+                                       deployed <- true
+                                       return
+
+                               }
+                               time.Sleep(5 * time.Second)
+                       }
+               }
+       }()
+
+       select {
+       case <-deployed:
+               fmt.Printf(" - ✅ Operator is ready\n")
+       }
+}
+
+func checkOperatorInstalled() {
+       var status = operator.NewStatusOperatorCommand()
+       err := status.Execute()
+       if err != nil {
+               fmt.Println("Failed to check operator status:", err)
+               os.Exit(1)
+       }
+}
+
+func uninstallOperator() {
+       var uninstall = operator.NewUnInstallOperatorCommand()
+       err := uninstall.Execute()
+       if err != nil {
+               fmt.Println("Failed to uninstall operator:", err)
+               os.Exit(1)
+       }
+}
diff --git a/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go 
b/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go
index bd9c3d7305c..ccd6b0eed99 100644
--- a/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go
+++ b/packages/kn-plugin-workflow/pkg/command/deploy_undeploy_common.go
@@ -66,7 +66,7 @@ func checkEnvironment(cfg *DeployUndeployCmdConfig) error {
 
        //setup namespace
        if len(cfg.NameSpace) == 0 {
-               if defaultNamespace, err := common.GetNamespace(); err == nil {
+               if defaultNamespace, err := common.GetCurrentNamespace(); err 
== nil {
                        cfg.NameSpace = defaultNamespace
                } else {
                        return err
diff --git a/packages/kn-plugin-workflow/pkg/command/gen_manifest.go 
b/packages/kn-plugin-workflow/pkg/command/gen_manifest.go
index b7dc0b6a7c5..93b8ae96826 100644
--- a/packages/kn-plugin-workflow/pkg/command/gen_manifest.go
+++ b/packages/kn-plugin-workflow/pkg/command/gen_manifest.go
@@ -198,7 +198,7 @@ func setupEnvironment(cfg *DeployUndeployCmdConfig) error {
 
        //setup namespace
        if len(cfg.NameSpace) == 0 && !cfg.EmptyNameSpace {
-               if defaultNamespace, err := common.GetNamespace(); err == nil {
+               if defaultNamespace, err := common.GetCurrentNamespace(); err 
== nil {
                        cfg.NameSpace = defaultNamespace
                        fmt.Printf(" - ✅  resolved namespace: %s\n", 
cfg.NameSpace)
                } else {
diff --git a/packages/kn-plugin-workflow/pkg/command/operator/install.go 
b/packages/kn-plugin-workflow/pkg/command/operator/install.go
new file mode 100644
index 00000000000..261b9164b4a
--- /dev/null
+++ b/packages/kn-plugin-workflow/pkg/command/operator/install.go
@@ -0,0 +1,72 @@
+/*
+ * 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 operator
+
+import (
+       "fmt"
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common"
+       "github.com/ory/viper"
+       "github.com/spf13/cobra"
+)
+
+func NewInstallOperatorCommand() *cobra.Command {
+       var cmd = &cobra.Command{
+               Use:   "install",
+               Short: "Install the SonataFlow Operator, which is responsible 
for deploying SonataFlow projects on Kubernetes.",
+               Long: `
+       Install the SonataFlow Operator, which is responsible for deploying 
SonataFlow projects on Kubernetes.
+       `,
+               Example: `
+       # Install the SonataFlow Operator. Usually in Openshift the namespace 
is "openshift-operators", in case of Minikube or Kind, with
+       # default OLM installation, the namespace is "operators".
+       {{.Name}} operator install --namespace <your_operator_namespace>
+               `,
+               PreRunE:    common.BindEnv("namespace"),
+       }
+
+       cmd.RunE = func(cmd *cobra.Command, args []string) error {
+               return runInstallOperatorCommand(cmd, args)
+       }
+       cmd.Flags().StringP("namespace", "n", "", "Target namespace of your 
Operator deployment.")
+       cmd.SetHelpFunc(common.DefaultTemplatedHelp)
+
+       return cmd
+}
+
+func runInstallOperatorCommand(cmd *cobra.Command, args []string) error {
+       fmt.Println("🚀 Installing the SonataFlow Operator...")
+
+       namespace := viper.GetString("namespace")
+
+       operator := common.NewOperatorManager(namespace)
+
+       err := operator.CheckOLMInstalled()
+       if err != nil {
+               return err
+       }
+
+       err = operator.InstallSonataflowOperator()
+       if err != nil {
+               return err
+       }
+       fmt.Println("🎉 SonataFlow Operator successfully installed, wait for the 
operator to be ready.")
+
+       return nil
+}
diff --git a/packages/kn-plugin-workflow/pkg/command/operator/operator.go 
b/packages/kn-plugin-workflow/pkg/command/operator/operator.go
new file mode 100644
index 00000000000..afba4a58c7b
--- /dev/null
+++ b/packages/kn-plugin-workflow/pkg/command/operator/operator.go
@@ -0,0 +1,54 @@
+/*
+ * 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 operator
+
+import (
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common"
+       "github.com/spf13/cobra"
+)
+
+func NewOperatorCommand() *cobra.Command {
+       var cmd = &cobra.Command{
+               Use:   "operator",
+               Short: "Manage the SonataFlow Operator, which is responsible 
for deploying SonataFlow projects on Kubernetes.",
+               Long: `
+       Install or uninstall the SonataFlow Operator, which is responsible for 
deploying SonataFlow projects on Kubernetes.
+       `,
+               Example: `
+       # Install the SonataFlow Operator.
+       {{.Name}} operator install
+
+       # Uninstall the SonataFlow Operator.
+       {{.Name}} operator uninstall
+
+       # Check the status of the SonataFlow Operator.
+       {{.Name}} operator status
+               `,
+       }
+
+       cmd.AddCommand(NewInstallOperatorCommand())
+       cmd.AddCommand(NewUnInstallOperatorCommand())
+       cmd.AddCommand(NewStatusOperatorCommand())
+
+
+       cmd.SetHelpFunc(common.DefaultTemplatedHelp)
+
+       return cmd
+}
diff --git a/packages/kn-plugin-workflow/pkg/command/operator/status.go 
b/packages/kn-plugin-workflow/pkg/command/operator/status.go
new file mode 100644
index 00000000000..3bdc2b7a65c
--- /dev/null
+++ b/packages/kn-plugin-workflow/pkg/command/operator/status.go
@@ -0,0 +1,67 @@
+/*
+ * 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 operator
+
+import (
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common"
+       "github.com/ory/viper"
+       "github.com/spf13/cobra"
+)
+
+func NewStatusOperatorCommand() *cobra.Command {
+       var cmd = &cobra.Command{
+               Use:   "status",
+               Short: "Get the status of deployed SonataFlow Operator.",
+               Long: `
+       Get the status of deployed SonataFlow Operator.
+       `,
+               Example: `
+       # Check the status of the SonataFlow Operator.
+       {{.Name}} operator status --namespace <your_operator_namespace>
+               `,
+               PreRunE:    common.BindEnv("namespace"),
+       }
+
+       cmd.RunE = func(cmd *cobra.Command, args []string) error {
+               return runStatusCommand(cmd, args)
+       }
+       cmd.Flags().StringP("namespace", "n", "", "Target namespace of your 
Operator deployment.")
+
+       cmd.SetHelpFunc(common.DefaultTemplatedHelp)
+
+       return cmd
+}
+
+func runStatusCommand(cmd *cobra.Command, args []string) error {
+       namespace := viper.GetString("namespace")
+
+       operator := common.NewOperatorManager(namespace)
+
+       err:= operator.CheckOLMInstalled()
+       if err != nil {
+               return err
+       }
+
+       err = operator.GetSonataflowOperatorStats()
+       if err != nil {
+               return err
+       }
+       return nil
+}
diff --git a/packages/kn-plugin-workflow/pkg/command/operator/uninstall.go 
b/packages/kn-plugin-workflow/pkg/command/operator/uninstall.go
new file mode 100644
index 00000000000..ec383b238f0
--- /dev/null
+++ b/packages/kn-plugin-workflow/pkg/command/operator/uninstall.go
@@ -0,0 +1,83 @@
+/*
+ * 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 operator
+
+import (
+       "fmt"
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common"
+       "github.com/ory/viper"
+       "github.com/spf13/cobra"
+)
+
+func NewUnInstallOperatorCommand() *cobra.Command {
+       var cmd = &cobra.Command{
+               Use:   "uninstall",
+               Short: "Uninstall the SonataFlow Operator.",
+               Long: `
+       Uninstall the SonataFlow Operator.
+       `,
+               Example: `
+       # Uninstall the SonataFlow Operator.
+       {{.Name}} operator uninstall --namespace <your_operator_namespace>
+               `,
+               PreRunE:    common.BindEnv("namespace"),
+       }
+
+       cmd.RunE = func(cmd *cobra.Command, args []string) error {
+               return runUnInstallOperatorCommand(cmd, args)
+       }
+       cmd.Flags().StringP("namespace", "n", "", "Target namespace of your 
Operator deployment.")
+
+       cmd.SetHelpFunc(common.DefaultTemplatedHelp)
+
+       return cmd
+}
+
+func runUnInstallOperatorCommand(cmd *cobra.Command, args []string) error {
+       fmt.Println("🚀 Uninstalling the SonataFlow Operator...")
+
+       namespace := viper.GetString("namespace")
+
+       operator := common.NewOperatorManager(namespace)
+
+       err := operator.RemoveCR()
+       if err != nil {
+               return fmt.Errorf("failed to remove CR: %v", err)
+       }
+
+       err = operator.RemoveCSV()
+       if err != nil {
+               return fmt.Errorf("failed to remove CSV: %v", err)
+       }
+
+       err = operator.RemoveCRD()
+       if err != nil {
+               return fmt.Errorf("failed to remove CRD: %v", err)
+       }
+
+       err = operator.RemoveSubscription()
+       if err != nil {
+               return fmt.Errorf("failed to remove subscription: %v", err)
+       }
+
+       fmt.Println("🎉 SonataFlow Operator successfully uninstalled.")
+
+       return nil
+}
diff --git a/packages/kn-plugin-workflow/pkg/command/quarkus/deploy_test.go 
b/packages/kn-plugin-workflow/pkg/command/quarkus/deploy_test.go
index addfccc4af4..f8bf333e7f3 100644
--- a/packages/kn-plugin-workflow/pkg/command/quarkus/deploy_test.go
+++ b/packages/kn-plugin-workflow/pkg/command/quarkus/deploy_test.go
@@ -91,19 +91,19 @@ func TestRunDeploy(t *testing.T) {
        common.FS = afero.NewMemMapFs()
        originalParseYamlFile := k8sclient.ParseYamlFile
        originalDynamicClient := k8sclient.DynamicClient
-       originalGetNamespace := k8sclient.GetNamespace
+       originalGetNamespace := k8sclient.GetCurrentNamespace
 
        fakeClient := k8sclient.Fake{FS: common.FS}
 
        defer func() {
                k8sclient.ParseYamlFile = originalParseYamlFile
                k8sclient.DynamicClient = originalDynamicClient
-               k8sclient.GetNamespace = originalGetNamespace
+               k8sclient.GetCurrentNamespace = originalGetNamespace
        }()
 
        k8sclient.ParseYamlFile = fakeClient.FakeParseYamlFile
        k8sclient.DynamicClient = fakeClient.FakeDynamicClient
-       k8sclient.GetNamespace = fakeClient.GetNamespace
+       k8sclient.GetCurrentNamespace = fakeClient.GetCurrentNamespace
 
        for _, test := range testRunDeploy {
                checkDeploy(t, test)
@@ -197,7 +197,7 @@ func knativeFixQuarkusVersionAndWriteToTestFolder(t 
*testing.T, test testDeploy)
 
 func checkObjectCreated(obj unstructured.Unstructured, namespace string) 
(bool, error) {
        if namespace == "" {
-               currentNamespace, err := common.GetNamespace()
+               currentNamespace, err := common.GetCurrentNamespace()
                if err != nil {
                        return false, fmt.Errorf("❌ ERROR: Failed to get 
current namespace: %v", err)
                }
diff --git a/packages/kn-plugin-workflow/pkg/common/k8sclient/fake.go 
b/packages/kn-plugin-workflow/pkg/common/k8sclient/fake.go
index 4e6813a4608..7e3903ce246 100644
--- a/packages/kn-plugin-workflow/pkg/common/k8sclient/fake.go
+++ b/packages/kn-plugin-workflow/pkg/common/k8sclient/fake.go
@@ -47,7 +47,7 @@ func (m Fake) FakeDynamicClient() (dynamic.Interface, error) {
        return currentDynamicClient, nil
 }
 
-func (m Fake) GetNamespace() (string, error) {
+func (m Fake) GetCurrentNamespace() (string, error) {
        return "default", nil
 }
 
diff --git a/packages/kn-plugin-workflow/pkg/common/k8sclient/goapi.go 
b/packages/kn-plugin-workflow/pkg/common/k8sclient/goapi.go
index df34b8348db..f3d14b26316 100644
--- a/packages/kn-plugin-workflow/pkg/common/k8sclient/goapi.go
+++ b/packages/kn-plugin-workflow/pkg/common/k8sclient/goapi.go
@@ -17,6 +17,7 @@
  * under the License.
  */
 
+
 package k8sclient
 
 import (
@@ -30,12 +31,15 @@ import (
        "strings"
 
        v1 "k8s.io/api/apps/v1"
+       corev1 "k8s.io/api/core/v1"
        "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
        "k8s.io/apimachinery/pkg/api/errors"
        "k8s.io/apimachinery/pkg/api/meta"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+       "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/util/yaml"
+
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/kubernetes"
        "k8s.io/client-go/rest"
@@ -47,8 +51,70 @@ import (
 
 type GoAPI struct{}
 
-func (m GoAPI) GetNamespace() (string, error) {
-       return GetNamespace()
+var selfSubjectAccessGVR = schema.GroupVersionResource{
+       Group:    "authorization.k8s.io",
+       Version:  "v1",
+       Resource: "selfsubjectaccessreviews",
+}
+
+var kubeconfigInfoDisplayed = false
+
+func (m GoAPI) IsCreateAllowed(resourcePath string, namespace string) (bool, 
error) {
+       dynamicClient, err := DynamicClient()
+       if err != nil {
+               return false, fmt.Errorf("❌ ERROR: Failed to create dynamic 
Kubernetes client: %v", err)
+       }
+
+       if resources, err := ParseYamlFile(resourcePath); err != nil {
+               return false, fmt.Errorf("❌ ERROR: Failed to parse YAML file: 
%v", err)
+       } else {
+               for _, resource := range resources {
+                       sar := parseResource(resource, namespace)
+
+                       result, err := 
dynamicClient.Resource(selfSubjectAccessGVR).Create(context.TODO(), sar, 
metav1.CreateOptions{})
+                       if err != nil {
+                               return false, fmt.Errorf("failed to perform 
access review: %v", err)
+                       }
+
+                       allowed, _, _ := unstructured.NestedBool(result.Object, 
"status", "allowed")
+                       return allowed, nil
+               }
+       }
+       return false, nil
+}
+
+func (m GoAPI) IsDeleteAllowed(name string, namespace string) error {
+       dynamicClient, err := DynamicClient()
+       if err != nil {
+               return fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes 
client: %v", err)
+       }
+
+       err = 
dynamicClient.Resource(selfSubjectAccessGVR).Namespace(namespace).Delete(context.TODO(),
 name, metav1.DeleteOptions{})
+       if err != nil {
+               return fmt.Errorf("failed to perform access review: %v", err)
+       }
+       return nil
+}
+
+func (m GoAPI) GetCurrentNamespace() (string, error) {
+       return GetCurrentNamespace()
+}
+
+func (m GoAPI) GetNamespace(namespace string) (*corev1.Namespace, error) {
+       config, err := KubeRestConfig()
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to create rest config 
for Kubernetes client: %v", err)
+       }
+
+       clientSet, err := kubernetes.NewForConfig(config)
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to create k8s client: 
%v", err)
+       }
+       ns, err := clientSet.CoreV1().Namespaces().Get(context.TODO(), 
namespace, metav1.GetOptions{})
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to get namespace: %v", 
err)
+       }
+       return ns, nil
 }
 
 func (m GoAPI) CheckContext() (string, error) {
@@ -72,7 +138,7 @@ func (m GoAPI) ExecuteApply(path, namespace string) error {
        fmt.Printf("🔨 Applying YAML file %s\n", path)
 
        if namespace == "" {
-               currentNamespace, err := m.GetNamespace()
+               currentNamespace, err := m.GetCurrentNamespace()
                if err != nil {
                        return fmt.Errorf("❌ ERROR: Failed to get current 
namespace: %w", err)
                }
@@ -119,14 +185,36 @@ func (m GoAPI) ExecuteApply(path, namespace string) error 
{
        return nil
 }
 
-func (m GoAPI) ExecuteDelete(path, namespace string) error {
-       client, err := DynamicClient()
+func (m GoAPI) ExecuteCreate(gvr schema.GroupVersionResource, object 
*unstructured.Unstructured, namespace string) (*unstructured.Unstructured, 
error) {
+       dynamicClient, err := DynamicClient()
        if err != nil {
-               return fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes 
client: %v", err)
+               return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic 
Kubernetes client: %v", err)
+       }
+       resulted, err := 
dynamicClient.Resource(gvr).Namespace(namespace).Create(context.Background(), 
object, metav1.CreateOptions{})
+       if err != nil {
+               if errors.IsAlreadyExists(err) {
+                       fmt.Printf("✅ Resource %q already exists\n", 
object.GetName())
+               }
+               return nil, fmt.Errorf("❌ Failed to create resource: %v", err)
        }
 
+       return resulted, nil
+}
+
+func (m GoAPI) ExecuteGet(gvr schema.GroupVersionResource, name string, 
namespace string) (*unstructured.Unstructured, error) {
+       dynamicClient, err := DynamicClient()
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic 
Kubernetes client: %v", err)
+       }
+
+       return 
dynamicClient.Resource(gvr).Namespace(namespace).Get(context.Background(), 
name, metav1.GetOptions{})
+}
+
+func (m GoAPI) ExecuteDelete(path, namespace string) error {
+       fmt.Println("🔨 Deleting resources...", path)
+
        if namespace == "" {
-               currentNamespace, err := m.GetNamespace()
+               currentNamespace, err := m.GetCurrentNamespace()
                if err != nil {
                        return fmt.Errorf("❌ ERROR: Failed to get current 
namespace: %w", err)
                }
@@ -136,22 +224,36 @@ func (m GoAPI) ExecuteDelete(path, namespace string) 
error {
        if resources, err := ParseYamlFile(path); err != nil {
                return fmt.Errorf("❌ ERROR: Failed to parse YAML file: %v", err)
        } else {
-               deletePolicy := metav1.DeletePropagationForeground
                for _, resource := range resources {
                        gvk := resource.GroupVersionKind()
                        gvr, _ := meta.UnsafeGuessKindToResource(gvk)
 
-                       err = 
client.Resource(gvr).Namespace(namespace).Delete(context.Background(), 
resource.GetName(), metav1.DeleteOptions{
-                               PropagationPolicy: &deletePolicy,
-                       })
+                       err := m.ExecuteDeleteGVR(gvr, resource.GetName(), 
namespace)
                        if err != nil {
-                               return fmt.Errorf("❌ ERROR: Failed to delete 
Resource: %w", err)
+                               return fmt.Errorf("❌ ERROR: Failed to delete 
resource: %v", err)
                        }
                }
        }
        return nil
 }
 
+func (m GoAPI) ExecuteDeleteGVR(gvr schema.GroupVersionResource, name string, 
namespace string) error {
+       client, err := DynamicClient()
+       if err != nil {
+               return fmt.Errorf("❌ ERROR: Failed to create dynamic Kubernetes 
client: %v", err)
+       }
+
+       deletePolicy := metav1.DeletePropagationForeground
+       err = 
client.Resource(gvr).Namespace(namespace).Delete(context.Background(), name, 
metav1.DeleteOptions{
+               PropagationPolicy: &deletePolicy,
+       })
+
+       if err != nil {
+               return fmt.Errorf("❌ ERROR: Failed to delete Resource: %w", err)
+       }
+       return nil
+}
+
 func (m GoAPI) CheckCrdExists(crd string) error {
        config, err := KubeRestConfig()
        if err != nil {
@@ -173,7 +275,7 @@ func (m GoAPI) CheckCrdExists(crd string) error {
 
 func (m GoAPI) GetDeploymentStatus(namespace, deploymentName string) 
(v1.DeploymentStatus, error) {
        if namespace == "" {
-               currentNamespace, err := m.GetNamespace()
+               currentNamespace, err := m.GetCurrentNamespace()
                if err != nil {
                        return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: 
Failed to get current namespace: %w", err)
                }
@@ -189,7 +291,7 @@ func (m GoAPI) GetDeploymentStatus(namespace, 
deploymentName string) (v1.Deploym
        if err != nil {
                return v1.DeploymentStatus{}, fmt.Errorf("❌ ERROR: Failed to 
create k8s client: %v", err)
        }
-       deployments, err := 
newConfig.AppsV1().Deployments("default").List(context.TODO(), 
metav1.ListOptions{
+       deployments, err := 
newConfig.AppsV1().Deployments(namespace).List(context.TODO(), 
metav1.ListOptions{
                LabelSelector: fmt.Sprintf("sonataflow.org/workflow-app=%s", 
deploymentName),
        })
 
@@ -210,7 +312,7 @@ func (m GoAPI) GetDeploymentStatus(namespace, 
deploymentName string) (v1.Deploym
 
 func (m GoAPI) PortForward(namespace, serviceName, portFrom, portTo string, 
onReady func()) error  {
        if namespace == "" {
-               currentNamespace, err := m.GetNamespace()
+               currentNamespace, err := m.GetCurrentNamespace()
                if err != nil {
                        return fmt.Errorf("❌ ERROR: Failed to get current 
namespace: %w", err)
                }
@@ -249,7 +351,7 @@ func (m GoAPI) PortForward(namespace, serviceName, 
portFrom, portTo string, onRe
        }
 
        req := 
clientSet.CoreV1().RESTClient().Post().Resource("pods").Namespace(pods.Items[0].Namespace).
-                                                                 
Name(pods.Items[0].Name).SubResource("portforward")
+               Name(pods.Items[0].Name).SubResource("portforward")
 
        transport, upgrader, err := spdy.RoundTripperFor(config)
        if err != nil {
@@ -285,12 +387,30 @@ func (m GoAPI) PortForward(namespace, serviceName, 
portFrom, portTo string, onRe
        return nil
 }
 
+func (m GoAPI) ExecuteList(gvr schema.GroupVersionResource, namespace string) 
(*unstructured.UnstructuredList, error) {
+       client, err := DynamicClient()
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to create dynamic 
Kubernetes client: %v", err)
+       }
+
+       list, err := 
client.Resource(gvr).Namespace(namespace).List(context.Background(), 
metav1.ListOptions{})
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to list resources: %v", 
err)
+       }
+
+       return list, nil
+}
+
 func KubeApiConfig() (*api.Config, error) {
        homeDir, err := os.UserHomeDir()
        if err != nil {
                return nil, fmt.Errorf("error getting user home dir: %w", err)
        }
        kubeConfigPath := filepath.Join(homeDir, ".kube", "config")
+       if !kubeconfigInfoDisplayed {
+               fmt.Printf("🔎 Using kubeconfig: %s\n", kubeConfigPath)
+               kubeconfigInfoDisplayed = true
+       }
        config, err := clientcmd.LoadFromFile(kubeConfigPath)
        if err != nil {
                return nil, fmt.Errorf("❌ ERROR: Failed to load kubeconfig: 
%w", err)
@@ -308,7 +428,7 @@ func KubeRestConfig() (*rest.Config, error) {
                clientConfig := clientcmd.NewDefaultClientConfig(*kubeConfig, 
&clientcmd.ConfigOverrides{})
                restConfig, err := clientConfig.ClientConfig()
                if err != nil {
-                       log.Fatalf("❌ Error converting to rest.Config: %v", err)
+                       log.Fatalf("Error converting to rest.Config: %v", err)
                }
                return restConfig, nil
        }
@@ -347,30 +467,43 @@ var ParseYamlFile = func(path string) 
([]unstructured.Unstructured, error) {
        return result, nil
 }
 
-var GetNamespace = func() (string, error) {
+var GetCurrentNamespace = func() (string, error) {
        fmt.Println("🔎 Checking current namespace in k8s...")
 
        config, err := KubeApiConfig()
        if err != nil {
-               fmt.Println("❌ ERROR: Failed to get current k8s namespace: %w", 
err)
                return "", fmt.Errorf("❌ ERROR: Failed to get current k8s 
namespace: %w", err)
        }
+       namespace := config.Contexts[config.CurrentContext].Namespace
 
-       var namespace string
-
-       if contextes, ok := config.Contexts[config.CurrentContext]; !ok {
+       if len(namespace) == 0 {
                namespace = "default"
-       } else {
-               if len(contextes.Namespace) == 0 {
-                       namespace = "default"
-               } else {
-                       namespace = contextes.Namespace
-               }
        }
        fmt.Printf(" - ✅  k8s current namespace: %s\n", namespace)
        return namespace, nil
 }
 
+func parseResource(resource unstructured.Unstructured, namespace string) 
*unstructured.Unstructured {
+       gvk := resource.GroupVersionKind()
+       gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+
+       sar := &unstructured.Unstructured{
+               Object: map[string]interface{}{
+                       "apiVersion": "authorization.k8s.io/v1",
+                       "kind":       "SelfSubjectAccessReview",
+                       "spec": map[string]interface{}{
+                               "resourceAttributes": map[string]interface{}{
+                                       "namespace": namespace,
+                                       "verb":      gvr.Version,
+                                       "group":     gvk.Group,
+                                       "resource":  gvr.Resource,
+                               },
+                       },
+               },
+       }
+       return sar
+}
+
 func doRollback(created []unstructured.Unstructured, applyNamespace string, 
client dynamic.Interface) error {
        for _, r := range created {
                gvk := r.GroupVersionKind()
diff --git a/packages/kn-plugin-workflow/pkg/common/kubectl.go 
b/packages/kn-plugin-workflow/pkg/common/kubectl.go
index ab549256905..780901d56b1 100644
--- a/packages/kn-plugin-workflow/pkg/common/kubectl.go
+++ b/packages/kn-plugin-workflow/pkg/common/kubectl.go
@@ -22,34 +22,73 @@ package common
 import (
        
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common/k8sclient"
        v1 "k8s.io/api/apps/v1"
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 )
 
 type K8sApi interface {
-       GetNamespace() (string, error)
+
+       IsCreateAllowed(resourcePath string, namespace string) (bool, error)
+       IsDeleteAllowed(resourcePath string, namespace string) error
+       GetCurrentNamespace() (string, error)
+       GetNamespace(namespace string) (*corev1.Namespace, error)
        CheckContext() (string, error)
-       ExecuteApply(crd, namespace string) error
-       ExecuteDelete(crd, namespace string) error
-       CheckCrdExists(crd string) error
+       ExecuteApply(path, namespace string) error
+       ExecuteCreate(gvr schema.GroupVersionResource, object 
*unstructured.Unstructured, namespace string) (*unstructured.Unstructured, 
error)
+       ExecuteDelete(path, namespace string) error
+       ExecuteDeleteGVR(gvr schema.GroupVersionResource, name string, 
namespace string) error
+       ExecuteGet(gvr schema.GroupVersionResource, name string, namespace 
string) (*unstructured.Unstructured, error)
+       ExecuteList(gvr schema.GroupVersionResource, namespace string) 
(*unstructured.UnstructuredList, error)
+       CheckCrdExists(path string) error
        GetDeploymentStatus(namespace, deploymentName string) 
(v1.DeploymentStatus, error)
        PortForward(namespace, serviceName, portFrom, portTo string, onReady 
func()) error
 }
 
 var Current K8sApi = k8sclient.GoAPI{}
 
+func IsCreateAllowed(resourcePath string, namespace string) (bool, error) {
+       return Current.IsCreateAllowed(resourcePath, namespace)
+}
+
+func IsDeleteAllowed(name string, namespace string) error {
+       return Current.IsDeleteAllowed(name, namespace)
+}
+
 func CheckContext() (string, error) {
-       return Current.GetNamespace()
+       return Current.GetCurrentNamespace()
+}
+
+func GetCurrentNamespace() (string, error) {
+       return Current.GetCurrentNamespace()
+}
+
+func GetNamespace(namespace string) (*corev1.Namespace, error)  {
+       return Current.GetNamespace(namespace)
+}
+
+var ExecuteApply = func(path, namespace string) error {
+       return Current.ExecuteApply(path, namespace)
+}
+
+func ExecuteCreate(gvr schema.GroupVersionResource, object 
*unstructured.Unstructured, namespace string) (*unstructured.Unstructured, 
error) {
+       return Current.ExecuteCreate(gvr, object, namespace)
+}
+
+func ExecuteDelete(path, namespace string) error {
+       return Current.ExecuteDelete(path, namespace)
 }
 
-func GetNamespace() (string, error) {
-       return Current.GetNamespace()
+func ExecuteDeleteGVR(gvr schema.GroupVersionResource, name string, namespace 
string) error {
+       return Current.ExecuteDeleteGVR(gvr, name, namespace)
 }
 
-var ExecuteApply = func(crd, namespace string) error {
-       return Current.ExecuteApply(crd, namespace)
+func ExecuteGet(gvr schema.GroupVersionResource, name string, namespace 
string) (*unstructured.Unstructured, error) {
+       return Current.ExecuteGet(gvr, name, namespace)
 }
 
-func ExecuteDelete(crd, namespace string) error {
-       return Current.ExecuteDelete(crd, namespace)
+func ExecuteList(gvr schema.GroupVersionResource, namespace 
string)(*unstructured.UnstructuredList, error)  {
+       return Current.ExecuteList(gvr, namespace)
 }
 
 func CheckCrdExists(crd string) error {
diff --git a/packages/kn-plugin-workflow/pkg/common/operator.go 
b/packages/kn-plugin-workflow/pkg/common/operator.go
index 5a92f31e393..8a6f27e873b 100644
--- a/packages/kn-plugin-workflow/pkg/common/operator.go
+++ b/packages/kn-plugin-workflow/pkg/common/operator.go
@@ -20,13 +20,22 @@
 package common
 
 import (
+       "context"
        "fmt"
-       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/metadata"
-       "gopkg.in/yaml.v2"
        "io"
+       "k8s.io/client-go/discovery"
+       "k8s.io/client-go/dynamic"
        "os"
        "path/filepath"
        "strings"
+
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common/k8sclient"
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/metadata"
+       "gopkg.in/yaml.v2"
+       "k8s.io/apimachinery/pkg/api/errors"
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 )
 
 type Document struct {
@@ -36,6 +45,78 @@ type Document struct {
        } `yaml:"metadata"`
 }
 
+var namespacesGVR = schema.GroupVersionResource{
+       Group:    "",
+       Version:  "v1",
+       Resource: "namespaces",
+}
+
+var sonataflowGVR = schema.GroupVersionResource{
+       Group:    "sonataflow.org",
+       Version:  "v1alpha08",
+       Resource: "sonataflows",
+}
+
+var subscriptionsGVR = schema.GroupVersionResource{
+       Group:    "operators.coreos.com",
+       Version:  "v1alpha1",
+       Resource: "subscriptions",
+}
+
+var clusterServiceVersionsGVR = schema.GroupVersionResource{
+       Group:    "operators.coreos.com",
+       Version:  "v1alpha1",
+       Resource: "clusterserviceversions",
+}
+
+var customResourceDefinitionsGVR = schema.GroupVersionResource{
+       Group:    "apiextensions.k8s.io",
+       Version:  "v1",
+       Resource: "customresourcedefinitions",
+}
+
+var catalogSourcesGVR = schema.GroupVersionResource{
+       Group:    "operators.coreos.com",
+       Version:  "v1alpha1",
+       Resource: "catalogsources",
+}
+
+type OperatorManager struct {
+       namespace     string
+       isOpenshift   bool
+       dynamicClient dynamic.Interface
+}
+
+var openshiftOperatorNamespaces = []string{"openshift-operators", 
"community-operators"}
+
+func NewOperatorManager(namespace string) *OperatorManager {
+       isOpenshift, err := isOpenshift()
+       if err != nil {
+               fmt.Printf("❌ ERROR: %v\n", err)
+               os.Exit(1)
+       }
+
+       if namespace == "" {
+               namespace, err = guessOperatorNamespace(isOpenshift)
+               if err != nil {
+                       fmt.Printf("❌ ERROR: %v\n", err)
+                       os.Exit(1)
+               }
+       }
+
+       dynamicClient, err := k8sclient.DynamicClient()
+       if err != nil {
+               fmt.Printf("❌ ERROR: %v\n", err)
+               os.Exit(1)
+       }
+
+       return &OperatorManager{
+               namespace:     namespace,
+               isOpenshift:   isOpenshift,
+               dynamicClient: dynamicClient,
+       }
+}
+
 func checkOperatorRunning(getPodsOutPut string) bool {
        pods := strings.Split(getPodsOutPut, "\n")
        for _, pod := range pods {
@@ -91,3 +172,283 @@ func FindServiceFiles(directory string) ([]string, error) {
 
        return serviceFiles, nil
 }
+
+func (m OperatorManager) CheckOLMInstalled() error {
+       resources, err := 
m.dynamicClient.Resource(catalogSourcesGVR).List(context.Background(), 
v1.ListOptions{})
+       if err != nil {
+               return fmt.Errorf("❌ ERROR: OLM (Operator Lifecycle Manager) 
isn't installed, install OLM first : %v\n", err)
+       }
+       var operatorSources []string
+
+       if m.isOpenshift {
+               operatorSources = append(operatorSources, 
openshiftOperatorNamespaces...)
+       } else {
+               operatorSources = append(operatorSources, 
"operatorhubio-catalog")
+       }
+
+       for _, resource := range resources.Items {
+               name, found, err := unstructured.NestedString(resource.Object, 
"metadata", "name")
+               namespace, found, err := 
unstructured.NestedString(resource.Object, "metadata", "namespace")
+               if err != nil || !found {
+                       continue
+               }
+               for _, operatorSource := range operatorSources {
+                       if name == operatorSource && 
metadata.OLMCatalogSourcesMap[name] == namespace {
+                               return nil
+                       }
+               }
+       }
+
+       return fmt.Errorf("❌ ERROR: OLM (Operator Lifecycle Manager) is not 
installed. Please install OLM before installing the SonataFlow Operator.")
+}
+
+func (m OperatorManager) InstallSonataflowOperator() error {
+       var source string
+       var sourceNamespace string
+
+       if m.isOpenshift {
+               source = "community-operators"
+               sourceNamespace = "openshift-marketplace"
+       } else {
+               source = "operatorhubio-catalog"
+               sourceNamespace = "olm"
+       }
+
+       subscriptionUnstructured := &unstructured.Unstructured{
+               Object: map[string]interface{}{
+                       "apiVersion": "operators.coreos.com/v1alpha1",
+                       "kind":       "Subscription",
+                       "metadata": map[string]interface{}{
+                               "name":      metadata.SonataFlowOperatorName,
+                               "namespace": m.namespace,
+                       },
+                       "spec": map[string]interface{}{
+                               "channel":         "alpha",
+                               "name":            "sonataflow-operator",
+                               "source":          source,
+                               "sourceNamespace": sourceNamespace,
+                       },
+               },
+       }
+
+       _, err := ExecuteCreate(subscriptionsGVR, subscriptionUnstructured, 
m.namespace)
+       if err != nil {
+               return fmt.Errorf("❌ Failed to create subscription: %v", err)
+       }
+       fmt.Println("✅ Subscription created successfully")
+
+       return nil
+}
+
+func (m OperatorManager) GetSonataflowOperatorStats() error {
+       subscription, err := ExecuteGet(subscriptionsGVR, 
metadata.SonataFlowOperatorName, m.namespace)
+       if err != nil {
+               return fmt.Errorf("❌ SonataFlow Operator is not installed.")
+       }
+
+       status, found, err := unstructured.NestedMap(subscription.Object, 
"status")
+       if err != nil {
+               return fmt.Errorf("error getting status: %v", err)
+       }
+       if !found {
+               return fmt.Errorf("status not found in subscription")
+       }
+
+       currentCSV, _, _ := unstructured.NestedString(status, "currentCSV")
+       installedCSV, _, _ := unstructured.NestedString(status, "installedCSV")
+       state, _, _ := unstructured.NestedString(status, "state")
+
+       fmt.Println()
+       fmt.Println("📊 Subscription Status:")
+       fmt.Println()
+
+       fmt.Printf("Current CSV: %s\n", currentCSV)
+       fmt.Printf("Installed CSV: %s\n", installedCSV)
+       fmt.Printf("State: %s\n", state)
+
+       conditions, exists, _ := unstructured.NestedSlice(status, "conditions")
+       if exists {
+               fmt.Println()
+               fmt.Printf("Conditions:\n")
+               for _, c := range conditions {
+                       condition := c.(map[string]interface{})
+                       fmt.Printf("Type: %s\n", condition["type"])
+                       fmt.Printf("Status: %s\n", condition["status"])
+                       fmt.Printf("Message: %s\n", condition["message"])
+                       fmt.Printf("Last Transition Time: %s\n\n", 
condition["lastTransitionTime"])
+               }
+       }
+
+       return nil
+}
+
+func (m OperatorManager) RemoveSubscription() error {
+       fmt.Println("🔧 Deleting the SonataFlow Operator subscription...")
+
+       err := ExecuteDeleteGVR(subscriptionsGVR, 
metadata.SonataFlowOperatorName, m.namespace)
+       if err != nil {
+               return fmt.Errorf("❌ Failed to delete subscription 
`sonataflow-operator` in namespace %s: %v\n", m.namespace, err)
+       }
+       fmt.Printf("✅ Subscription `sonataflow-operator` deleted successfully 
in namespace %s\n", m.namespace)
+
+       return nil
+}
+
+func (m OperatorManager) RemoveCSV() error {
+       resources, err := ExecuteList(clusterServiceVersionsGVR, m.namespace)
+       if err != nil {
+               return fmt.Errorf("❌ ERROR: failed to get CSV resources: %v", 
err)
+       }
+       for _, resource := range resources.Items {
+               name, found, err := unstructured.NestedString(resource.Object, 
"metadata", "name")
+               if err != nil || !found {
+                       continue
+               }
+               if strings.HasPrefix(name, metadata.SonataFlowOperatorName) {
+                       err := ExecuteDeleteGVR(clusterServiceVersionsGVR, 
name, m.namespace)
+                       if err != nil {
+                               return fmt.Errorf("❌ ERROR: Failed to delete 
CSV `sonataflow-operator` in namespace %s: %v\n", m.namespace, err)
+                       }
+                       fmt.Printf("✅ CSV `sonataflow-operator` deleted 
successfully in namespace %s\n", m.namespace)
+                       return nil
+               }
+       }
+       return fmt.Errorf("❌ ERROR: CSV `sonataflow-operator` not found in 
namespace %s\n", m.namespace)
+}
+
+func (m OperatorManager) ListOperatorResources() ([]unstructured.Unstructured, 
error) {
+       resources, err := 
m.dynamicClient.Resource(clusterServiceVersionsGVR).List(context.Background(), 
v1.ListOptions{})
+       if err != nil {
+               return nil, fmt.Errorf("❌ ERROR: Failed to list resources: %v", 
err)
+       }
+
+       if len(resources.Items) == 0 {
+               return nil, fmt.Errorf("❌ No resources found")
+       }
+       var result []unstructured.Unstructured
+       for _, csv := range resources.Items {
+               if strings.HasPrefix(csv.GetName(), 
metadata.SonataFlowOperatorName) {
+                       result = append(result, csv)
+               }
+       }
+       return result, nil
+}
+
+func (m OperatorManager) RemoveCRD() error {
+       subscription, err := ExecuteGet(subscriptionsGVR, 
metadata.SonataFlowOperatorName, m.namespace)
+       if err != nil {
+               return fmt.Errorf("failed to get subscription %s: %v", 
metadata.SonataFlowOperatorName, err)
+       }
+
+       installedCSV, found, err := 
unstructured.NestedString(subscription.Object, "status", "installedCSV")
+       if err != nil || !found {
+               return fmt.Errorf("failed to extract installedCSV from 
subscription: %v", err)
+       }
+
+       csvObj, err := ExecuteGet(clusterServiceVersionsGVR, installedCSV, 
m.namespace)
+       if err != nil {
+               return fmt.Errorf("❌ Failed to get CSV %q in namespace %q: %v", 
installedCSV, m.namespace, err)
+       }
+
+       ownedCRDs, found, err := unstructured.NestedSlice(csvObj.Object,
+               "spec", "customresourcedefinitions", "owned")
+       if err != nil {
+               return fmt.Errorf("❌ Failed to extract owned CRDs: %v", err)
+       }
+       if !found {
+               fmt.Println("✅ No owned CRDs found in CSV")
+               return nil
+       }
+
+       for _, crdRaw := range ownedCRDs {
+               crdMap, ok := crdRaw.(map[string]interface{})
+               if !ok {
+                       continue
+               }
+               crdName, _, _ := unstructured.NestedString(crdMap, "name")
+               crdKind, _, _ := unstructured.NestedString(crdMap, "kind")
+               crdVersion, _, _ := unstructured.NestedString(crdMap, "version")
+
+               fmt.Printf("✅ CRD Name: %s, Kind: %s, Version: %s\n", crdName, 
crdKind, crdVersion)
+
+               err := ExecuteDeleteGVR(customResourceDefinitionsGVR, crdName, 
"")
+               if err != nil {
+                       return fmt.Errorf("❌ ERROR: Failed to delete CRD %q: 
%v\n", crdName, err)
+               }
+               fmt.Printf("✅ CRD %q deleted\n", crdName)
+       }
+       return nil
+}
+
+func (m OperatorManager) RemoveCR() error {
+       namespaces, err := ExecuteList(namespacesGVR, "")
+       if err != nil {
+               return fmt.Errorf("❌ Failed to list resources in namespace: 
%v", err)
+       }
+
+       for _, ns := range namespaces.Items {
+               namespace := ns.GetName()
+
+               sonataflows, err := ExecuteList(sonataflowGVR, namespace)
+               if err != nil {
+                       if !errors.IsNotFound(err) && !errors.IsForbidden(err) {
+                               return fmt.Errorf("❌ Error listing sonataflows 
in namespace %s: %v\n", namespace, err)
+                       }
+                       continue
+               }
+
+               if len(sonataflows.Items) > 0 {
+                       for _, deployment := range sonataflows.Items {
+                               name := deployment.GetName()
+                               err := ExecuteDeleteGVR(sonataflowGVR, name, 
namespace)
+                               if err != nil {
+                                       return fmt.Errorf("❌ Failed to delete 
%s: %v\n", name, err)
+                               } else {
+                                       fmt.Printf("✅ Deleted %s 
successfully\n", name)
+                               }
+                       }
+               }
+       }
+
+       return nil
+}
+
+func isOpenshift() (bool, error) {
+       dynamicConfig, err := k8sclient.KubeRestConfig()
+       if err != nil {
+               return false, fmt.Errorf("❌ ERROR: Failed to create dynamic 
Kubernetes client: %v", err)
+       }
+
+       discoveryClient, err := 
discovery.NewDiscoveryClientForConfig(dynamicConfig)
+       if err != nil {
+               return false, err
+       }
+
+       apiGroups, err := discoveryClient.ServerGroups()
+       if err != nil {
+               return false, fmt.Errorf("❌ ERROR: Failed to discover server 
groups: %v", err)
+       }
+
+       for _, group := range apiGroups.Groups {
+               if group.Name == "route.openshift.io" {
+                       return true, nil
+               }
+       }
+       return false, nil
+}
+
+var possibleOpenshiftNamespaces = []string{"openshift-operators", 
"community-operators"}
+
+func guessOperatorNamespace(isOpenshift bool) (string, error) {
+       if isOpenshift {
+               for _, ns := range possibleOpenshiftNamespaces {
+                       if _, err := GetNamespace(ns); err == nil {
+                               return ns, nil
+                       }
+               }
+       } else {
+               // In case of Minikube or Kind, with default OLM installation, 
the namespace is "operators"
+               return "operators", nil
+       }
+       return "", fmt.Errorf("❌ ERROR: No valid namespace found for the 
Operator, please provide a namespace with the --namespace flag")
+}
diff --git a/packages/kn-plugin-workflow/pkg/metadata/constants.go 
b/packages/kn-plugin-workflow/pkg/metadata/constants.go
index 37d9284e796..871bdd087e4 100644
--- a/packages/kn-plugin-workflow/pkg/metadata/constants.go
+++ b/packages/kn-plugin-workflow/pkg/metadata/constants.go
@@ -49,9 +49,15 @@ var KogitoDependencies = []Dependency{
 }
 
 // requared crds for sonataflow
-var SonataflowCRDs = []string{"sonataflowbuilds.sonataflow.org", 
"sonataflowclusterplatforms.sonataflow.org", 
"sonataflowplatforms.sonataflow.org", "sonataflows.sonataflow.org"}
+var SonataflowCRDs = []string{"sonataflows.sonataflow.org", 
"sonataflowbuilds.sonataflow.org", "sonataflowplatforms.sonataflow.org"}
 var KnativeCoreServingCRDs = []string{"images.caching.internal.knative.dev", 
"certificates.networking.internal.knative.dev", 
"configurations.serving.knative.dev", 
"clusterdomainclaims.networking.internal.knative.dev", 
"domainmappings.serving.knative.dev", 
"ingresses.networking.internal.knative.dev", 
"metrics.autoscaling.internal.knative.dev", 
"podautoscalers.autoscaling.internal.knative.dev", 
"revisions.serving.knative.dev", "routes.serving.knative.dev", 
"services.serving.knative.dev", " [...]
 
+// OLM CatalogSources
+var OLMCatalogSourcesMap = map[string]string{"operatorhubio-catalog": "olm", 
"community-operators": "openshift-marketplace"}
+
+var SonataFlowOperatorName = "sonataflow-operator"
+
+
 const (
        QuarkusMavenPlugin                          = "quarkus-maven-plugin"
        QuarkusKubernetesExtension                  = "quarkus-kubernetes"
diff --git a/packages/kn-plugin-workflow/pkg/openshift/operation.go 
b/packages/kn-plugin-workflow/pkg/openshift/operation.go
new file mode 100644
index 00000000000..991d3863288
--- /dev/null
+++ b/packages/kn-plugin-workflow/pkg/openshift/operation.go
@@ -0,0 +1,51 @@
+/*
+ * 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 openshift
+
+import (
+       "fmt"
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common"
+)
+
+func CheckOCInstalled() error {
+       fmt.Println("✅ Checking if Openshift CLI installed...")
+       ocCheck := common.ExecCommand("oc", "version")
+       err := ocCheck.Run()
+       if err != nil {
+               fmt.Println("ERROR: Openshift CLI not found.")
+               return fmt.Errorf("rror while checking if Openshift CLI is 
installed: %w", err)
+       }
+       return nil
+}
+
+func CheckPermissions() error {
+       fmt.Println("✅ Checking permissions...")
+       namespace := "sonataflow-operator-system"
+       checkPermissions := common.ExecCommand("oc", "auth", "can-i", "create", 
"subscription", "-n", namespace)
+       result, err := checkPermissions.CombinedOutput()
+       if err != nil {
+               return fmt.Errorf("error while checking permissions: %w", err)
+       }
+       if string(result) != "yes\n" {
+               return fmt.Errorf("you don't have the required permissions to 
create a subscription in the namespace %s", namespace)
+       }
+
+       return nil
+}
\ No newline at end of file
diff --git a/packages/kn-plugin-workflow/pkg/root/root.go 
b/packages/kn-plugin-workflow/pkg/root/root.go
index 343b9ecab6e..be2aed47d5e 100644
--- a/packages/kn-plugin-workflow/pkg/root/root.go
+++ b/packages/kn-plugin-workflow/pkg/root/root.go
@@ -21,6 +21,7 @@ package root
 
 import (
        "fmt"
+       
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/operator"
        
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/specs"
 
        
"github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/quarkus"
@@ -78,6 +79,7 @@ func NewRootCommand(cfg RootCmdConfig) *cobra.Command {
        cmd.AddCommand(quarkus.NewQuarkusCommand())
        cmd.AddCommand(command.NewVersionCommand(cfg.Version))
        cmd.AddCommand(specs.SpecsCommand())
+       cmd.AddCommand(operator.NewOperatorCommand())
 
        cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
                runRootHelp(cmd, args)
diff --git a/packages/kn-plugin-workflow/pkg/root/root_test.go 
b/packages/kn-plugin-workflow/pkg/root/root_test.go
index eb901e24f19..fd5a3f60d1c 100644
--- a/packages/kn-plugin-workflow/pkg/root/root_test.go
+++ b/packages/kn-plugin-workflow/pkg/root/root_test.go
@@ -50,6 +50,7 @@ func TestNewRootCommand(t *testing.T) {
                        "undeploy",
                        "gen-manifest",
                        "version",
+                       "operator",
                }
 
                cmd := NewRootCommand(cfgTestInputRoot)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to