From 3e6324f9ed07e3c245d6d0c6f4bcfc52fe2fa16f Mon Sep 17 00:00:00 2001 From: Lalatendu Mohanty Date: Tue, 18 Mar 2025 17:36:28 -0400 Subject: [PATCH] Code for extension install and delete Signed-off-by: Lalatendu Mohanty --- Makefile | 4 + .../cmd/internal/olmv1/extension_delete.go | 50 +++++++ .../cmd/internal/olmv1/extension_install.go | 19 ++- .../cmd/internal/olmv1/extension_uninstall.go | 34 ----- internal/cmd/olmv1.go | 15 +- internal/pkg/v1/action/extension_delete.go | 81 +++++++++++ .../pkg/v1/action/extension_delete_test.go | 111 +++++++++++++++ internal/pkg/v1/action/extension_install.go | 129 +++++++++++++----- .../pkg/v1/action/extension_install_test.go | 63 +++++++++ internal/pkg/v1/action/extension_uninstall.go | 42 ------ 10 files changed, 429 insertions(+), 119 deletions(-) create mode 100644 internal/cmd/internal/olmv1/extension_delete.go delete mode 100644 internal/cmd/internal/olmv1/extension_uninstall.go create mode 100644 internal/pkg/v1/action/extension_delete.go create mode 100644 internal/pkg/v1/action/extension_delete_test.go create mode 100644 internal/pkg/v1/action/extension_install_test.go delete mode 100644 internal/pkg/v1/action/extension_uninstall.go diff --git a/Makefile b/Makefile index 3c7d2080..1f41e641 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,10 @@ gen-demo: lint: $(GOLANGCI_LINT) $(GOLANGCI_LINT) --timeout 3m run +.PHONY: fix-lint-issues +lint-fix: $(GOLANGCI_LINT) #HELP Run golangci linter. + $(GOLANGCI_LINT) run --fix --timeout 3m + .PHONY: release RELEASE_ARGS?=release --clean --snapshot release: $(GORELEASER) diff --git a/internal/cmd/internal/olmv1/extension_delete.go b/internal/cmd/internal/olmv1/extension_delete.go new file mode 100644 index 00000000..bc1ff940 --- /dev/null +++ b/internal/cmd/internal/olmv1/extension_delete.go @@ -0,0 +1,50 @@ +package olmv1 + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" + v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +func NewExtensionDeleteCmd(cfg *action.Configuration) *cobra.Command { + e := v1action.NewExtensionDelete(cfg) + e.Logf = log.Printf + + cmd := &cobra.Command{ + Use: "extension [extension_name]", + Aliases: []string{"extensions [extension_name]"}, + Short: "Delete an extension", + Long: `Warning: Permanently deletes the named cluster extension object. + If the extension contains CRDs, the CRDs will be deleted, which + cascades to the deletion of all operands.`, + Args: cobra.RangeArgs(0, 1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + extensions, err := e.Run(cmd.Context()) + if err != nil { + log.Fatalf("failed deleting extension: %v", err) + } + for _, extn := range extensions { + log.Printf("extension %q deleted", extn) + } + + return + } + e.ExtensionName = args[0] + _, errs := e.Run(cmd.Context()) + if errs != nil { + log.Fatalf("delete extension: %v", errs) + } + log.Printf("deleted extension %q", e.ExtensionName) + }, + } + bindExtensionDeleteFlags(cmd.Flags(), e) + return cmd +} + +func bindExtensionDeleteFlags(fs *pflag.FlagSet, e *v1action.ExtensionDeletion) { + fs.BoolVarP(&e.DeleteAll, "all", "a", false, "delete all extensions") +} diff --git a/internal/cmd/internal/olmv1/extension_install.go b/internal/cmd/internal/olmv1/extension_install.go index 0b6032cb..6bee6f00 100644 --- a/internal/cmd/internal/olmv1/extension_install.go +++ b/internal/cmd/internal/olmv1/extension_install.go @@ -1,7 +1,10 @@ package olmv1 import ( + "time" + "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action" @@ -13,18 +16,28 @@ func NewExtensionInstallCmd(cfg *action.Configuration) *cobra.Command { i.Logf = log.Printf cmd := &cobra.Command{ - Use: "install ", + Use: "extension ", Short: "Install an extension", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - i.Package = args[0] + i.ExtensionName = args[0] _, err := i.Run(cmd.Context()) if err != nil { log.Fatalf("failed to install extension: %v", err) } - log.Printf("extension %q created", i.Package) + log.Printf("extension %q created", i.ExtensionName) }, } + bindOperatorInstallFlags(cmd.Flags(), i) return cmd } + +func bindOperatorInstallFlags(fs *pflag.FlagSet, i *v1action.ExtensionInstall) { + fs.StringVarP(&i.Namespace.Name, "namespace", "n", "", "namespace to install the operator in") + fs.StringVarP(&i.PackageName, "package-name", "p", "", "package name of the operator to install") + fs.StringSliceVarP(&i.Channels, "channels", "c", []string{}, "channels which would be to used for getting updates e.g --channels \"stable,dev-preview,preview\"") + fs.StringVarP(&i.Version, "version", "v", "", "version (or version range) from which to resolve bundles") + fs.StringVarP(&i.ServiceAccount, "service-account", "s", "default", "service account name to use for the extension installation") + fs.DurationVarP(&i.CleanupTimeout, "cleanup-timeout", "d", time.Minute, "the amount of time to wait before cancelling cleanup after a failed creation attempt") +} diff --git a/internal/cmd/internal/olmv1/extension_uninstall.go b/internal/cmd/internal/olmv1/extension_uninstall.go deleted file mode 100644 index 3bc54194..00000000 --- a/internal/cmd/internal/olmv1/extension_uninstall.go +++ /dev/null @@ -1,34 +0,0 @@ -package olmv1 - -import ( - "github.com/spf13/cobra" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -func NewExtensionUninstallCmd(cfg *action.Configuration) *cobra.Command { - u := v1action.NewExtensionUninstall(cfg) - u.Logf = log.Printf - - cmd := &cobra.Command{ - Use: "uninstall ", - Short: "Uninstall an extension", - Long: `Uninstall deletes the named extension object. - -Warning: this command permanently deletes objects from the cluster. If the -uninstalled extension bundle contains CRDs, the CRDs will be deleted, which -cascades to the deletion of all operands. -`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - u.Package = args[0] - if err := u.Run(cmd.Context()); err != nil { - log.Fatalf("uninstall extension: %v", err) - } - log.Printf("deleted extension %q", u.Package) - }, - } - return cmd -} diff --git a/internal/cmd/olmv1.go b/internal/cmd/olmv1.go index 8791d3e4..fad8b763 100644 --- a/internal/cmd/olmv1.go +++ b/internal/cmd/olmv1.go @@ -36,7 +36,10 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command { Short: "Delete a resource", Long: "Delete a resource", } - deleteCmd.AddCommand(olmv1.NewCatalogDeleteCmd(cfg)) + deleteCmd.AddCommand( + olmv1.NewCatalogDeleteCmd(cfg), + olmv1.NewExtensionDeleteCmd(cfg), + ) updateCmd := &cobra.Command{ Use: "update", @@ -47,9 +50,15 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command { olmv1.NewExtensionUpdateCmd(cfg), ) + installCmd := &cobra.Command{ + Use: "install", + Short: "Install a resource", + Long: "Install a resource", + } + installCmd.AddCommand(olmv1.NewExtensionInstallCmd(cfg)) + cmd.AddCommand( - olmv1.NewExtensionInstallCmd(cfg), - olmv1.NewExtensionUninstallCmd(cfg), + installCmd, getCmd, createCmd, deleteCmd, diff --git a/internal/pkg/v1/action/extension_delete.go b/internal/pkg/v1/action/extension_delete.go new file mode 100644 index 00000000..488fce4a --- /dev/null +++ b/internal/pkg/v1/action/extension_delete.go @@ -0,0 +1,81 @@ +package action + +import ( + "context" + "errors" + "fmt" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + olmv1 "github.com/operator-framework/operator-controller/api/v1" + + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +// ExtensionDeletion deletes an extension or all extensions in the cluster +type ExtensionDeletion struct { + config *action.Configuration + ExtensionName string + DeleteAll bool + Logf func(string, ...interface{}) +} + +// NewExtensionDelete creates a new ExtensionDeletion action +// with the given configuration +// and a logger function that can be used to log messages +func NewExtensionDelete(cfg *action.Configuration) *ExtensionDeletion { + return &ExtensionDeletion{ + config: cfg, + Logf: func(string, ...interface{}) {}, + } +} + +func (u *ExtensionDeletion) Run(ctx context.Context) ([]string, error) { + if u.DeleteAll && u.ExtensionName != "" { + return nil, fmt.Errorf("cannot specify both --all and an extension name") + } + if !u.DeleteAll { + return u.deleteExtension(ctx, u.ExtensionName) + } + + // delete all existing extensions + return u.deleteAllExtensions(ctx) +} + +// deleteExtension deletes a single extension in the cluster +func (u *ExtensionDeletion) deleteExtension(ctx context.Context, extName string) ([]string, error) { + op := &olmv1.ClusterExtension{} + op.SetName(extName) + op.SetGroupVersionKind(olmv1.GroupVersion.WithKind("ClusterExtension")) + lowerKind := strings.ToLower(op.GetObjectKind().GroupVersionKind().Kind) + err := u.config.Client.Delete(ctx, op) + if err != nil { + if !apierrors.IsNotFound(err) { + return []string{u.ExtensionName}, fmt.Errorf("delete %s %q: %v", lowerKind, op.GetName(), err) + } + return nil, err + } + // wait for deletion + return []string{u.ExtensionName}, waitForDeletion(ctx, u.config.Client, op) +} + +// deleteAllExtensions deletes all extensions in the cluster +func (u *ExtensionDeletion) deleteAllExtensions(ctx context.Context) ([]string, error) { + var extensionList olmv1.ClusterExtensionList + if err := u.config.Client.List(ctx, &extensionList); err != nil { + return nil, err + } + if len(extensionList.Items) == 0 { + return nil, ErrNoResourcesFound + } + errs := make([]error, 0, len(extensionList.Items)) + names := make([]string, 0, len(extensionList.Items)) + for _, extension := range extensionList.Items { + names = append(names, extension.Name) + if _, err := u.deleteExtension(ctx, extension.Name); err != nil { + errs = append(errs, fmt.Errorf("failed deleting extension %q: %w", extension.Name, err)) + } + } + return names, errors.Join(errs...) +} diff --git a/internal/pkg/v1/action/extension_delete_test.go b/internal/pkg/v1/action/extension_delete_test.go new file mode 100644 index 00000000..5b4f4af4 --- /dev/null +++ b/internal/pkg/v1/action/extension_delete_test.go @@ -0,0 +1,111 @@ +package action_test + +import ( + "context" + "slices" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + olmv1 "github.com/operator-framework/operator-controller/api/v1" + + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +var _ = Describe("ExtensionDelete", func() { + setupEnv := func(extensions ...client.Object) action.Configuration { + var cfg action.Configuration + + sch, err := action.NewScheme() + Expect(err).To(BeNil()) + + cl := fake.NewClientBuilder(). + WithObjects(extensions...). + WithScheme(sch). + Build() + cfg.Scheme = sch + cfg.Client = cl + + return cfg + } + + It("fails because of both extension name and --all specifier being present", func() { + cfg := setupEnv(setupTestExtensions(2)...) + + deleter := internalaction.NewExtensionDelete(&cfg) + deleter.ExtensionName = "foo" + deleter.DeleteAll = true + extNames, err := deleter.Run(context.TODO()) + Expect(err).NotTo(BeNil()) + Expect(extNames).To(BeEmpty()) + + validateExistingExtensions(cfg.Client, []string{"ext1", "ext2"}) + }) + + It("fails deleting a non-existent extensions", func() { + cfg := setupEnv(setupTestExtensions(2)...) + + deleter := internalaction.NewExtensionDelete(&cfg) + deleter.ExtensionName = "does-not-exist" + extNames, err := deleter.Run(context.TODO()) + Expect(err).NotTo(BeNil()) + Expect(extNames).To(BeEmpty()) + + validateExistingExtensions(cfg.Client, []string{"ext1", "ext2"}) + }) + + It("successfully deletes an existing extension", func() { + cfg := setupEnv(setupTestExtensions(3)...) + + deleter := internalaction.NewExtensionDelete(&cfg) + deleter.ExtensionName = "ext2" + _, err := deleter.Run(context.TODO()) + Expect(err).To(BeNil()) + + validateExistingExtensions(cfg.Client, []string{"ext1", "ext3"}) + }) + + It("fails deleting all extensions because there are none", func() { + cfg := setupEnv() + + deleter := internalaction.NewExtensionDelete(&cfg) + deleter.DeleteAll = true + extNames, err := deleter.Run(context.TODO()) + Expect(err).NotTo(BeNil()) + Expect(extNames).To(BeEmpty()) + + validateExistingExtensions(cfg.Client, []string{}) + }) + + It("successfully deletes all extensions", func() { + cfg := setupEnv(setupTestExtensions(3)...) + + deleter := internalaction.NewExtensionDelete(&cfg) + deleter.DeleteAll = true + extNames, err := deleter.Run(context.TODO()) + Expect(err).To(BeNil()) + Expect(extNames).To(ContainElements([]string{"ext1", "ext2", "ext3"})) + + validateExistingExtensions(cfg.Client, []string{}) + }) +}) + +// validateExistingExtensions compares the names of the existing extensions with the wanted names +// and ensures that all wanted names are present in the existing extensions +func validateExistingExtensions(c client.Client, wantedNames []string) { + var extensionList olmv1.ClusterExtensionList + err := c.List(context.TODO(), &extensionList) + Expect(err).To(BeNil()) + + extensions := extensionList.Items + Expect(extensions).To(HaveLen(len(wantedNames))) + for _, wantedName := range wantedNames { + Expect(slices.ContainsFunc(extensions, func(ext olmv1.ClusterExtension) bool { + return ext.Name == wantedName + })).To(BeTrue()) + } +} diff --git a/internal/pkg/v1/action/extension_install.go b/internal/pkg/v1/action/extension_install.go index 00fce350..05126791 100644 --- a/internal/pkg/v1/action/extension_install.go +++ b/internal/pkg/v1/action/extension_install.go @@ -2,24 +2,33 @@ package action import ( "context" + "errors" "fmt" + "time" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" - olmv1 "github.com/operator-framework/operator-controller/api/v1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/kubectl-operator/pkg/action" ) type ExtensionInstall struct { - config *action.Configuration - - Package string - - Logf func(string, ...interface{}) + config *action.Configuration + ExtensionName string + Namespace NamespaceConfig + PackageName string + Channels []string + Version string + ServiceAccount string + CleanupTimeout time.Duration + Logf func(string, ...interface{}) +} +type NamespaceConfig struct { + Name string } func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall { @@ -29,47 +38,93 @@ func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall { } } -func (i *ExtensionInstall) Run(ctx context.Context) (*olmv1.ClusterExtension, error) { - // TODO(developer): Lookup package information when the OLMv1 equivalent of the - // packagemanifests API is available. That way, we can check to see if the - // package is actually available to the cluster before creating the Extension - // object. - - opKey := types.NamespacedName{Name: i.Package} - op := &olmv1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: olmv1.ClusterExtensionSpec{ - Source: olmv1.SourceConfig{ - SourceType: "Catalog", - Catalog: &olmv1.CatalogFilter{ - PackageName: i.Package, +func (i *ExtensionInstall) buildClusterExtension() ocv1.ClusterExtension { + extension := ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: i.ExtensionName, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: ocv1.SourceTypeCatalog, + Catalog: &ocv1.CatalogFilter{ + PackageName: i.PackageName, + Version: i.Version, }, }, + Namespace: i.Namespace.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: i.ServiceAccount, + }, }, } - if err := i.config.Client.Create(ctx, op); err != nil { - return nil, err + + return extension +} + +func (i *ExtensionInstall) Run(ctx context.Context) (*ocv1.ClusterExtension, error) { + extension := i.buildClusterExtension() + + // Add Channels to extension + if len(i.Channels) > 0 { + extension.Spec.Source.Catalog.Channels = i.Channels } - // TODO(developer): Improve the logic in this poll wait once the Extension reconciler - // and conditions types and reasons are improved. For now, this will stop waiting as - // soon as a Ready condition is found, but we should probably wait until the Extension - // stops progressing. - // All Types will exist, so Ready may have a false Status. So, wait until - // Type=Ready,Status=True happens + // TODO: Add CatalogSelector to extension + + // Create the extension + if err := i.config.Client.Create(ctx, &extension); err != nil { + return nil, err + } + clusterExtension, err := i.waitForExtensionInstall(ctx) + if err != nil { + cleanupCtx, cancelCleanup := context.WithTimeout(context.Background(), i.CleanupTimeout) + defer cancelCleanup() + cleanupErr := i.cleanup(cleanupCtx) + return nil, errors.Join(err, cleanupErr) + } + return clusterExtension, nil +} - if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) { - if err := i.config.Client.Get(conditionCtx, opKey, op); err != nil { +// waitForClusterExtensionInstalled waits for the ClusterExtension to be installed +// and returns the ClusterExtension object +func (i *ExtensionInstall) waitForExtensionInstall(ctx context.Context) (*ocv1.ClusterExtension, error) { + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: i.ExtensionName, + }, + } + errMsg := "" + key := client.ObjectKeyFromObject(clusterExtension) + if err := wait.PollUntilContextCancel(ctx, time.Millisecond*250, true, func(conditionCtx context.Context) (bool, error) { + if err := i.config.Client.Get(conditionCtx, key, clusterExtension); err != nil { return false, err } - installedCondition := meta.FindStatusCondition(op.Status.Conditions, olmv1.TypeInstalled) - if installedCondition != nil && installedCondition.Status == metav1.ConditionTrue { - return true, nil + progressingCondition := meta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + if progressingCondition != nil && progressingCondition.Reason != ocv1.ReasonSucceeded { + errMsg = progressingCondition.Message + return false, nil } - return false, nil + if !meta.IsStatusConditionPresentAndEqual(clusterExtension.Status.Conditions, ocv1.TypeInstalled, metav1.ConditionTrue) { + return false, nil + } + return true, nil }); err != nil { - return nil, fmt.Errorf("waiting for extension to become ready: %v", err) + if errMsg == "" { + errMsg = err.Error() + } + return nil, fmt.Errorf("cluster extension %q did not finish installing: %s", clusterExtension.Name, errMsg) } + return clusterExtension, nil +} - return op, nil +func (i *ExtensionInstall) cleanup(ctx context.Context) error { + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: i.ExtensionName, + }, + } + if err := waitForDeletion(ctx, i.config.Client, clusterExtension); err != nil { + return fmt.Errorf("delete clusterextension %q: %v", i.ExtensionName, err) + } + return nil } diff --git a/internal/pkg/v1/action/extension_install_test.go b/internal/pkg/v1/action/extension_install_test.go new file mode 100644 index 00000000..a0cef177 --- /dev/null +++ b/internal/pkg/v1/action/extension_install_test.go @@ -0,0 +1,63 @@ +package action_test + +import ( + "context" + "errors" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +var _ = Describe("InstallExtension", func() { + extensionName := "testExtension" + packageName := "testPackage" + packageVersion := "1.0.0" + serviceAccount := "testServiceAccount" + namespace := "testNamespace" + + expectedExtension := ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: extensionName, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: ocv1.SourceTypeCatalog, + Catalog: &ocv1.CatalogFilter{ + PackageName: packageName, + Version: packageVersion, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + It("Cluster extension install fails", func() { + expectedErr := errors.New("extension install failed") + testClient := fakeClient{createErr: expectedErr} + Expect(testClient.Initialize()).To(Succeed()) + + installer := internalaction.NewExtensionInstall(&action.Configuration{Client: testClient}) + installer.ExtensionName = expectedExtension.Name + installer.PackageName = expectedExtension.Spec.Source.Catalog.PackageName + installer.Channels = expectedExtension.Spec.Source.Catalog.Channels + installer.Version = expectedExtension.Spec.Source.Catalog.Version + installer.ServiceAccount = expectedExtension.Spec.ServiceAccount.Name + installer.CleanupTimeout = 1 * time.Minute + installer.Namespace.Name = expectedExtension.Spec.Namespace + _, err := installer.Run(context.TODO()) + + Expect(err).NotTo(BeNil()) + Expect(err).To(MatchError(expectedErr)) + Expect(testClient.createCalled).To(Equal(1)) + }) +}) diff --git a/internal/pkg/v1/action/extension_uninstall.go b/internal/pkg/v1/action/extension_uninstall.go deleted file mode 100644 index 59d58563..00000000 --- a/internal/pkg/v1/action/extension_uninstall.go +++ /dev/null @@ -1,42 +0,0 @@ -package action - -import ( - "context" - "fmt" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - - olmv1 "github.com/operator-framework/operator-controller/api/v1" - - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -type ExtensionUninstall struct { - config *action.Configuration - - Package string - - Logf func(string, ...interface{}) -} - -func NewExtensionUninstall(cfg *action.Configuration) *ExtensionUninstall { - return &ExtensionUninstall{ - config: cfg, - Logf: func(string, ...interface{}) {}, - } -} - -func (u *ExtensionUninstall) Run(ctx context.Context) error { - opKey := types.NamespacedName{Name: u.Package} - op := &olmv1.ClusterExtension{} - op.SetName(opKey.Name) - op.SetGroupVersionKind(olmv1.GroupVersion.WithKind("Extension")) - - lowerKind := strings.ToLower(op.GetObjectKind().GroupVersionKind().Kind) - if err := u.config.Client.Delete(ctx, op); err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("delete %s %q: %v", lowerKind, op.GetName(), err) - } - return waitForDeletion(ctx, u.config.Client, op) -}