Skip to content

Commit fde6841

Browse files
committed
Add command to delete an existing olmv1 catalog
Signed-off-by: Artur Zych <[email protected]>
1 parent adb5f78 commit fde6841

10 files changed

+281
-47
lines changed

Diff for: internal/cmd/internal/olmv1/catalog_delete.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package olmv1
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/spf13/pflag"
6+
7+
"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
8+
v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
9+
"github.com/operator-framework/kubectl-operator/pkg/action"
10+
)
11+
12+
// NewCatalogDeleteCmd allows deleting either a single or all
13+
// existing catalogs
14+
func NewCatalogDeleteCmd(cfg *action.Configuration) *cobra.Command {
15+
d := v1action.NewCatalogDelete(cfg)
16+
d.Logf = log.Printf
17+
18+
cmd := &cobra.Command{
19+
Use: "catalog [catalog_name]",
20+
Aliases: []string{"catalogs [catalog_name]"},
21+
Args: cobra.RangeArgs(0, 1),
22+
Short: "Delete either a single or all of the existing catalogs",
23+
Run: func(cmd *cobra.Command, args []string) {
24+
if len(args) == 0 {
25+
cats, err := d.Run(cmd.Context())
26+
if err != nil {
27+
log.Fatalf("failed deleting catalogs: %v", err)
28+
}
29+
for _, cat := range cats {
30+
log.Printf("catalog %q deleted", cat)
31+
}
32+
33+
return
34+
}
35+
36+
d.CatalogName = args[0]
37+
if _, err := d.Run(cmd.Context()); err != nil {
38+
log.Fatalf("failed to delete catalog %q: %v", d.CatalogName, err)
39+
}
40+
log.Printf("catalog %q deleted", d.CatalogName)
41+
},
42+
}
43+
bindCatalogDeleteFlags(cmd.Flags(), d)
44+
45+
return cmd
46+
}
47+
48+
func bindCatalogDeleteFlags(fs *pflag.FlagSet, d *v1action.CatalogDelete) {
49+
fs.BoolVar(&d.DeleteAll, "all", false, "delete all catalogs")
50+
}

Diff for: internal/cmd/olmv1.go

+8
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,19 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
3131
}
3232
createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
3333

34+
deleteCmd := &cobra.Command{
35+
Use: "delete",
36+
Short: "Delete a resource",
37+
Long: "Delete a resource",
38+
}
39+
deleteCmd.AddCommand(olmv1.NewCatalogDeleteCmd(cfg))
40+
3441
cmd.AddCommand(
3542
olmv1.NewOperatorInstallCmd(cfg),
3643
olmv1.NewOperatorUninstallCmd(cfg),
3744
getCmd,
3845
createCmd,
46+
deleteCmd,
3947
)
4048

4149
return cmd

Diff for: internal/pkg/v1/action/action_suite_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ package action_test
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67

78
. "github.com/onsi/ginkgo"
89
. "github.com/onsi/gomega"
910

11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1012
"sigs.k8s.io/controller-runtime/pkg/client"
13+
14+
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
1115
)
1216

