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]