Skip to content

Commit

Permalink
operator uninstall with operands
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Sover <dsover@redhat.com>
  • Loading branch information
exdx committed Apr 21, 2021
1 parent 9e5901c commit 67e2991
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 78 deletions.
13 changes: 0 additions & 13 deletions internal/cmd/operator_list_operands.go
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
Expand Down Expand Up @@ -64,18 +63,6 @@ the operator's ClusterServiceVersion.`,
return
}

sort.Slice(operands.Items, func(i, j int) bool {
if operands.Items[i].GetAPIVersion() != operands.Items[j].GetAPIVersion() {
return operands.Items[i].GetAPIVersion() < operands.Items[j].GetAPIVersion()
}
if operands.Items[i].GetKind() != operands.Items[j].GetKind() {
return operands.Items[i].GetKind() < operands.Items[j].GetKind()
}
if operands.Items[i].GetNamespace() != operands.Items[j].GetNamespace() {
return operands.Items[i].GetNamespace() < operands.Items[j].GetNamespace()
}
return operands.Items[i].GetName() < operands.Items[j].GetName()
})
if err := writeOutput(os.Stdout, operands); err != nil {
log.Fatal(err)
}
Expand Down
19 changes: 15 additions & 4 deletions internal/cmd/operator_uninstall.go
Expand Up @@ -15,8 +15,20 @@ func newOperatorUninstallCmd(cfg *action.Configuration) *cobra.Command {

cmd := &cobra.Command{
Use: "uninstall <operator>",
Short: "Uninstall an operator",
Args: cobra.ExactArgs(1),
Short: "Uninstall an operator and operands",
Long: `Uninstall removes the subscription, operator and optionally operands managed by the operator.
This command first finds and deletes the subscription associated with the operator. It then
lists all operands found throughout the cluster for the operator
specified on the command line. Since the scope of an operator is restricted by
its operator group, the this search will include namespace-scoped operands from the
operator group's target namespaces and all cluster-scoped operands.
The operand-deletion strategy is then considered if any operands are found on-cluster. One of cancel|ignore|delete.
By default, the strategy is "cancel", which means that if any operands are found when deleting the operator abort the uninstall.
The "ignore" strategy keeps the operands on cluster and only deletes the operator itself.
The "delete" strategy deletes both the operands, and after they have finished finalizing, the operator itself.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
u.Package = args[0]
if err := u.Run(cmd.Context()); err != nil {
Expand All @@ -30,8 +42,7 @@ func newOperatorUninstallCmd(cfg *action.Configuration) *cobra.Command {
}

func bindOperatorUninstallFlags(fs *pflag.FlagSet, u *internalaction.OperatorUninstall) {
fs.BoolVarP(&u.DeleteAll, "delete-all", "X", false, "enable all delete flags")
fs.BoolVar(&u.DeleteCRDs, "delete-crds", false, "delete all owned CRDs and all CRs")
fs.BoolVar(&u.DeleteOperatorGroups, "delete-operator-groups", false, "delete operator groups if no other operators remain")
fs.StringSliceVar(&u.DeleteOperatorGroupNames, "delete-operator-group-names", nil, "specific operator group names to delete (only effective with --delete-operator-groups)")
fs.VarP(&u.OperandStrategy, "operand-strategy", "os", "determines how to handle operands when deleting the operator, one of cancel|ignore|delete (default cancel)")
}
13 changes: 13 additions & 0 deletions internal/pkg/action/action_suite_test.go
@@ -0,0 +1,13 @@
package action_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestCommand(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Internal action Suite")
}
1 change: 0 additions & 1 deletion internal/pkg/action/constants.go
Expand Up @@ -2,5 +2,4 @@ package action

const (
csvKind = "ClusterServiceVersion"
crdKind = "CustomResourceDefinition"
)
116 changes: 56 additions & 60 deletions internal/pkg/action/operator_uninstall.go
Expand Up @@ -8,24 +8,21 @@ import (
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"

"github.com/operator-framework/kubectl-operator/internal/pkg/operand"
"github.com/operator-framework/kubectl-operator/pkg/action"
)

type OperatorUninstall struct {
config *action.Configuration

Package string
DeleteAll bool
DeleteCRDs bool
OperandStrategy operand.DeletionStrategy
DeleteOperatorGroups bool
DeleteOperatorGroupNames []string

Logf func(string, ...interface{})
Logf func(string, ...interface{})
}

func NewOperatorUninstall(cfg *action.Configuration) *OperatorUninstall {
Expand All @@ -44,11 +41,6 @@ func (e ErrPackageNotFound) Error() string {
}

func (u *OperatorUninstall) Run(ctx context.Context) error {
if u.DeleteAll {
u.DeleteCRDs = true
u.DeleteOperatorGroups = true
}

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)
Expand All @@ -74,57 +66,78 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
return fmt.Errorf("get subscription CSV %q: %v", csvName, err)
}

// Subscriptions can be deleted asynchronously.
if err := u.deleteObjects(ctx, sub); err != nil {
return err
}

// If we could not find a csv associated with the subscription, that likely
// means there is no CSV associated with it yet. Return a nil error.
if csv == nil {
u.Logf("csv for package %q not found", u.Package)
return nil
}

// Deletion order:
//
// 1. Subscription to prevent further installs or upgrades of the operator while cleaning up.
// 2. CustomResourceDefinitions so the operator has a chance to handle CRs that have finalizers.
// 2. Operands so the operator has a chance to handle CRs that have finalizers.
// Note: the correct strategy must be chosen in order to process an opertor delete with operand on-cluster.
// 3. ClusterServiceVersion. OLM puts an ownerref on every namespaced resource to the CSV,
// and an owner label on every cluster scoped resource so they get gc'd on deletion.

// Subscriptions can be deleted asynchronously.
if err := u.deleteObjects(ctx, sub); err != nil {
return err
// create lister and list operands
lister := action.NewOperatorListOperands(u.config)
operands, err := lister.Run(ctx, u.Package)
if err != nil {
return fmt.Errorf("listing operands associated with operator %s: %s", u.Package, err)
}

if csv != nil {
// Ensure CustomResourceDefinitions are deleted next, so that the operator
// has a chance to handle CRs that have finalizers.
if u.DeleteCRDs {
crds := getCRDs(csv)
if err := u.deleteObjects(ctx, crds...); err != nil {
switch u.OperandStrategy.Kind {
case operand.Cancel:
if len(operands.Items) > 0 {
return fmt.Errorf("%d operands exist and operand strategy %q is in use; "+
"delete operands or re-run with a different operand strategy", len(operands.Items), operand.Cancel)
}
case operand.Ignore:
for _, op := range operands.Items {
u.Logf("%s %q orphaned", strings.ToLower(op.GetKind()), op.GetName()+"/"+op.GetNamespace())
}
case operand.Delete:
for _, op := range operands.Items {
op := op
if err := u.deleteObjects(ctx, &op); err != nil {
return err
}
u.Logf("%s %q deleted", strings.ToLower(op.GetKind()), op.GetName()+"/"+op.GetNamespace())
}
default:
return fmt.Errorf("unknown operand deletion strategy %q", u.OperandStrategy)
}

// OLM puts an ownerref on every namespaced resource to the CSV,
// and an owner label on every cluster scoped resource. When CSV is deleted
// kube and olm gc will remove all the referenced resources.
if err := u.deleteObjects(ctx, csv); err != nil {
return err
}
// OLM puts an ownerref on every namespaced resource to the CSV,
// and an owner label on every cluster scoped resource. When CSV is deleted
// kube and olm gc will remove all the referenced resources.
if err := u.deleteObjects(ctx, csv); err != nil {
return err
}

if u.DeleteOperatorGroups {
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)
// If there are no subscriptions left, delete the operator group(s).
if len(subs.Items) == 0 {
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)
}
// If there are no subscriptions left, delete the operator group(s).
if len(subs.Items) == 0 {
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 {
og := og
if len(u.DeleteOperatorGroupNames) == 0 || contains(u.DeleteOperatorGroupNames, og.GetName()) {
if err := u.deleteObjects(ctx, &og); err != nil {
return err
}
for _, og := range ogs.Items {
og := og
if len(u.DeleteOperatorGroupNames) == 0 || contains(u.DeleteOperatorGroupNames, og.GetName()) {
if err := u.deleteObjects(ctx, &og); err != nil {
return err
}
}
}
}

return nil
}

Expand Down Expand Up @@ -172,23 +185,6 @@ func csvNameFromSubscription(subscription *v1alpha1.Subscription) string {
return subscription.Status.CurrentCSV
}

// getCRDs returns the list of CRDs required by a CSV.
func getCRDs(csv *v1alpha1.ClusterServiceVersion) (crds []client.Object) {
for _, resource := range csv.Status.RequirementStatus {
if resource.Kind == crdKind {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Group: resource.Group,
Version: resource.Version,
Kind: resource.Kind,
})
obj.SetName(resource.Name)
crds = append(crds, obj)
}
}
return
}

func contains(haystack []string, needle string) bool {
for _, n := range haystack {
if n == needle {
Expand Down

0 comments on commit 67e2991

Please sign in to comment.