From c9430db462e2b4a19f8028d24307b12e36ed332a Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Wed, 18 Jan 2023 14:10:39 -0500 Subject: [PATCH] add experimental OLMv1 support behind envvar toggle Signed-off-by: Joe Lanford --- go.mod | 5 +- go.sum | 14 ++-- .../cmd/internal/olmv1/operator_install.go | 30 ++++++++ .../cmd/internal/olmv1/operator_uninstall.go | 34 +++++++++ internal/cmd/root.go | 39 ++++++----- .../experimental/action/operator_install.go | 67 ++++++++++++++++++ .../experimental/action/operator_uninstall.go | 70 +++++++++++++++++++ pkg/action/config.go | 2 + 8 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 internal/cmd/internal/olmv1/operator_install.go create mode 100644 internal/cmd/internal/olmv1/operator_uninstall.go create mode 100644 internal/pkg/experimental/action/operator_install.go create mode 100644 internal/pkg/experimental/action/operator_uninstall.go diff --git a/go.mod b/go.mod index 9e295a0..6b572e0 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,14 @@ go 1.19 require ( github.com/containerd/containerd v1.6.10 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.20.1 + github.com/onsi/gomega v1.22.1 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/operator-framework/api v0.17.3 + github.com/operator-framework/operator-controller v0.0.0-20230117211241-081c4b017b63 github.com/operator-framework/operator-lifecycle-manager v0.23.1 github.com/operator-framework/operator-registry v1.17.5 github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.4.0 + github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 k8s.io/api v0.25.4 k8s.io/apiextensions-apiserver v0.25.4 diff --git a/go.sum b/go.sum index f8c607f..9e8b109 100644 --- a/go.sum +++ b/go.sum @@ -286,7 +286,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= @@ -804,7 +804,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= +github.com/onsi/ginkgo/v2 v2.3.1 h1:8SbseP7qM32WcvE6VaN6vfXxv698izmsJ1UQX9ve7T8= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -816,8 +816,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -850,6 +850,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/operator-framework/api v0.7.1/go.mod h1:L7IvLd/ckxJEJg/t4oTTlnHKAJIP/p51AvEslW3wYdY= github.com/operator-framework/api v0.17.3 h1:wddE1SLKTNiIzwt28DbBIO+vPG2GOV6dkB9xBkDfT3o= github.com/operator-framework/api v0.17.3/go.mod h1:34tb98EwTN5SZLkgoxwvRkhMJKLHUWHOrrcv1ZwvEeA= +github.com/operator-framework/operator-controller v0.0.0-20230117211241-081c4b017b63 h1:WbLynlU3tCtiiRZA9S+k8qvkHvSlMWfqeA9Q9/PWK88= +github.com/operator-framework/operator-controller v0.0.0-20230117211241-081c4b017b63/go.mod h1:fxBWcATKNalFgRs89Hhyoo0gUydqW3A0jnYlGkKsAxU= github.com/operator-framework/operator-lifecycle-manager v0.23.1 h1:Xw2ml1T4W2ieoFaVwanW/eFlZ11yAOJZUpUI8RLSql8= github.com/operator-framework/operator-lifecycle-manager v0.23.1/go.mod h1:q/QgVi/WooEyOFw8ipQrb2A/InjM4djCwPf7IlCpSOQ= github.com/operator-framework/operator-registry v1.17.5 h1:LR8m1rFz5Gcyje8WK6iYt+gIhtzqo52zMRALdmTYHT0= @@ -957,8 +959,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= diff --git a/internal/cmd/internal/olmv1/operator_install.go b/internal/cmd/internal/olmv1/operator_install.go new file mode 100644 index 0000000..f4e945b --- /dev/null +++ b/internal/cmd/internal/olmv1/operator_install.go @@ -0,0 +1,30 @@ +package olmv1 + +import ( + "github.com/spf13/cobra" + + "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" + experimentalaction "github.com/operator-framework/kubectl-operator/internal/pkg/experimental/action" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +func NewOperatorInstallCmd(cfg *action.Configuration) *cobra.Command { + i := experimentalaction.NewOperatorInstall(cfg) + i.Logf = log.Printf + + cmd := &cobra.Command{ + Use: "install ", + Short: "Install an operator", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + i.Package = args[0] + _, err := i.Run(cmd.Context()) + if err != nil { + log.Fatalf("failed to install operator: %v", err) + } + log.Printf("operator %q created", i.Package) + }, + } + + return cmd +} diff --git a/internal/cmd/internal/olmv1/operator_uninstall.go b/internal/cmd/internal/olmv1/operator_uninstall.go new file mode 100644 index 0000000..07ea7c1 --- /dev/null +++ b/internal/cmd/internal/olmv1/operator_uninstall.go @@ -0,0 +1,34 @@ +package olmv1 + +import ( + "github.com/spf13/cobra" + + "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" + experimentalaction "github.com/operator-framework/kubectl-operator/internal/pkg/experimental/action" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +func NewOperatorUninstallCmd(cfg *action.Configuration) *cobra.Command { + u := experimentalaction.NewOperatorUninstall(cfg) + u.Logf = log.Printf + + cmd := &cobra.Command{ + Use: "uninstall ", + Short: "Uninstall an operator", + Long: `Uninstall deletes the named Operator object. + +Warning: this command permanently deletes objects from the cluster. If the +uninstalled Operator 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 operator: %v", err) + } + log.Printf("deleted operator %q", u.Package) + }, + } + return cmd +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index fa27e0f..5a4b38d 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -2,13 +2,14 @@ package cmd import ( "context" - "reflect" + "fmt" + "os" "time" - "unsafe" "github.com/spf13/cobra" "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" + "github.com/operator-framework/kubectl-operator/internal/cmd/internal/olmv1" "github.com/operator-framework/kubectl-operator/pkg/action" ) @@ -17,16 +18,24 @@ func Execute() { log.Fatal(err) } } + +const ( + experimentalOLMV1EnvVar = "EXPERIMENTAL_USE_OLMV1_APIS" +) + func newCmd() *cobra.Command { cmd := &cobra.Command{ Use: "operator", Short: "Manage operators in a cluster from the command line", - Long: `Manage operators in a cluster from the command line. + Long: fmt.Sprintf(`Manage operators in a cluster from the command line. kubectl operator helps you manage operator installations in your cluster. It can install and uninstall operator catalogs, list operators available for installation, and install and uninstall -operators from the installed catalogs.`, +operators from the installed catalogs. + +To try out experimental OLMv1 APIs, set "%s=on". +`, experimentalOLMV1EnvVar), } var ( @@ -42,18 +51,21 @@ operators from the installed catalogs.`, cmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { var ctx context.Context ctx, cancel = context.WithTimeout(cmd.Context(), timeout) - - // This sets the unexported cmd.ctx value using unsafe. If - // https://github.com/spf13/cobra/pull/1118 gets merged, we - // should use cmd.SetContext() instead. - setContext(cmd, ctx) - + cmd.SetContext(ctx) return cfg.Load() } cmd.PersistentPostRun = func(command *cobra.Command, _ []string) { cancel() } + if v := os.Getenv(experimentalOLMV1EnvVar); v == "on" { + cmd.AddCommand( + olmv1.NewOperatorInstallCmd(&cfg), + olmv1.NewOperatorUninstallCmd(&cfg), + ) + return cmd + } + cmd.AddCommand( newCatalogCmd(&cfg), newOperatorInstallCmd(&cfg), @@ -68,10 +80,3 @@ operators from the installed catalogs.`, return cmd } - -func setContext(cmd *cobra.Command, ctx context.Context) { //nolint:golint - rs := reflect.ValueOf(cmd).Elem() - rf := rs.FieldByName("ctx") - rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() - rf.Set(reflect.ValueOf(ctx)) -} diff --git a/internal/pkg/experimental/action/operator_install.go b/internal/pkg/experimental/action/operator_install.go new file mode 100644 index 0000000..5874f9d --- /dev/null +++ b/internal/pkg/experimental/action/operator_install.go @@ -0,0 +1,67 @@ +package action + +import ( + "context" + "fmt" + "time" + + olmv1 "github.com/operator-framework/operator-controller/api/v1alpha1" + "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" + + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +type OperatorInstall struct { + config *action.Configuration + + Package string + + Logf func(string, ...interface{}) +} + +func NewOperatorInstall(cfg *action.Configuration) *OperatorInstall { + return &OperatorInstall{ + config: cfg, + Logf: func(string, ...interface{}) {}, + } +} + +func (i *OperatorInstall) Run(ctx context.Context) (*olmv1.Operator, 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 Operator + // object. + + opKey := types.NamespacedName{Name: i.Package} + op := &olmv1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: olmv1.OperatorSpec{PackageName: i.Package}, + } + if err := i.config.Client.Create(ctx, op); err != nil { + return nil, err + } + + // TODO(developer): Improve the logic in this poll wait once the Operator 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 Operator + // stops progressing. + + if err := wait.PollImmediateUntil(time.Millisecond*250, func() (bool, error) { + if err := i.config.Client.Get(ctx, opKey, op); err != nil { + return false, err + } + readyCondition := meta.FindStatusCondition(op.Status.Conditions, olmv1.TypeReady) + if readyCondition == nil { + return false, nil + } + return true, nil + }, ctx.Done()); err != nil { + return nil, fmt.Errorf("waiting for operator to become ready: %v", err) + } + + return op, nil +} diff --git a/internal/pkg/experimental/action/operator_uninstall.go b/internal/pkg/experimental/action/operator_uninstall.go new file mode 100644 index 0000000..9b8b3a8 --- /dev/null +++ b/internal/pkg/experimental/action/operator_uninstall.go @@ -0,0 +1,70 @@ +package action + +import ( + "context" + "fmt" + "strings" + "time" + + olmv1 "github.com/operator-framework/operator-controller/api/v1alpha1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +type OperatorUninstall struct { + config *action.Configuration + + Package string + + Logf func(string, ...interface{}) +} + +func NewOperatorUninstall(cfg *action.Configuration) *OperatorUninstall { + return &OperatorUninstall{ + config: cfg, + Logf: func(string, ...interface{}) {}, + } +} + +func (u *OperatorUninstall) Run(ctx context.Context) error { + opKey := types.NamespacedName{Name: u.Package} + op := &olmv1.Operator{} + op.SetName(opKey.Name) + op.SetGroupVersionKind(olmv1.GroupVersion.WithKind("Operator")) + + 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) +} + +func objectKeyForObject(obj client.Object) types.NamespacedName { + return types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + } +} + +func waitForDeletion(ctx context.Context, cl client.Client, objs ...client.Object) error { + for _, obj := range objs { + obj := obj + lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) + key := objectKeyForObject(obj) + if err := wait.PollImmediateUntil(250*time.Millisecond, func() (bool, error) { + if err := cl.Get(ctx, key, obj); apierrors.IsNotFound(err) { + return true, nil + } else if err != nil { + return false, err + } + return false, nil + }, ctx.Done()); err != nil { + return fmt.Errorf("wait for %s %q deleted: %v", lowerKind, key.Name, err) + } + } + return nil +} diff --git a/pkg/action/config.go b/pkg/action/config.go index 5681a68..ec9625c 100644 --- a/pkg/action/config.go +++ b/pkg/action/config.go @@ -5,6 +5,7 @@ import ( v1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" + olmv1 "github.com/operator-framework/operator-controller/api/v1alpha1" operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" "github.com/spf13/pflag" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -20,6 +21,7 @@ func NewScheme() (*runtime.Scheme, error) { operatorsv1.AddToScheme, v1.AddToScheme, apiextv1.AddToScheme, + olmv1.AddToScheme, } { if err := f(sch); err != nil { return nil, err