1317
func TestCommand(t *testing.T) {
@@ -44,3 +48,18 @@ func (mg *mockGetter) Get(ctx context.Context, key client.ObjectKey, obj client.
4448
mg.getCalled++
4549
return mg.getErr
4650
}
51+
52+
func setupTestCatalogs(n int) []client.Object {
53+
var result []client.Object
54+
for i := 1; i <= n; i++ {
55+
result = append(result, newClusterCatalog(fmt.Sprintf("cat%d", i)))
56+
}
57+
58+
return result
59+
}
60+
61+
func newClusterCatalog(name string) *olmv1catalogd.ClusterCatalog {
62+
return &olmv1catalogd.ClusterCatalog{
63+
ObjectMeta: metav1.ObjectMeta{Name: name},
64+
}
65+
}

Diff for: internal/pkg/v1/action/catalog_delete.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package action
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
9+
10+
"github.com/operator-framework/kubectl-operator/pkg/action"
11+
)
12+
13+
type CatalogDelete struct {
14+
config *action.Configuration
15+
CatalogName string
16+
DeleteAll bool
17+
18+
Logf func(string, ...interface{})
19+
}
20+
21+
func NewCatalogDelete(cfg *action.Configuration) *CatalogDelete {
22+
return &CatalogDelete{
23+
config: cfg,
24+
Logf: func(string, ...interface{}) {},
25+
}
26+
}
27+
28+
func (cd *CatalogDelete) Run(ctx context.Context) ([]string, error) {
29+
// validate
30+
if cd.DeleteAll && cd.CatalogName != "" {
31+
return nil, errNameAndSelector
32+
}
33+
34+
// delete single, specified catalog
35+
if !cd.DeleteAll {
36+
return nil, cd.deleteCatalog(ctx, cd.CatalogName)
37+
}
38+
39+
// delete all existing catalogs
40+
var catsList olmv1catalogd.ClusterCatalogList
41+
if err := cd.config.Client.List(ctx, &catsList); err != nil {
42+
return nil, err
43+
}
44+
if len(catsList.Items) == 0 {
45+
return nil, errNoResourcesFound
46+
}
47+
48+
errs := make([]error, 0, len(catsList.Items))
49+
names := make([]string, 0, len(catsList.Items))
50+
for _, cat := range catsList.Items {
51+
names = append(names, cat.Name)
52+
if err := cd.deleteCatalog(ctx, cat.Name); err != nil {
53+
errs = append(errs, fmt.Errorf("failed deleting catalog %q: %w", cat.Name, err))
54+
}
55+
}
56+
57+
return names, errors.Join(errs...)
58+
}
59+
60+
func (cd *CatalogDelete) deleteCatalog(ctx context.Context, name string) error {
61+
op := &olmv1catalogd.ClusterCatalog{}
62+
op.SetName(name)
63+
64+
if err := cd.config.Client.Delete(ctx, op); err != nil {
65+
return err
66+
}
67+
68+
return waitForDeletion(ctx, cd.config.Client, op)
69+
}

Diff for: internal/pkg/v1/action/catalog_delete_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package action_test
2+
3+
import (
4+
"context"
5+
"slices"
6+
7+
. "github.com/onsi/ginkgo"
8+
. "github.com/onsi/gomega"
9+
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
12+
13+
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
14+
15+
internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
16+
"github.com/operator-framework/kubectl-operator/pkg/action"
17+
)
18+
19+
var _ = Describe("CatalogDelete", func() {
20+
setupEnv := func(catalogs ...client.Object) action.Configuration {
21+
var cfg action.Configuration
22+
23+
sch, err := action.NewScheme()
24+
Expect(err).To(BeNil())
25+
26+
cl := fake.NewClientBuilder().
27+
WithObjects(catalogs...).
28+
WithScheme(sch).
29+
Build()
30+
cfg.Scheme = sch
31+
cfg.Client = cl
32+
33+
return cfg
34+
}
35+
36+
It("fails because of both resource name and --all specifier being present", func() {
37+
cfg := setupEnv(setupTestCatalogs(2)...)
38+
39+
deleter := internalaction.NewCatalogDelete(&cfg)
40+
deleter.CatalogName = "name"
41+
deleter.DeleteAll = true
42+
catNames, err := deleter.Run(context.TODO())
43+
Expect(err).NotTo(BeNil())
44+
Expect(catNames).To(BeEmpty())
45+
46+
validateExistingCatalogs(cfg.Client, []string{"cat1", "cat2"})
47+
})
48+
49+
It("fails deleting a non-existing catalog", func() {
50+
cfg := setupEnv(setupTestCatalogs(2)...)
51+
52+
deleter := internalaction.NewCatalogDelete(&cfg)
53+
deleter.CatalogName = "does-not-exist"
54+
catNames, err := deleter.Run(context.TODO())
55+
Expect(err).NotTo(BeNil())
56+
Expect(catNames).To(BeEmpty())
57+
58+
validateExistingCatalogs(cfg.Client, []string{"cat1", "cat2"})
59+
})
60+
61+
It("successfully deletes an existing catalog", func() {
62+
cfg := setupEnv(setupTestCatalogs(3)...)
63+
64+
deleter := internalaction.NewCatalogDelete(&cfg)
65+
deleter.CatalogName = "cat2"
66+
catNames, err := deleter.Run(context.TODO())
67+
Expect(err).To(BeNil())
68+
Expect(catNames).To(BeEmpty())
69+
70+
validateExistingCatalogs(cfg.Client, []string{"cat1", "cat3"})
71+
})
72+
73+
It("fails deleting catalogs because there are none", func() {
74+
cfg := setupEnv()
75+
76+
deleter := internalaction.NewCatalogDelete(&cfg)
77+
deleter.DeleteAll = true
78+
catNames, err := deleter.Run(context.TODO())
79+
Expect(err).NotTo(BeNil())
80+
Expect(catNames).To(BeEmpty())
81+
82+
validateExistingCatalogs(cfg.Client, []string{})
83+
})
84+
85+
It("successfully deletes all catalogs", func() {
86+
cfg := setupEnv(setupTestCatalogs(3)...)
87+
88+
deleter := internalaction.NewCatalogDelete(&cfg)
89+
deleter.DeleteAll = true
90+
catNames, err := deleter.Run(context.TODO())
91+
Expect(err).To(BeNil())
92+
Expect(catNames).To(ContainElements([]string{"cat1", "cat2", "cat3"}))
93+
94+
validateExistingCatalogs(cfg.Client, []string{})
95+
})
96+
})
97+
98+
func validateExistingCatalogs(c client.Client, wantedNames []string) {
99+
var catalogsList olmv1catalogd.ClusterCatalogList
100+
err := c.List(context.TODO(), &catalogsList)
101+
Expect(err).To(BeNil())
102+
103+
catalogs := catalogsList.Items
104+
Expect(catalogs).To(HaveLen(len(wantedNames)))
105+
for _, wantedName := range wantedNames {
106+
Expect(slices.ContainsFunc(catalogs, func(cat olmv1catalogd.ClusterCatalog) bool {
107+
return cat.Name == wantedName
108+
})).To(BeTrue())
109+
}
110+
}

