Skip to content

Commit bb0242f

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 bb0242f

File tree

6 files changed

+154
-67
lines changed

6 files changed

+154
-67
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: 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_test.go

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,20 @@ 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"
1617
)
1718

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-
3019
var _ = Describe("CatalogCreate", func() {
20+
catalogName := "testcatalog"
3121
pollInterval := 20
3222
expectedCatalog := olmv1.ClusterCatalog{
3323
ObjectMeta: metav1.ObjectMeta{
34-
Name: "testcatalog",
24+
Name: catalogName,
3525
Labels: map[string]string{"a": "b"},
3626
},
3727
Spec: olmv1.ClusterCatalogSpec{
@@ -49,9 +39,10 @@ var _ = Describe("CatalogCreate", func() {
4939

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

54-
creator := internalaction.NewCatalogCreate(mockClient)
45+
creator := internalaction.NewCatalogCreate(testClient)
5546
creator.Available = true
5647
creator.CatalogName = expectedCatalog.Name
5748
creator.ImageSourceRef = expectedCatalog.Spec.Source.Image.Ref
@@ -62,41 +53,78 @@ var _ = Describe("CatalogCreate", func() {
6253

6354
Expect(err).NotTo(BeNil())
6455
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)
56+
Expect(testClient.createCalled).To(Equal(1))
7157
})
7258

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

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

8069
Expect(err).NotTo(BeNil())
8170
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))
71+
Expect(testClient.createCalled).To(Equal(1))
72+
Expect(testClient.getCalled).To(Equal(1))
73+
Expect(testClient.deleteCalled).To(Equal(1))
8574
})
8675

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

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

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

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)