Skip to content

Commit 95f75bd

Browse files
committed
fix empty action config client and update catalog creation unit test
Also adding some nil value checks for printing clusterCatalogs and clusterExtensions. Signed-off-by: Ankita Thomas <[email protected]>
1 parent 7794db4 commit 95f75bd

File tree

8 files changed

+152
-78
lines changed

8 files changed

+152
-78
lines changed

internal/cmd/internal/olmv1/catalog_create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
// NewCatalogCreateCmd allows creating a new catalog
1515
func NewCatalogCreateCmd(cfg *action.Configuration) *cobra.Command {
16-
i := v1action.NewCatalogCreate(cfg.Client)
16+
i := v1action.NewCatalogCreate(cfg)
1717
i.Logf = log.Printf
1818

1919
cmd := &cobra.Command{

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/pkg/v1/action/action_suite_test.go

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,85 @@ 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

1618
olmv1 "github.com/operator-framework/operator-controller/api/v1"
19+
20+
"github.com/operator-framework/kubectl-operator/pkg/action"
21+
)
22+
23+
const (
24+
verbCreate = "create"
1725
)
1826

1927
func TestCommand(t *testing.T) {
2028
RegisterFailHandler(Fail)
2129
RunSpecs(t, "Internal v1 action Suite")
2230
}
2331

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-
}
32+
type fakeClient struct {
33+
// Expected errors for create/delete/get.
34+
createErr error
35+
deleteErr error
36+
getErr error
3337

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

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

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

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

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

internal/pkg/v1/action/catalog_create.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,12 @@ import (
77
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
88

99
olmv1 "github.com/operator-framework/operator-controller/api/v1"
10-
)
1110

12-
type createClient interface {
13-
creator
14-
deleter
15-
getter
16-
}
11+
"github.com/operator-framework/kubectl-operator/pkg/action"
12+
)
1713

1814
type CatalogCreate struct {
19-
client createClient
15+
config *action.Configuration
2016
CatalogName string
2117
ImageSourceRef string
2218

@@ -29,28 +25,28 @@ type CatalogCreate struct {
2925
Logf func(string, ...interface{})
3026
}
3127

32-
func NewCatalogCreate(client createClient) *CatalogCreate {
28+
func NewCatalogCreate(config *action.Configuration) *CatalogCreate {
3329
return &CatalogCreate{
34-
client: client,
30+
config: config,
3531
Logf: func(string, ...interface{}) {},
3632
}
3733
}
3834

3935
func (i *CatalogCreate) Run(ctx context.Context) error {
4036
catalog := i.buildCatalog()
41-
if err := i.client.Create(ctx, &catalog); err != nil {
37+
if err := i.config.Client.Create(ctx, &catalog); err != nil {
4238
return err
4339
}
4440

4541
var err error
4642
if i.Available {
47-
err = waitUntilCatalogStatusCondition(ctx, i.client, &catalog, olmv1.TypeServing, metav1.ConditionTrue)
43+
err = waitUntilCatalogStatusCondition(ctx, i.config.Client, &catalog, olmv1.TypeServing, metav1.ConditionTrue)
4844
} else {
49-
err = waitUntilCatalogStatusCondition(ctx, i.client, &catalog, olmv1.TypeServing, metav1.ConditionFalse)
45+
err = waitUntilCatalogStatusCondition(ctx, i.config.Client, &catalog, olmv1.TypeServing, metav1.ConditionFalse)
5046
}
5147

5248
if err != nil {
53-
if cleanupErr := deleteWithTimeout(i.client, &catalog, i.CleanupTimeout); cleanupErr != nil {
49+
if cleanupErr := deleteWithTimeout(i.config.Client, &catalog, i.CleanupTimeout); cleanupErr != nil {
5450
i.Logf("cleaning up failed catalog: %v", cleanupErr)
5551
}
5652
return err

internal/pkg/v1/action/catalog_create_test.go

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,21 @@ import (
88
. "github.com/onsi/gomega"
99

1010
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/types"
1112
"sigs.k8s.io/controller-runtime/pkg/client"
1213

1314
olmv1 "github.com/operator-framework/operator-controller/api/v1"
1415

1516
internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
17+
"github.com/operator-framework/kubectl-operator/pkg/action"
1618
)
1719

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-
3020
var _ = Describe("CatalogCreate", func() {
21+
catalogName := "testcatalog"
3122
pollInterval := 20
3223
expectedCatalog := olmv1.ClusterCatalog{
3324
ObjectMeta: metav1.ObjectMeta{
34-
Name: "testcatalog",
25+
Name: catalogName,
3526
Labels: map[string]string{"a": "b"},
3627
},
3728
Spec: olmv1.ClusterCatalogSpec{
@@ -49,9 +40,10 @@ var _ = Describe("CatalogCreate", func() {
4940

5041
It("fails creating catalog", func() {
5142
expectedErr := errors.New("create failed")
52-
mockClient := &mockCreateClient{&mockCreator{createErr: expectedErr}, nil, nil, &expectedCatalog}
43+
testClient := fakeClient{createErr: expectedErr}
44+
Expect(testClient.Initialize()).To(Succeed())
5345

54-
creator := internalaction.NewCatalogCreate(mockClient)
46+
creator := internalaction.NewCatalogCreate(&action.Configuration{Client: testClient})
5547
creator.Available = true
5648
creator.CatalogName = expectedCatalog.Name
5749
creator.ImageSourceRef = expectedCatalog.Spec.Source.Image.Ref
@@ -62,41 +54,78 @@ var _ = Describe("CatalogCreate", func() {
6254

6355
Expect(err).NotTo(BeNil())
6456
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)
57+
Expect(testClient.createCalled).To(Equal(1))
7158
})
7259

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

77-
creator := internalaction.NewCatalogCreate(mockClient)
65+
creator := internalaction.NewCatalogCreate(&action.Configuration{Client: testClient})
66+
// fakeClient requires at least the catalogName to be set to run
67+
creator.CatalogName = expectedCatalog.Name
7868
err := creator.Run(context.TODO())
7969

8070
Expect(err).NotTo(BeNil())
8171
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))
72+
Expect(testClient.createCalled).To(Equal(1))
73+
Expect(testClient.getCalled).To(Equal(1))
74+
Expect(testClient.deleteCalled).To(Equal(1))
8575
})
8676

8777
It("fails waiting for created catalog status, fails clean up", func() {
8878
getErr := errors.New("get failed")
8979
deleteErr := errors.New("delete failed")
90-
mockClient := &mockCreateClient{&mockCreator{}, &mockGetter{getErr: getErr}, &mockDeleter{deleteErr: deleteErr}, nil}
80+
testClient := fakeClient{deleteErr: deleteErr, getErr: getErr}
81+
Expect(testClient.Initialize()).To(Succeed())
9182

92-
creator := internalaction.NewCatalogCreate(mockClient)
83+
creator := internalaction.NewCatalogCreate(&action.Configuration{Client: testClient})
84+
// fakeClient requires at least the catalogName to be set to run
85+
creator.CatalogName = expectedCatalog.Name
9386
err := creator.Run(context.TODO())
9487

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

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/interfaces.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ import (
66
"sigs.k8s.io/controller-runtime/pkg/client"
77
)
88

9-
type creator interface {
10-
Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error
11-
}
12-
139
type deleter interface {
1410
Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error
1511
}

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)