Skip to content

Commit

Permalink
CLI: add "SF wipe" subcommand
Browse files Browse the repository at this point in the history
The subcommand can be used to remove instances, PVs and even the
operator from a cluster. It doesn't require Ansible to run.

Use the subcommand in CI playbooks.

Change-Id: Ia5ba21acaecf13f4ba32950c45d01afe7585f69f
  • Loading branch information
mhuin committed Jan 17, 2024
1 parent 58e3f69 commit a684056
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 7 deletions.
20 changes: 20 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ func GetM(env *ENV, name string, obj client.Object) (bool, error) {
}
}

func DeleteOrDie(env *ENV, obj client.Object) bool {
err := env.Cli.Delete(env.Ctx, obj)
if apierrors.IsNotFound(err) {
return false
} else if err != nil {
msg := fmt.Sprintf("Error while deleting %s \"%s\"", reflect.TypeOf(obj).Name(), obj.GetName())
ctrl.Log.Error(err, msg)
os.Exit(1)
}
return true
}

func GetMOrDie(env *ENV, name string, obj client.Object) bool {
_, err := GetM(env, name, obj)
if apierrors.IsNotFound(err) {
Expand Down Expand Up @@ -232,6 +244,14 @@ func CreateROrDie(env *ENV, obj client.Object) {
ctrl.Log.Info(msg)
}

func DeleteAllOfOrDie(env *ENV, obj client.Object, opts ...client.DeleteAllOfOption) {
if err := env.Cli.DeleteAllOf(env.Ctx, obj, opts...); err != nil {
var msg = "Error while deleting"
ctrl.Log.Error(err, msg)
os.Exit(1)
}
}

func getCLIctxOrDie(kmd *cobra.Command, args []string, allowedArgs []string) SoftwareFactoryConfigContext {
cliCtx, err := GetCLIContext(kmd)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cli/cmd/sf.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func MkSFCmd() *cobra.Command {
sfCmd.AddCommand(MkBackupCmd())
sfCmd.AddCommand(MkRestoreCmd())
sfCmd.AddCommand(configureCmd)
sfCmd.AddCommand(MkWipeCmd())

return sfCmd
}
200 changes: 200 additions & 0 deletions cli/cmd/wipe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
Copyright © 2023 Red Hat
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 cmd

/*
"wipe" subcommand cleans up Software Factory resources.
*/

import (
"context"
"os"

sfv1 "github.com/softwarefactory-project/sf-operator/api/v1"

v1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/spf13/cobra"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func getOperatorSelector() labels.Selector {
selector := labels.NewSelector()
req, err := labels.NewRequirement(
"operators.coreos.com/sf-operator.operators",
selection.Exists,
[]string{})
if err != nil {
ctrl.Log.Error(err, "could not set label selector to clean subscriptions")
os.Exit(1)
}
return selector.Add(*req)
}

func cleanSubscription(env *ENV) {
selector := getOperatorSelector()

subscriptionListOpts := []client.ListOption{
client.InNamespace("operators"),
client.MatchingLabelsSelector{
Selector: selector,
},
}

subsList := v1alpha1.SubscriptionList{}
if err := env.Cli.List(env.Ctx, &subsList, subscriptionListOpts...); err != nil {
ctrl.Log.Error(err, "error listing subscriptions")
os.Exit(1)
}
if len(subsList.Items) > 0 {
subscriptionDeleteOpts := []client.DeleteAllOfOption{
client.InNamespace("operators"),
client.MatchingLabelsSelector{
Selector: selector,
},
}
sub := v1alpha1.Subscription{}
DeleteAllOfOrDie(env, &sub, subscriptionDeleteOpts...)
}
}

func cleanCatalogSource(env *ENV) {
cs := v1alpha1.CatalogSource{}
cs.SetName("sf-operator-catalog")
cs.SetNamespace("operators")
if !DeleteOrDie(env, &cs) {
ctrl.Log.Info("CatalogSource object not found")
}
}

func cleanClusterServiceVersion(env *ENV) {
selector := getOperatorSelector()

subscriptionListOpts := []client.ListOption{
client.InNamespace("operators"),
client.MatchingLabelsSelector{
Selector: selector,
},
}

csvsList := v1alpha1.ClusterServiceVersionList{}
if err := env.Cli.List(env.Ctx, &csvsList, subscriptionListOpts...); err != nil {
ctrl.Log.Error(err, "error listing cluster service versions")
os.Exit(1)
}
if len(csvsList.Items) > 0 {
csvDeleteOpts := []client.DeleteAllOfOption{
client.InNamespace("operators"),
client.MatchingLabelsSelector{
Selector: selector,
},
}
csv := v1alpha1.ClusterServiceVersion{}
DeleteAllOfOrDie(env, &csv, csvDeleteOpts...)
}
}

func cleanSFInstance(env *ENV, ns string) {
var sf sfv1.SoftwareFactory
sfDeleteOpts := []client.DeleteAllOfOption{
client.InNamespace(ns),
}
DeleteAllOfOrDie(env, &sf, sfDeleteOpts...)
var cm apiv1.ConfigMap
cm.SetName("sf-standalone-owner")
cm.SetNamespace(ns)
if !DeleteOrDie(env, &cm) {
ctrl.Log.Info("standalone mode configmap not found")
}
}

func cleanPVCs(env *ENV, ns string) {
selector := labels.NewSelector()
appReq, err := labels.NewRequirement(
"app",
selection.In,
[]string{"sf"})
if err != nil {
ctrl.Log.Error(err, "could not set app label requirement to clean PVCs")
os.Exit(1)
}
runReq, err := labels.NewRequirement(
"run",
selection.NotIn,
[]string{"gerrit"})
if err != nil {
ctrl.Log.Error(err, "could not set run label requirement to clean PVCs")
os.Exit(1)
}
selector = selector.Add([]labels.Requirement{*appReq, *runReq}...)
pvcDeleteOpts := []client.DeleteAllOfOption{
client.InNamespace(ns),
client.MatchingLabelsSelector{
Selector: selector,
},
}
var pvc apiv1.PersistentVolumeClaim
DeleteAllOfOrDie(env, &pvc, pvcDeleteOpts...)
}

func wipeSFCmd(kmd *cobra.Command, args []string) {
cliCtx, err := GetCLIContext(kmd)
if err != nil {
ctrl.Log.Error(err, "Error initializing")
os.Exit(1)
}
ns := cliCtx.Namespace
kubeContext := cliCtx.KubeContext
delPVCs, _ := kmd.Flags().GetBool("rm-data")
delAll, _ := kmd.Flags().GetBool("all")
env := ENV{
Cli: CreateKubernetesClientOrDie(kubeContext),
Ctx: context.TODO(),
Ns: ns,
}
cleanSFInstance(&env, ns)
if delPVCs || delAll {
cleanPVCs(&env, ns)
}
if delAll {
cleanSubscription(&env)
cleanCatalogSource(&env)
cleanClusterServiceVersion(&env)
}
}

func MkWipeCmd() *cobra.Command {

var (
deleteData bool
deleteAll bool
wipeCmd = &cobra.Command{
Use: "wipe",
Short: "wipe SF instance and related resources",
Long: `This command can be used to remove all Software Factory instances in the provided namespace,
their persistent volumes, and even remove the SF operator completely.`,
Run: wipeSFCmd,
}
)

wipeCmd.Flags().BoolVar(&deleteData, "rm-data", false, "Delete also all persistent volumes after removing the instances. This will result in data loss, like build results and artifacts.")
wipeCmd.Flags().BoolVar(&deleteAll, "all", false, "Remove also the operator completely from the cluster.")
return wipeCmd
}
20 changes: 20 additions & 0 deletions doc/reference/cli/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ deployments, beyond what can be defined in a custom resource manifest.
1. [backup](#backup)
1. [configure TLS](#configure-tls)
1. [restore](#restore)
1. [wipe](#wipe)

## Running the CLI

Expand Down Expand Up @@ -212,3 +213,22 @@ Flags:
#### restore

Not implemented yet

#### wipe

The `wipe` subcommand can be used to remove all Software Factory instances in the provided namespace,
their persistent volumes, and even remove the SF operator completely.

The default behavior is to stop and remove all containers related to a Software Factory deployment, and
keep the existing persistent volumes.

```sh
go run ./main.go [GLOBAL FLAGS] SF wipe [FLAGS]
```

Flags:

| Argument | Type | Description | Optional | Default |
|----------|------|-------|----|----|
| --rm-data | boolean | Also delete all persistent volumes after removing the instances | yes | False |
| --all | boolean | Remove all data like with the `--rm-data` flag, and remove the operator from the cluster | yes | False |
2 changes: 1 addition & 1 deletion playbooks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
name: "{{ item }}"
loop:
- build-operator-assets
- clean-installations
- clean-installations-cli
- install-operator

- name: Apply the minimal SF CR and ensure reconciled happened
Expand Down
2 changes: 1 addition & 1 deletion playbooks/upgrade.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- role: build-operator-assets
vars:
build_bundle: false
- clean-installations
- clean-installations-cli
- role: install-operator
vars:
ci_bundle_img: quay.io/software-factory/sf-operator-bundle:latest
Expand Down
7 changes: 7 additions & 0 deletions roles/clean-installations-cli/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- name: Clean up previous operator installation
when: remote_os_host
ansible.builtin.shell: >
go run ./main.go --namespace sf SF wipe --all
args:
chdir: "{{ zuul.project.src_dir | default(src_dir) }}"
5 changes: 0 additions & 5 deletions tools/wipe-deployment.sh

This file was deleted.

0 comments on commit a684056

Please sign in to comment.