diff --git a/changelog/fragments/refactor-cleanup.yaml b/changelog/fragments/refactor-cleanup.yaml new file mode 100644 index 0000000000..623295c6ae --- /dev/null +++ b/changelog/fragments/refactor-cleanup.yaml @@ -0,0 +1,18 @@ +entries: + - description: Updated `operator-sdk cleanup` command to be more generic + kind: change + breaking: true + migration: + header: Update usage of `operator-sdk cleanup` + body: | + The `operator-sdk cleanup packagemanifests` command has been + removed and replaced with a simpler `operator-sdk cleanup` + command. + + Update usages of `operator-sdk cleanup packagemanifests` to + use `operator-sdk cleanup `. + + The value for `` can be found in the `*.package.yaml` + file in the root of your packagemanifests folder. It is typically + your project name. + diff --git a/internal/cmd/operator-sdk/cleanup/cmd.go b/internal/cmd/operator-sdk/cleanup/cmd.go index 34d61c3e52..edc9600753 100644 --- a/internal/cmd/operator-sdk/cleanup/cmd.go +++ b/internal/cmd/operator-sdk/cleanup/cmd.go @@ -15,24 +15,42 @@ package cleanup import ( + "context" + "time" + + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/operator-framework/operator-sdk/internal/cmd/operator-sdk/cleanup/packagemanifests" + "github.com/operator-framework/operator-sdk/internal/operator" ) func NewCmd() *cobra.Command { + var timeout time.Duration + cfg := &operator.Configuration{} + cfg.Log = log.Infof cmd := &cobra.Command{ - Use: "cleanup", + Use: "cleanup ", Short: "Clean up an Operator deployed with the 'run' subcommand", - Long: `This command has subcommands that will destroy an Operator deployed with OLM. -Currently only the package manifests format is supported via the 'packagemanifests' subcommand. -Run 'operator-sdk cleanup --help' for more information. -`, - } + Long: "This command has subcommands that will destroy an Operator deployed with OLM.", + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { + return cfg.Load() + }, + Run: func(cmd *cobra.Command, args []string) { + u := operator.NewUninstall(cfg) + u.Package = args[0] - cmd.AddCommand( - packagemanifests.NewCmd(), - ) + ctx, cancel := context.WithTimeout(cmd.Context(), timeout) + defer cancel() + + if err := u.Run(ctx); err != nil { + log.Fatalf("Uninstall operator: %v\n", err) + } + log.Infof("Operator %q uninstalled\n", u.Package) + }, + } + cmd.Flags().DurationVar(&timeout, "timeout", 2*time.Minute, "Time to wait for the command to complete before failing") + cfg.BindFlags(cmd.PersistentFlags()) return cmd } diff --git a/internal/cmd/operator-sdk/cleanup/packagemanifests/packagemanifests.go b/internal/cmd/operator-sdk/cleanup/packagemanifests/packagemanifests.go deleted file mode 100644 index 80dc1fe22f..0000000000 --- a/internal/cmd/operator-sdk/cleanup/packagemanifests/packagemanifests.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed 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 packagemanifests - -import ( - "fmt" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - olmoperator "github.com/operator-framework/operator-sdk/internal/olm/operator" -) - -type packagemanifestsCmd struct { - olmoperator.PackageManifestsCmd -} - -func NewCmd() *cobra.Command { - c := &packagemanifestsCmd{} - - cmd := &cobra.Command{ - Use: "packagemanifests", - Short: "Clean up an Operator in the package manifests format deployed with OLM", - Long: `'cleanup packagemanifests' destroys an Operator deployed with OLM using the 'run packagemanifests' command. -The command's argument must be set to a valid package manifests root directory, -ex. '/packagemanifests'.`, - PreRunE: func(cmd *cobra.Command, args []string) error { - return c.validate(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return c.run() - }, - } - - c.PackageManifestsCmd.AddToFlagSet(cmd.Flags()) - - return cmd -} - -func (c *packagemanifestsCmd) validate(args []string) error { - if len(args) > 0 { - if len(args) > 1 { - return fmt.Errorf("exactly one argument is required") - } - c.ManifestsDir = args[0] - } else { - c.ManifestsDir = "packagemanifests" - } - return nil -} - -func (c *packagemanifestsCmd) run() error { - log.Infof("Cleaning up operator in directory %s", c.ManifestsDir) - - if err := c.Cleanup(); err != nil { - log.Fatalf("Failed to clean up operator: %v", err) - } - return nil -} diff --git a/internal/olm/operator/internal/registry.go b/internal/olm/operator/internal/registry.go index 73399a02d1..4bf3c836ad 100644 --- a/internal/olm/operator/internal/registry.go +++ b/internal/olm/operator/internal/registry.go @@ -20,11 +20,13 @@ import ( "path" apimanifests "github.com/operator-framework/api/pkg/manifests" + "github.com/operator-framework/api/pkg/operators/v1alpha1" log "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" olmclient "github.com/operator-framework/operator-sdk/internal/olm/client" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" @@ -110,7 +112,7 @@ func (rr *RegistryResources) IsRegistryDataStale(ctx context.Context, namespace // CreatePackageManifestsRegistry creates all registry objects required to serve // manifests from rr.manifests in namespace. -func (rr *RegistryResources) CreatePackageManifestsRegistry(ctx context.Context, namespace string) error { +func (rr *RegistryResources) CreatePackageManifestsRegistry(ctx context.Context, catsrc *v1alpha1.CatalogSource, namespace string) error { pkgName := rr.Pkg.PackageName labels := makeRegistryLabels(pkgName) @@ -119,6 +121,14 @@ func (rr *RegistryResources) CreatePackageManifestsRegistry(ctx context.Context, return err } + catsrcKey := types.NamespacedName{ + Namespace: catsrc.Namespace, + Name: catsrc.Name, + } + if err := rr.Client.KubeClient.Get(ctx, catsrcKey, catsrc); err != nil { + return fmt.Errorf("get catalog source: %v", err) + } + // Objects to create. objs := make([]runtime.Object, 0, len(binaryDataByConfigMap)+2) // Options for creating a Deployment, since we need to mount all package @@ -129,6 +139,9 @@ func (rr *RegistryResources) CreatePackageManifestsRegistry(ctx context.Context, for cmName, binaryData := range binaryDataByConfigMap { cm := newConfigMap(cmName, namespace, withBinaryData(binaryData)) cm.SetLabels(labels) + if err := controllerutil.SetOwnerReference(catsrc, cm, olmclient.Scheme); err != nil { + return fmt.Errorf("set configmap %q owner reference: %v", cm.GetName(), err) + } objs = append(objs, cm) volName := k8sutil.TrimDNS1123Label(cmName + "-volume") @@ -141,8 +154,14 @@ func (rr *RegistryResources) CreatePackageManifestsRegistry(ctx context.Context, // Add registry Deployment and Service to objects. dep := newRegistryDeployment(pkgName, namespace, opts...) dep.SetLabels(labels) + if err := controllerutil.SetOwnerReference(catsrc, dep, olmclient.Scheme); err != nil { + return fmt.Errorf("set deployment %q owner reference: %v", dep.GetName(), err) + } service := newRegistryService(pkgName, namespace, withTCPPort("grpc", registryGRPCPort)) service.SetLabels(labels) + if err := controllerutil.SetOwnerReference(catsrc, service, olmclient.Scheme); err != nil { + return fmt.Errorf("set service %q owner reference: %v", service.GetName(), err) + } objs = append(objs, dep, service) if err := rr.Client.DoCreate(ctx, objs...); err != nil { diff --git a/internal/olm/operator/olm.go b/internal/olm/operator/olm.go index 4112ff2d07..9726ad0468 100644 --- a/internal/olm/operator/olm.go +++ b/internal/olm/operator/olm.go @@ -22,12 +22,10 @@ import ( operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/operator-framework/operator-sdk/internal/operator" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" ) -// General OperatorGroup for operators created with the SDK. -const sdkOperatorGroupName = "operator-sdk-og" - func getSubscriptionName(csvName string) string { name := k8sutil.FormatOperatorNameDNS1123(csvName) return fmt.Sprintf("%s-sub", name) @@ -94,15 +92,6 @@ func getCatalogSourceName(pkgName string) string { return fmt.Sprintf("%s-ocs", name) } -// withGRPC returns a function that sets the CatalogSource argument's -// server type to GRPC and address at addr. -func withGRPC(addr string) func(*operatorsv1alpha1.CatalogSource) { - return func(catsrc *operatorsv1alpha1.CatalogSource) { - catsrc.Spec.SourceType = operatorsv1alpha1.SourceTypeGrpc - catsrc.Spec.Address = addr - } -} - // newCatalogSource creates a new CatalogSource with a name derived from // pkgName, the package manifest's packageName, in namespace. opts will // be applied to the CatalogSource object. @@ -151,7 +140,7 @@ func newSDKOperatorGroup(namespace string, opts ...func(*operatorsv1.OperatorGro Kind: operatorsv1.OperatorGroupKind, }, ObjectMeta: metav1.ObjectMeta{ - Name: sdkOperatorGroupName, + Name: operator.SDKOperatorGroupName, Namespace: namespace, }, } diff --git a/internal/olm/operator/operator_manager.go b/internal/olm/operator/operator_manager.go index 0d291f0f35..12b3dea490 100644 --- a/internal/olm/operator/operator_manager.go +++ b/internal/olm/operator/operator_manager.go @@ -121,6 +121,7 @@ func (c *OperatorCmd) newManager() (*operatorManager, error) { m := &operatorManager{} // Cluster and operator namespace info. + // TODO(joelanford): Migrate this to use `internal/operator.Configuration` rc, ns, err := k8sutil.GetKubeconfigAndNamespace(c.KubeconfigPath) if err != nil { return nil, fmt.Errorf("failed to get namespace from kubeconfig %s: %w", c.KubeconfigPath, err) diff --git a/internal/olm/operator/packagemanifests.go b/internal/olm/operator/packagemanifests.go index a46568ffec..ca32eea8e6 100644 --- a/internal/olm/operator/packagemanifests.go +++ b/internal/olm/operator/packagemanifests.go @@ -31,7 +31,7 @@ type PackageManifestsCmd struct { // ManifestsDir is a directory containing 1..N package directories and // a package manifest. // Version can be set to the version of the desired operator package - // and Run()/Cleanup() will deploy that operator version. + // and Run() will deploy that operator version. ManifestsDir string // Version is the version of the operator to deploy. It must be // a semantic version, ex. 0.0.1. @@ -80,19 +80,3 @@ func (c *PackageManifestsCmd) Run() error { defer cancel() return m.run(ctx) } - -func (c *PackageManifestsCmd) Cleanup() (err error) { - c.initialize() - if err := c.validate(); err != nil { - return fmt.Errorf("validation error: %w", err) - } - m, err := c.newManager() - if err != nil { - return fmt.Errorf("error initializing operator manager: %w", err) - } - // Cleanups should clean up all resources, which includes the registry. - m.forceRegistry = true - ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) - defer cancel() - return m.cleanup(ctx) -} diff --git a/internal/olm/operator/packagemanifests_manager.go b/internal/olm/operator/packagemanifests_manager.go index 0804bb4b8b..ab27abf4ce 100644 --- a/internal/olm/operator/packagemanifests_manager.go +++ b/internal/olm/operator/packagemanifests_manager.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" internalregistry "github.com/operator-framework/operator-sdk/internal/olm/operator/internal" ) @@ -108,13 +109,21 @@ func (m *packageManifestsManager) run(ctx context.Context) (err error) { return fmt.Errorf("an operator with name %q is present and has resource errors\n%s", pkgName, status) } - if err = m.registryUp(ctx, m.namespace); err != nil { + // New CatalogSource. + catsrc := newCatalogSource(pkgName, m.namespace) + log.Info("Creating catalog source") + if err = m.client.DoCreate(ctx, catsrc); err != nil { + return fmt.Errorf("error creating catalog source: %w", err) + } + + if err = m.registryUp(ctx, catsrc, m.namespace); err != nil { return fmt.Errorf("error creating registry resources: %w", err) } - // New CatalogSource. - registryGRPCAddr := internalregistry.GetRegistryServiceAddr(pkgName, m.namespace) - catsrc := newCatalogSource(pkgName, m.namespace, withGRPC(registryGRPCAddr)) + if err := m.updateCatalogSource(ctx, pkgName, catsrc); err != nil { + return fmt.Errorf("error updating catalog source: %w", err) + } + // New Subscription. channel, err := getChannelForCSVName(m.pkg, csv.GetName()) if err != nil { @@ -126,7 +135,7 @@ func (m *packageManifestsManager) run(ctx context.Context) (err error) { // New SDK-managed OperatorGroup. og := newSDKOperatorGroup(m.namespace, withTargetNamespaces(m.targetNamespaces...)) - objects := []runtime.Object{catsrc, sub, og} + objects := []runtime.Object{sub, og} log.Info("Creating resources") if err = m.client.DoCreate(ctx, objects...); err != nil { return fmt.Errorf("error creating operator resources: %w", err) @@ -155,46 +164,7 @@ func (m *packageManifestsManager) run(ctx context.Context) (err error) { return nil } -func (m *packageManifestsManager) cleanup(ctx context.Context) (err error) { - pkgName := m.pkg.PackageName - bundle, err := getPackageForVersion(m.bundles, m.version) - if err != nil { - return fmt.Errorf("error getting package for version %s: %w", m.version, err) - } - csv := bundle.CSV - - if err = m.registryDown(ctx, m.namespace); err != nil { - return fmt.Errorf("error removing registry resources: %w", err) - } - - // Delete CatalogSource, Subscription, the SDK-managed OperatorGroup, and any bundle objects. - toDelete := []runtime.Object{ - newCatalogSource(pkgName, m.namespace), - newSubscription(csv.GetName(), m.namespace), - newSDKOperatorGroup(m.namespace), - } - for _, obj := range bundle.Objects { - objc := obj.DeepCopy() - objc.SetNamespace(m.namespace) - toDelete = append(toDelete, objc) - } - log.Info("Deleting resources") - if err = m.client.DoDelete(ctx, toDelete...); err != nil { - return fmt.Errorf("error deleting operator resources: %w", err) - } - - status := m.status(ctx, bundle.Objects...) - if installed, err := status.HasInstalledResources(); installed { - return fmt.Errorf("operator %q still exists", pkgName) - } else if err != nil { - return fmt.Errorf("operator %q still exists and has resource errors\n%s", pkgName, status) - } - log.Infof("OLM has successfully uninstalled %q and related resources have been deleted", csv.GetName()) - - return nil -} - -func (m packageManifestsManager) registryUp(ctx context.Context, namespace string) error { +func (m packageManifestsManager) registryUp(ctx context.Context, catsrc *operatorsv1alpha1.CatalogSource, namespace string) error { rr := internalregistry.RegistryResources{ Client: m.client, Pkg: m.pkg, @@ -218,30 +188,13 @@ func (m packageManifestsManager) registryUp(ctx context.Context, namespace strin } } log.Infof("Creating %s registry", m.pkg.PackageName) - if err := rr.CreatePackageManifestsRegistry(ctx, namespace); err != nil { + if err := rr.CreatePackageManifestsRegistry(ctx, catsrc, namespace); err != nil { return fmt.Errorf("error registering package: %w", err) } return nil } -func (m *packageManifestsManager) registryDown(ctx context.Context, namespace string) error { - rr := internalregistry.RegistryResources{ - Client: m.client, - Pkg: m.pkg, - Bundles: m.bundles, - } - - if m.forceRegistry { - log.Print("Deleting registry") - if err := rr.DeletePackageManifestsRegistry(ctx, namespace); err != nil { - return fmt.Errorf("error deleting registered package: %w", err) - } - } - - return nil -} - func getPackageForVersion(bundles []*apimanifests.Bundle, version string) (*apimanifests.Bundle, error) { versions := []string{} for _, bundle := range bundles { @@ -253,3 +206,29 @@ func getPackageForVersion(bundles []*apimanifests.Bundle, version string) (*apim } return nil, fmt.Errorf("no package found for version %s; valid versions: %+q", version, versions) } + +// updateCatalogSource gets the registry address of the newly created +// ephemeral packagemanifest index pod and updates the catalog source +// with the necessary address and source type fields to enable the +// catalog source to connect to the registry. +func (m *packageManifestsManager) updateCatalogSource(ctx context.Context, pkgName string, catsrc *operatorsv1alpha1.CatalogSource) error { + registryGRPCAddr := internalregistry.GetRegistryServiceAddr(pkgName, m.namespace) + catsrcKey := types.NamespacedName{ + Namespace: catsrc.Namespace, + Name: catsrc.Name, + } + if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + if err := m.client.KubeClient.Get(ctx, catsrcKey, catsrc); err != nil { + return err + } + catsrc.Spec.Address = registryGRPCAddr + catsrc.Spec.SourceType = operatorsv1alpha1.SourceTypeGrpc + if err := m.client.KubeClient.Update(ctx, catsrc); err != nil { + return err + } + return nil + }); err != nil { + return fmt.Errorf("error setting grpc address on catalog source: %v", err) + } + return nil +} diff --git a/internal/operator/config.go b/internal/operator/config.go new file mode 100644 index 0000000000..c743dd1a7e --- /dev/null +++ b/internal/operator/config.go @@ -0,0 +1,118 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed 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 ( + "context" + + v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/spf13/pflag" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Configuration struct { + Namespace string + KubeconfigPath string + RESTConfig *rest.Config + Client client.Client + Scheme *runtime.Scheme + Log func(string, ...interface{}) + + overrides *clientcmd.ConfigOverrides +} + +func (c *Configuration) BindFlags(fs *pflag.FlagSet) { + if c.overrides == nil { + c.overrides = &clientcmd.ConfigOverrides{} + } + clientcmd.BindOverrideFlags(c.overrides, fs, clientcmd.ConfigOverrideFlags{ + ContextOverrideFlags: clientcmd.ContextOverrideFlags{ + Namespace: clientcmd.FlagInfo{ + LongName: "namespace", + ShortName: "n", + Default: "", + Description: "If present, namespace scope for this CLI request", + }, + }, + }) + fs.StringVar(&c.KubeconfigPath, "kubeconfig", "", + "Path to the kubeconfig file to use for CLI requests.") +} + +func (c *Configuration) Load() error { + if c.overrides == nil { + c.overrides = &clientcmd.ConfigOverrides{} + } + if c.Log == nil { + c.Log = func(_ string, _ ...interface{}) {} + } + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = c.KubeconfigPath + mergedConfig, err := loadingRules.Load() + if err != nil { + return err + } + cfg := clientcmd.NewDefaultClientConfig(*mergedConfig, c.overrides) + cc, err := cfg.ClientConfig() + if err != nil { + return err + } + + ns, _, err := cfg.Namespace() + if err != nil { + return err + } + + sch := scheme.Scheme + for _, f := range []func(*runtime.Scheme) error{ + v1alpha1.AddToScheme, + v1.AddToScheme, + apiextv1.AddToScheme, + } { + if err := f(sch); err != nil { + return err + } + } + cl, err := client.New(cc, client.Options{ + Scheme: sch, + }) + if err != nil { + return err + } + + c.Scheme = sch + c.Client = &operatorClient{cl} + if c.Namespace == "" { + c.Namespace = ns + } + c.RESTConfig = cc + + return nil +} + +type operatorClient struct { + client.Client +} + +func (c *operatorClient) Create(ctx context.Context, obj runtime.Object, opts ...client.CreateOption) error { + opts = append(opts, client.FieldOwner("operator-sdk")) + return c.Client.Create(ctx, obj, opts...) +} diff --git a/internal/operator/constants.go b/internal/operator/constants.go new file mode 100644 index 0000000000..c10e0c7ae2 --- /dev/null +++ b/internal/operator/constants.go @@ -0,0 +1,19 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed 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 + +const ( + SDKOperatorGroupName = "operator-sdk-og" +) diff --git a/internal/operator/uninstall.go b/internal/operator/uninstall.go new file mode 100644 index 0000000000..deec545521 --- /dev/null +++ b/internal/operator/uninstall.go @@ -0,0 +1,165 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed 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 ( + "context" + "fmt" + "sort" + "strings" + + v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/api/pkg/operators/v1alpha1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/yaml" +) + +type Uninstall struct { + config *Configuration + + Package string +} + +func NewUninstall(cfg *Configuration) *Uninstall { + return &Uninstall{ + config: cfg, + } +} + +func (u *Uninstall) Run(ctx context.Context) error { + subs := v1alpha1.SubscriptionList{} + if err := u.config.Client.List(ctx, &subs, client.InNamespace(u.config.Namespace)); err != nil { + return fmt.Errorf("list subscriptions: %v", err) + } + + var sub *v1alpha1.Subscription + for i := range subs.Items { + s := subs.Items[i] + if u.Package == s.Spec.Package { + sub = &s + break + } + } + if sub == nil { + return fmt.Errorf("operator package %q not found", u.Package) + } + + catsrcKey := types.NamespacedName{ + Namespace: sub.Spec.CatalogSourceNamespace, + Name: sub.Spec.CatalogSource, + } + catsrc := &v1alpha1.CatalogSource{} + if err := u.config.Client.Get(ctx, catsrcKey, catsrc); err != nil { + return fmt.Errorf("get catalog source: %v", err) + } + + installPlanKey := types.NamespacedName{ + Namespace: sub.Status.InstallPlanRef.Namespace, + Name: sub.Status.InstallPlanRef.Name, + } + + // Since the install plan is owned by the subscription, we need to + // read all of the resource references from the install plan before + // deleting the subscription. + deleteObjs, err := u.getInstallPlanResources(ctx, installPlanKey) + if err != nil { + return err + } + + // Delete the subscription first, so that no further installs or upgrades + // of the operator occur while we're cleaning up. + if err := u.config.Client.Delete(ctx, sub); err != nil { + return fmt.Errorf("delete subscription %q: %v", sub.Name, err) + } + u.config.Log("subscription %q deleted\n", sub.Name) + + // Ensure CustomResourceDefinitions are deleted first, so that the operator + // has a chance to handle CRs that have finalizers. + sort.SliceStable(deleteObjs, func(i, j int) bool { + return deleteObjs[i].GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" + }) + for _, obj := range deleteObjs { + err := u.config.Client.Delete(ctx, obj) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if err == nil { + u.config.Log("%s %q deleted\n", strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind), obj.GetName()) + } + } + + // Delete the catalog source. This assumes that all underlying resources related + // to this catalog source have an owner reference to this catalog source so that + // they are automatically garbage-collected. + if err := u.config.Client.Delete(ctx, catsrc); err != nil { + return fmt.Errorf("delete catalog source: %v", err) + } + u.config.Log("catalogsource %q deleted\n", catsrc.Name) + + // If this was the last subscription in the namespace and the operator group is + // the one we created, delete it + if len(subs.Items) == 1 { + ogs := v1.OperatorGroupList{} + if err := u.config.Client.List(ctx, &ogs, client.InNamespace(u.config.Namespace)); err != nil { + return fmt.Errorf("list operatorgroups: %v", err) + } + for _, og := range ogs.Items { + if og.GetName() == SDKOperatorGroupName { + if err := u.config.Client.Delete(ctx, &og); err != nil { + return fmt.Errorf("delete operatorgroup %q: %v", og.Name, err) + } + u.config.Log("operatorgroup %q deleted\n", og.Name) + } + } + } + + return nil +} + +func (u *Uninstall) getInstallPlanResources(ctx context.Context, installPlanKey types.NamespacedName) ([]controllerutil.Object, error) { + installPlan := &v1alpha1.InstallPlan{} + if err := u.config.Client.Get(ctx, installPlanKey, installPlan); err != nil { + return nil, fmt.Errorf("get install plan: %v", err) + } + + var objs []controllerutil.Object + for _, step := range installPlan.Status.Plan { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + lowerKind := strings.ToLower(step.Resource.Kind) + if err := yaml.Unmarshal([]byte(step.Resource.Manifest), &obj.Object); err != nil { + return nil, fmt.Errorf("parse %s manifest %q: %v", lowerKind, step.Resource.Name, err) + } + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: step.Resource.Group, + Version: step.Resource.Version, + Kind: step.Resource.Kind, + }) + + // TODO(joelanford): This seems necessary for service accounts tied to + // cluster roles and cluster role bindings because the SA namespace + // is not set in the manifest in this case. + // See: https://github.com/operator-framework/operator-lifecycle-manager/blob/c9405d035bc50d9aa290220cb8d75b0402e72707/pkg/controller/registry/resolver/rbac.go#L133 + if step.Resource.Kind == "ServiceAccount" && obj.GetNamespace() == "" { + obj.SetNamespace(installPlanKey.Namespace) + } + objs = append(objs, obj) + } + return objs, nil +} diff --git a/internal/scorecard/kubeclient.go b/internal/scorecard/kubeclient.go index 31a35343bd..db67b59c94 100644 --- a/internal/scorecard/kubeclient.go +++ b/internal/scorecard/kubeclient.go @@ -29,6 +29,7 @@ import ( // - the user's $HOME/.kube/config file // - in-cluster connection for when the sdk is run within a cluster instead of // the command line +// TODO(joelanford): migrate scorecard use `internal/operator.Configuration` func GetKubeClient(kubeconfig string) (client kubernetes.Interface, err error) { if kubeconfig != "" { @@ -59,6 +60,7 @@ func GetKubeClient(kubeconfig string) (client kubernetes.Interface, err error) { // - from the KUBECONFIG env var if set // - from the $HOME/.kube/config path if exists // - returns 'default' as the namespace if not set in the kubeconfig +// TODO(joelanford): migrate scorecard to use `internal/operator.Configuration` func GetKubeNamespace(kubeconfigPath, namespace string) string { if namespace != "" { diff --git a/test/e2e-helm/e2e_helm_olm_test.go b/test/e2e-helm/e2e_helm_olm_test.go index 77db89d0d0..67260d6a85 100644 --- a/test/e2e-helm/e2e_helm_olm_test.go +++ b/test/e2e-helm/e2e_helm_olm_test.go @@ -20,8 +20,8 @@ import ( "path/filepath" "strings" - . "github.com/onsi/ginkgo" //nolint:golint - . "github.com/onsi/gomega" //nolint:golint + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" testutils "github.com/operator-framework/operator-sdk/test/internal" ) @@ -36,17 +36,6 @@ var _ = Describe("Integrating Helm Projects with OLM", func() { testutils.ReplaceInFile(filepath.Join(tc.Dir, "Makefile"), replace, replace+" --interactive=false") }) - AfterEach(func() { - By("destroying the deployed package manifests-formatted operator") - cleanupPkgManCmd := exec.Command(tc.BinaryName, "cleanup", "packagemanifests", - "--version", operatorVersion, - "--timeout", "4m") - _, _ = tc.Run(cleanupPkgManCmd) - - By("uninstalling CRD's") - _ = tc.Make("uninstall") - }) - It("should generate and run a valid OLM bundle and packagemanifests", func() { By("building the bundle") err := tc.Make("bundle", "IMG="+tc.ImageName) @@ -81,6 +70,12 @@ var _ = Describe("Integrating Helm Projects with OLM", func() { "--timeout", "4m") _, err = tc.Run(runPkgManCmd) Expect(err).NotTo(HaveOccurred()) + + By("destroying the deployed package manifests-formatted operator") + cleanupPkgManCmd := exec.Command(tc.BinaryName, "cleanup", projectName, + "--timeout", "4m") + _, err = tc.Run(cleanupPkgManCmd) + Expect(err).NotTo(HaveOccurred()) }) }) }) diff --git a/test/e2e-helm/e2e_helm_suite_test.go b/test/e2e-helm/e2e_helm_suite_test.go index ade95bd8ad..f4b315f14f 100644 --- a/test/e2e-helm/e2e_helm_suite_test.go +++ b/test/e2e-helm/e2e_helm_suite_test.go @@ -44,6 +44,8 @@ var ( isOLMManagedBySuite = true // kubectx stores the k8s context from where the tests are running kubectx string + // projectName is the name of the test project + projectName string ) // BeforeSuite run before any specs are run to perform the required actions for all e2e Helm tests. @@ -54,6 +56,7 @@ var _ = BeforeSuite(func(done Done) { tc, err = testutils.NewTestContext("GO111MODULE=on") Expect(err).NotTo(HaveOccurred()) Expect(tc.Prepare()).To(Succeed()) + projectName = filepath.Base(tc.Dir) By("checking the cluster type") kubectx, err = tc.Kubectl.Command("config", "current-context") diff --git a/test/e2e/e2e_suite.go b/test/e2e/e2e_suite_test.go similarity index 97% rename from test/e2e/e2e_suite.go rename to test/e2e/e2e_suite_test.go index 049e262c5d..38ba573f26 100644 --- a/test/e2e/e2e_suite.go +++ b/test/e2e/e2e_suite_test.go @@ -14,7 +14,7 @@ // Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 -package e2e +package e2e_test import ( "bytes" @@ -26,8 +26,8 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" //nolint:golint - . "github.com/onsi/gomega" //nolint:golint + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" kbtestutils "sigs.k8s.io/kubebuilder/test/e2e/utils" @@ -248,9 +248,8 @@ var _ = Describe("operator-sdk", func() { } By("destroying the deployed package manifests-formatted operator") - cleanupPkgManCmd := exec.Command(tc.BinaryName, "cleanup", "packagemanifests", + cleanupPkgManCmd := exec.Command(tc.BinaryName, "cleanup", projectName, "--namespace", tc.Kubectl.Namespace, - "--version", operatorVersion, "--timeout", "4m") _, err = tc.Run(cleanupPkgManCmd) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 31e78db348..c519e4f485 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -14,7 +14,7 @@ // Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 -package e2e +package e2e_test import ( "testing" diff --git a/test/integration/operator_olm_test.go b/test/integration/operator_olm_test.go index e807b185cd..e9bb18c587 100644 --- a/test/integration/operator_olm_test.go +++ b/test/integration/operator_olm_test.go @@ -15,6 +15,7 @@ package e2e import ( + "context" "fmt" "os" "path/filepath" @@ -22,12 +23,13 @@ import ( "time" apimanifests "github.com/operator-framework/api/pkg/manifests" - operator "github.com/operator-framework/operator-sdk/internal/olm/operator" - "github.com/operator-framework/operator-sdk/internal/util/k8sutil" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/stretchr/testify/assert" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + + operator "github.com/operator-framework/operator-sdk/internal/olm/operator" + operator2 "github.com/operator-framework/operator-sdk/internal/operator" + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" ) const ( @@ -104,9 +106,12 @@ func PackageManifestsAllNamespaces(t *testing.T) { Version: defaultOperatorVersion, } // Cleanup. + cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath} + assert.NoError(t, cfg.Load()) + uninstall := operator2.NewUninstall(cfg) + uninstall.Package = defaultOperatorName defer func() { - opcmd.ForceRegistry = true - if err := opcmd.Cleanup(); err != nil { + if err := doUninstall(uninstall, opcmd.Timeout); err != nil { t.Fatal(err) } }() @@ -165,15 +170,13 @@ func PackageManifestsBasic(t *testing.T) { Version: defaultOperatorVersion, } // Cleanup. - defer func() { - opcmd.ForceRegistry = true - if err := opcmd.Cleanup(); err != nil { - t.Fatal(err) - } - }() + cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath} + assert.NoError(t, cfg.Load()) + uninstall := operator2.NewUninstall(cfg) + uninstall.Package = defaultOperatorName // "Remove operator before deploy" - assert.NoError(t, opcmd.Cleanup()) + assert.Error(t, doUninstall(uninstall, opcmd.Timeout)) // "Deploy operator" assert.NoError(t, opcmd.Run()) @@ -181,9 +184,9 @@ func PackageManifestsBasic(t *testing.T) { assert.Error(t, opcmd.Run()) // "Remove operator after deploy" - assert.NoError(t, opcmd.Cleanup()) + assert.NoError(t, doUninstall(uninstall, opcmd.Timeout)) // "Remove operator after removal" - assert.NoError(t, opcmd.Cleanup()) + assert.Error(t, doUninstall(uninstall, opcmd.Timeout)) } func PackageManifestsMultiplePackages(t *testing.T) { @@ -267,15 +270,19 @@ func PackageManifestsMultiplePackages(t *testing.T) { Version: operatorVersion2, } // Cleanup. - defer func() { - opcmd.ForceRegistry = true - if err := opcmd.Cleanup(); err != nil { - t.Fatal(err) - } - }() + cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath} + assert.NoError(t, cfg.Load()) + uninstall := operator2.NewUninstall(cfg) + uninstall.Package = defaultOperatorName // "Deploy operator" assert.NoError(t, opcmd.Run()) // "Remove operator after deploy" - assert.NoError(t, opcmd.Cleanup()) + assert.NoError(t, doUninstall(uninstall, opcmd.Timeout)) +} + +func doUninstall(u *operator2.Uninstall, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return u.Run(ctx) } diff --git a/website/content/en/docs/cli/operator-sdk_cleanup.md b/website/content/en/docs/cli/operator-sdk_cleanup.md index 33004ccb99..dc8ac9ebfb 100644 --- a/website/content/en/docs/cli/operator-sdk_cleanup.md +++ b/website/content/en/docs/cli/operator-sdk_cleanup.md @@ -8,14 +8,18 @@ Clean up an Operator deployed with the 'run' subcommand ### Synopsis This command has subcommands that will destroy an Operator deployed with OLM. -Currently only the package manifests format is supported via the 'packagemanifests' subcommand. -Run 'operator-sdk cleanup --help' for more information. +``` +operator-sdk cleanup [flags] +``` ### Options ``` - -h, --help help for cleanup + -h, --help help for cleanup + --kubeconfig string Path to the kubeconfig file to use for CLI requests. + -n, --namespace string If present, namespace scope for this CLI request + --timeout duration Time to wait for the command to complete before failing (default 2m0s) ``` ### Options inherited from parent commands @@ -27,5 +31,4 @@ Run 'operator-sdk cleanup --help' for more information. ### SEE ALSO * [operator-sdk](../operator-sdk) - Development kit for building Kubernetes extensions and tools. -* [operator-sdk cleanup packagemanifests](../operator-sdk_cleanup_packagemanifests) - Clean up an Operator in the package manifests format deployed with OLM diff --git a/website/content/en/docs/cli/operator-sdk_cleanup_packagemanifests.md b/website/content/en/docs/cli/operator-sdk_cleanup_packagemanifests.md deleted file mode 100644 index 976cf0bb53..0000000000 --- a/website/content/en/docs/cli/operator-sdk_cleanup_packagemanifests.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "operator-sdk cleanup packagemanifests" ---- -## operator-sdk cleanup packagemanifests - -Clean up an Operator in the package manifests format deployed with OLM - -### Synopsis - -'cleanup packagemanifests' destroys an Operator deployed with OLM using the 'run packagemanifests' command. -The command's argument must be set to a valid package manifests root directory, -ex. '<project-root>/packagemanifests'. - -``` -operator-sdk cleanup packagemanifests [flags] -``` - -### Options - -``` - -h, --help help for packagemanifests - --install-mode string InstallMode to create OperatorGroup with. Format: InstallModeType[=ns1,ns2[, ...]] - --kubeconfig string The file path to kubernetes configuration file. Defaults to location specified by $KUBECONFIG, or to default file rules if not set - --namespace string The namespace where operator resources are created. It must already exist in the cluster - --timeout duration Time to wait for the command to complete before failing (default 2m0s) - --version string Packaged version of the operator to deploy -``` - -### Options inherited from parent commands - -``` - --verbose Enable verbose logging -``` - -### SEE ALSO - -* [operator-sdk cleanup](../operator-sdk_cleanup) - Clean up an Operator deployed with the 'run' subcommand - diff --git a/website/content/en/docs/olm-integration/quickstart-package-manifests.md b/website/content/en/docs/olm-integration/quickstart-package-manifests.md index 0985f55080..1641cfe0d4 100644 --- a/website/content/en/docs/olm-integration/quickstart-package-manifests.md +++ b/website/content/en/docs/olm-integration/quickstart-package-manifests.md @@ -68,24 +68,26 @@ As long as both the `ClusterServiceVersion` and all `CustomResourceDefinition`'s the memcached-operator has been deployed successfully. Now that we're done testing the memcached-operator, we should probably clean up the Operator's resources. -[`operator-sdk cleanup packagemanifests`][cli-cleanup-packagemanifests] will do this for you: +[`operator-sdk cleanup`][cli-cleanup] will do this for you: ```console -$ operator-sdk cleanup packagemanifests --version 0.0.1 -INFO[0000] Deleting resources -INFO[0000] Deleting CatalogSource "default/memcached-operator-ocs" -INFO[0000] Deleting Subscription "default/memcached-operator-v0-0-1-sub" -INFO[0000] Deleting OperatorGroup "default/operator-sdk-og" -INFO[0000] Deleting CustomResourceDefinition "default/memcacheds.example.com" -INFO[0000] Deleting ClusterServiceVersion "default/memcached-operator.v0.0.1" -INFO[0000] Waiting for deleted resources to disappear -INFO[0001] Successfully uninstalled "memcached-operator.v0.0.1" on OLM version "0.15.1" +$ operator-sdk cleanup memcached-operator +INFO[0000] subscription "memcached-operator-v0-0-1-sub" deleted +INFO[0000] customresourcedefinition "memcacheds.cache.example.com" deleted +INFO[0000] clusterserviceversion "memcached-operator.v0.0.1" deleted +INFO[0000] clusterrole "memcached-operator-metrics-reader" deleted +INFO[0000] serviceaccount "default" deleted +INFO[0000] role "memcached-operator.v0.0.1-jhjk7" deleted +INFO[0000] rolebinding "memcached-operator.v0.0.1-jhjk7-default-mxv6m" deleted +INFO[0000] catalogsource "memcached-operator-ocs" deleted +INFO[0000] operatorgroup "operator-sdk-og" deleted +INFO[0001] operator "memcached-operator" uninstalled ``` [quickstart-bundle]:/docs/olm-integration/quickstart-bundle [operator-registry]:https://github.com/operator-framework/operator-registry [cli-run-packagemanifests]:/docs/cli/operator-sdk_run_packagemanifests -[cli-cleanup-packagemanifests]:/docs/cli/operator-sdk_cleanup_packagemanifests +[cli-cleanup]:/docs/cli/operator-sdk_cleanup [doc-olm-generate]:/docs/olm-integration/generation#overview [doc-testing-deployment]:/docs/olm-integration/testing-deployment