Skip to content

Commit 1b44365

Browse files
committed
fix empty action config client and update catalog creation unit test
Signed-off-by: Ankita Thomas <[email protected]>
1 parent 7794db4 commit 1b44365

File tree

6 files changed

+153
-68
lines changed

6 files changed

+153
-68
lines changed

internal/cmd/internal/olmv1/printing.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ func printFormattedOperators(extensions ...olmv1.ClusterExtension) {
2121

2222
sortOperators(extensions)
2323
for _, ext := range extensions {
24+
var bundleName, bundleVersion string
25+
if ext.Status.Install != nil {
26+
bundleName = ext.Status.Install.Bundle.Name
27+
bundleVersion = ext.Status.Install.Bundle.Version
28+
}
2429
age := time.Since(ext.CreationTimestamp.Time)
2530
_, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
2631
ext.Name,
27-
ext.Status.Install.Bundle.Name,
28-
ext.Status.Install.Bundle.Version,
32+
bundleName,
33+
bundleVersion,
2934
ext.Spec.Source.SourceType,
3035
status(ext.Status.Conditions, olmv1.TypeInstalled),
3136
status(ext.Status.Conditions, olmv1.TypeProgressing),
@@ -41,13 +46,16 @@ func printFormattedCatalogs(catalogs ...olmv1.ClusterCatalog) {
4146

4247
sortCatalogs(catalogs)
4348
for _, cat := range catalogs {
49+
var lastUnpacked string
50+
if cat.Status.LastUnpacked != nil {
51+
duration.HumanDuration(time.Since(cat.Status.LastUnpacked.Time))
52+
}
4453
age := time.Since(cat.CreationTimestamp.Time)
45-
lastUnpacked := time.Since(cat.Status.LastUnpacked.Time)
4654
_, _ = fmt.Fprintf(tw, "%s\t%s\t%d\t%s\t%s\t%s\n",
4755
cat.Name,
4856
string(cat.Spec.AvailabilityMode),
4957
cat.Spec.Priority,
50-
duration.HumanDuration(lastUnpacked),
58+
lastUnpacked,
5159
status(cat.Status.Conditions, olmv1.TypeServing),
5260
duration.HumanDuration(age),
5361
)
@@ -59,6 +67,9 @@ func printFormattedCatalogs(catalogs ...olmv1.ClusterCatalog) {
5967
// name (asc), version (desc)
6068
func sortOperators(extensions []olmv1.ClusterExtension) {
6169
slices.SortFunc(extensions, func(a, b olmv1.ClusterExtension) int {
70+
if a.Status.Install == nil || b.Status.Install == nil {
71+
return cmp.Compare(a.Name, b.Name)
72+
}
6273
return cmp.Or(
6374
cmp.Compare(a.Name, b.Name),
6475
-semver.MustParse(a.Status.Install.Bundle.Version).Compare(semver.MustParse(b.Status.Install.Bundle.Version)),

internal/cmd/root.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ import (
1111
)
1212

1313
func Execute() {
14-
if err := newCmd().Execute(); err != nil {
14+
cmd, err := newCmd()
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
19+
if err = cmd.Execute(); err != nil {
1520
log.Fatal(err)
1621
}
1722
}
18-
func newCmd() *cobra.Command {
23+
func newCmd() (*cobra.Command, error) {
1924
cmd := &cobra.Command{
2025
Use: "operator",
2126
Short: "Manage operators in a cluster from the command line",
@@ -36,14 +41,15 @@ operators from the installed catalogs.`,
3641
flags := cmd.PersistentFlags()
3742
cfg.BindFlags(flags)
3843
flags.DurationVar(&timeout, "timeout", 1*time.Minute, "The amount of time to wait before giving up on an operation.")
39-
40-
cmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
44+
err := cfg.Load()
45+
if err != nil {
46+
return nil, err
47+
}
48+
cmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
4149
var ctx context.Context
4250
ctx, cancel = context.WithTimeout(cmd.Context(), timeout)
4351

4452
cmd.SetContext(ctx)
45-
46-
return cfg.Load()
4753
}
4854
cmd.PersistentPostRun = func(command *cobra.Command, _ []string) {
4955
cancel()
@@ -62,5 +68,5 @@ operators from the installed catalogs.`,
6268
newVersionCmd(),
6369
)
6470

65-
return cmd
71+
return cmd, nil
6672
}

internal/pkg/v1/action/action_suite_test.go

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,84 @@ import (
1212
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313
"k8s.io/apimachinery/pkg/types"
1414
"sigs.k8s.io/controller-runtime/pkg/client"
15+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
16+
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
1517

18+
"github.com/operator-framework/kubectl-operator/pkg/action"
1619
olmv1 "github.com/operator-framework/operator-controller/api/v1"
1720
)
1821

22+
const (
23+
verbCreate = "create"
24+
)
25+
1926
func TestCommand(t *testing.T) {
2027
RegisterFailHandler(Fail)
2128
RunSpecs(t, "Internal v1 action Suite")
2229
}
2330

24-
type mockCreator struct {
25-
createErr error
26-
createCalled int
27-
}
28-
29-
func (mc *mockCreator) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
30-
mc.createCalled++
31-
return mc.createErr
32-
}
31+
type fakeClient struct {
32+
// Expected errors for create/delete/get.
33+
createErr error
34+
deleteErr error
35+
getErr error
3336

34-
type mockDeleter struct {
35-
deleteErr error
37+
// counters for number of create/delete/get calls seen.
38+
createCalled int
3639
deleteCalled int
37-
}
40+
getCalled int
3841

39-
func (md *mockDeleter) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
40-
md.deleteCalled++
41-
return md.deleteErr
42+
// transformer functions for applying changes to an object
43+
// matching the objectKey prior to an operation of the
44+
// type `verb` (get/create/delete), where the operation is
45+
// not set to error fail with a corresponding error (getErr/createErr/deleteErr).
46+
transformers []objectTransformer
47+
client.Client
4248
}
4349

44-
type mockGetter struct {
45-
getErr error
46-
getCalled int
50+
type objectTransformer struct {
51+
verb string
52+
objectKey client.ObjectKey
53+
transformFunc func(obj *client.Object)
4754
}
4855

49-
func (mg *mockGetter) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
50-
mg.getCalled++
51-
return mg.getErr
56+
func (c *fakeClient) Initialize() error {
57+
scheme, err := action.NewScheme()
58+
if err != nil {
59+
return err
60+
}
61+
clientBuilder := fake.NewClientBuilder().WithInterceptorFuncs(interceptor.Funcs{
62+
Create: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
63+
c.createCalled++
64+
if c.createErr != nil {
65+
return c.createErr
66+
}
67+
objKey := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}
68+
for _, t := range c.transformers {
69+
if t.verb == verbCreate && objKey == t.objectKey && t.transformFunc != nil {
70+
t.transformFunc(&obj)
71+
}
72+
}
73+
// make sure to plumb request through to underlying client
74+
return client.Create(ctx, obj, opts...)
75+
},
76+
Delete: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.DeleteOption) error {
77+
c.deleteCalled++
78+
if c.deleteErr != nil {
79+
return c.deleteErr
80+
}
81+
return client.Delete(ctx, obj, opts...)
82+
},
83+
Get: func(ctx context.Context, client client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
84+
c.getCalled++
85+
if c.getErr != nil {
86+
return c.getErr
87+
}
88+
return client.Get(ctx, key, obj, opts...)
89+
},
90+
}).WithScheme(scheme)
91+
c.Client = clientBuilder.Build()
92+
return nil
5293
}
5394

5495
func setupTestCatalogs(n int) []client.Object {

internal/pkg/v1/action/catalog_create_test.go

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,21 @@ import (
66

77
. "github.com/onsi/ginkgo"
88
. "github.com/onsi/gomega"
9-
109
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/types"
1111
"sigs.k8s.io/controller-runtime/pkg/client"
1212

1313
olmv1 "github.com/operator-framework/operator-controller/api/v1"
1414

1515
internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
1616
)
1717

18-
type mockCreateClient struct {
19-
*mockCreator
20-
*mockGetter
21-
*mockDeleter
22-
createCatalog *olmv1.ClusterCatalog
23-
}
24-
25-
func (mcc *mockCreateClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
26-
mcc.createCatalog = obj.(*olmv1.ClusterCatalog)
27-
return mcc.mockCreator.Create(ctx, obj, opts...)
28-
}
29-
3018
var _ = Describe("CatalogCreate", func() {
19+
catalogName := "testcatalog"
3120
pollInterval := 20
3221
expectedCatalog := olmv1.ClusterCatalog{
3322
ObjectMeta: metav1.ObjectMeta{
34-
Name: "testcatalog",
23+
Name: catalogName,
3524
Labels: map[string]string{"a": "b"},
3625
},
3726
Spec: olmv1.ClusterCatalogSpec{
@@ -49,9 +38,10 @@ var _ = Describe("CatalogCreate", func() {
4938

5039
It("fails creating catalog", func() {
5140
expectedErr := errors.New("create failed")
52-
mockClient := &mockCreateClient{&mockCreator{createErr: expectedErr}, nil, nil, &expectedCatalog}
41+
testClient := fakeClient{createErr: expectedErr}
42+
Expect(testClient.Initialize()).To(Succeed())
5343

54-
creator := internalaction.NewCatalogCreate(mockClient)
44+
creator := internalaction.NewCatalogCreate(testClient)
5545
creator.Available = true
5646
creator.CatalogName = expectedCatalog.Name
5747
creator.ImageSourceRef = expectedCatalog.Spec.Source.Image.Ref
@@ -62,41 +52,78 @@ var _ = Describe("CatalogCreate", func() {
6252

6353
Expect(err).NotTo(BeNil())
6454
Expect(err).To(MatchError(expectedErr))
65-
Expect(mockClient.createCalled).To(Equal(1))
66-
67-
// there is no way of testing a happy path in unit tests because we have no way to
68-
// set/mock the catalog status condition we're waiting for in waitUntilCatalogStatusCondition
69-
// but we can still at least verify that CR would have been created with expected attribute values
70-
validateCreateCatalog(mockClient.createCatalog, &expectedCatalog)
55+
Expect(testClient.createCalled).To(Equal(1))
7156
})
7257

7358
It("fails waiting for created catalog status, successfully cleans up", func() {
7459
expectedErr := errors.New("get failed")
75-
mockClient := &mockCreateClient{&mockCreator{}, &mockGetter{getErr: expectedErr}, &mockDeleter{}, nil}
60+
testClient := fakeClient{getErr: expectedErr}
61+
Expect(testClient.Initialize()).To(Succeed())
7662

77-
creator := internalaction.NewCatalogCreate(mockClient)
63+
creator := internalaction.NewCatalogCreate(testClient)
64+
// fakeClient requires at least the catalogName to be set to run
65+
creator.CatalogName = expectedCatalog.Name
7866
err := creator.Run(context.TODO())
7967

8068
Expect(err).NotTo(BeNil())
8169
Expect(err).To(MatchError(expectedErr))
82-
Expect(mockClient.createCalled).To(Equal(1))
83-
Expect(mockClient.getCalled).To(Equal(1))
84-
Expect(mockClient.deleteCalled).To(Equal(1))
70+
Expect(testClient.createCalled).To(Equal(1))
71+
Expect(testClient.getCalled).To(Equal(1))
72+
Expect(testClient.deleteCalled).To(Equal(1))
8573
})
8674

8775
It("fails waiting for created catalog status, fails clean up", func() {
8876
getErr := errors.New("get failed")
8977
deleteErr := errors.New("delete failed")
90-
mockClient := &mockCreateClient{&mockCreator{}, &mockGetter{getErr: getErr}, &mockDeleter{deleteErr: deleteErr}, nil}
78+
testClient := fakeClient{deleteErr: deleteErr, getErr: getErr}
79+
Expect(testClient.Initialize()).To(Succeed())
9180

92-
creator := internalaction.NewCatalogCreate(mockClient)
81+
creator := internalaction.NewCatalogCreate(testClient)
82+
// fakeClient requires at least the catalogName to be set to run
83+
creator.CatalogName = expectedCatalog.Name
9384
err := creator.Run(context.TODO())
9485

9586
Expect(err).NotTo(BeNil())
9687
Expect(err).To(MatchError(getErr))
97-
Expect(mockClient.createCalled).To(Equal(1))
98-
Expect(mockClient.getCalled).To(Equal(1))
99-
Expect(mockClient.deleteCalled).To(Equal(1))
88+
Expect(testClient.createCalled).To(Equal(1))
89+
Expect(testClient.getCalled).To(Equal(1))
90+
Expect(testClient.deleteCalled).To(Equal(1))
91+
})
92+
It("succeeds creating catalog", func() {
93+
testClient := fakeClient{
94+
transformers: []objectTransformer{
95+
{
96+
verb: verbCreate,
97+
objectKey: types.NamespacedName{Name: catalogName},
98+
transformFunc: func(obj *client.Object) {
99+
if obj == nil {
100+
return
101+
}
102+
catalogObj, ok := (*obj).(*olmv1.ClusterCatalog)
103+
if !ok {
104+
return
105+
}
106+
catalogObj.Status.Conditions = []metav1.Condition{{Type: olmv1.TypeServing, Status: metav1.ConditionTrue}}
107+
},
108+
},
109+
},
110+
}
111+
Expect(testClient.Initialize()).To(Succeed())
112+
113+
creator := internalaction.NewCatalogCreate(testClient)
114+
creator.Available = true
115+
creator.CatalogName = expectedCatalog.Name
116+
creator.ImageSourceRef = expectedCatalog.Spec.Source.Image.Ref
117+
creator.Priority = expectedCatalog.Spec.Priority
118+
creator.Labels = expectedCatalog.Labels
119+
creator.PollIntervalMinutes = *expectedCatalog.Spec.Source.Image.PollIntervalMinutes
120+
Expect(creator.Run(context.TODO())).To(Succeed())
121+
122+
Expect(testClient.createCalled).To(Equal(1))
123+
124+
actualCatalog := &olmv1.ClusterCatalog{TypeMeta: metav1.TypeMeta{Kind: "ClusterCatalog", APIVersion: "olm.operatorframework.io/v1"}}
125+
Expect(testClient.Client.Get(context.TODO(), types.NamespacedName{Name: catalogName}, actualCatalog)).To(Succeed())
126+
validateCreateCatalog(actualCatalog, &expectedCatalog)
100127
})
101128
})
102129

internal/pkg/v1/action/helpers.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,8 @@ func deleteWithTimeout(cl deleter, obj client.Object, timeout time.Duration) err
8080
return nil
8181
}
8282

83-
func waitForDeletion(ctx context.Context, cl client.Client, objs ...client.Object) error {
83+
func waitForDeletion(ctx context.Context, cl getter, objs ...client.Object) error {
8484
for _, obj := range objs {
85-
obj := obj
8685
lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
8786
key := objectKeyForObject(obj)
8887
if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {

internal/pkg/v1/action/operator_update_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,9 @@ var _ = Describe("OperatorUpdate", func() {
145145
cfg := setupEnv(testExt, buildExtension("test2"), buildExtension("test3"))
146146

147147
go func() {
148-
Eventually(updateOperatorConditionStatus("test", cfg.Client, olmv1.TypeInstalled, metav1.ConditionTrue)).
149-
WithTimeout(5 * time.Second).WithPolling(200 * time.Second).
148+
Eventually(updateOperatorConditionStatus).
149+
WithArguments("test", cfg.Client, olmv1.TypeInstalled, metav1.ConditionTrue).
150+
WithTimeout(5 * time.Second).WithPolling(200 * time.Millisecond).
150151
Should(Succeed())
151152
}()
152153

0 commit comments

Comments
 (0)