Diff for: internal/pkg/v1/action/catalog_installed_get_test.go

-17
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ package action_test
22

33
import (
44
"context"
5-
"fmt"
65
"slices"
76

87
. "github.com/onsi/ginkgo"
98
. "github.com/onsi/gomega"
109

11-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1210
"sigs.k8s.io/controller-runtime/pkg/client"
1311
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1412

@@ -82,18 +80,3 @@ var _ = Describe("CatalogInstalledGet", func() {
8280
Expect(operators).To(BeEmpty())
8381
})
8482
})
85-
86-
func setupTestCatalogs(n int) []client.Object {
87-
var result []client.Object
88-
for i := 1; i <= n; i++ {
89-
result = append(result, newClusterCatalog(fmt.Sprintf("cat%d", i)))
90-
}
91-
92-
return result
93-
}
94-
95-
func newClusterCatalog(name string) *olmv1catalogd.ClusterCatalog {
96-
return &olmv1catalogd.ClusterCatalog{
97-
ObjectMeta: metav1.ObjectMeta{Name: name},
98-
}
99-
}

Diff for: internal/pkg/v1/action/errors.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package action
2+
3+
import "errors"
4+
5+
var (
6+
errNoResourcesFound = errors.New("no resources found")
7+
errNameAndSelector = errors.New("name cannot be provided when a selector is specified")
8+
)

Diff for: internal/pkg/v1/action/helpers.go

+17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package action
22

33
import (
44
"context"
5+
"fmt"
56
"slices"
67
"time"
78

@@ -45,6 +46,22 @@ func waitUntilCatalogStatusCondition(
4546
})
4647
}
4748

49+
func waitForDeletion(ctx context.Context, cl client.Client, obj client.Object) error {
50+
key := objectKeyForObject(obj)
51+
if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
52+
if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) {
53+
return true, nil
54+
} else if err != nil {
55+
return false, err
56+
}
57+
return false, nil
58+
}); err != nil {
59+
return fmt.Errorf("waiting for deletion: %w", err)
60+
}
61+
62+
return nil
63+
}
64+
4865
func deleteWithTimeout(cl deleter, obj client.Object, timeout time.Duration) error {
4966
ctx, cancel := context.WithTimeout(context.Background(), timeout)
5067
defer cancel()

Diff for: internal/pkg/v1/action/operator.go

-9
This file was deleted.

0 commit comments

Comments
 (0)