Skip to content

Commit 99de912

Browse files
authored
Add ginkgo/gomega tests for list-operands (#41)
1 parent 8397c4b commit 99de912

File tree

6 files changed

+302
-3
lines changed

6 files changed

+302
-3
lines changed

.github/workflows/ci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717
with:
1818
go-version: 1.16
1919

20+
- name: Test
21+
run: make test
22+
2023
- name: Lint
2124
run: make lint
2225

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ all: install
2626
build:
2727
go build $(GO_BUILD_ARGS) -o bin/kubectl-operator
2828

29+
.PHONY: test
30+
test:
31+
go test ./...
32+
2933
.PHONY: install
3034
install: build
3135
install bin/kubectl-operator $(shell go env GOPATH)/bin

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.16
44

55
require (
66
github.com/containerd/containerd v1.4.3
7+
github.com/onsi/ginkgo v1.14.1
8+
github.com/onsi/gomega v1.10.2
79
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6
810
github.com/operator-framework/api v0.7.1
911
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200521062108-408ca95d458f

pkg/action/action_suite_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package action_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestAction(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Action Suite")
13+
}

pkg/action/operator_list_operands.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,12 @@ func (o *OperatorListOperands) list(ctx context.Context, crdDesc v1alpha1.CRDDes
105105
if err != nil {
106106
return nil, fmt.Errorf("get crd %q: %v", crdKey.String(), err)
107107
}
108-
group := crd.Spec.Group
109108

110109
list := unstructured.UnstructuredList{}
111110
gvk := schema.GroupVersionKind{
112-
Group: group,
111+
Group: crd.Spec.Group,
113112
Version: crdDesc.Version,
114-
Kind: crdDesc.Kind,
113+
Kind: crd.Spec.Names.ListKind,
115114
}
116115
list.SetGroupVersionKind(gvk)
117116
if err := o.config.Client.List(ctx, &list); err != nil {
+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
package action_test
2+
3+
import (
4+
"context"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
v1 "github.com/operator-framework/api/pkg/operators/v1"
9+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
10+
corev1 "k8s.io/api/core/v1"
11+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
"k8s.io/apimachinery/pkg/types"
16+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
17+
18+
"github.com/operator-framework/kubectl-operator/pkg/action"
19+
)
20+
21+
var _ = Describe("OperatorListOperands", func() {
22+
var (
23+
cfg action.Configuration
24+
operator *v1.Operator
25+
csv *v1alpha1.ClusterServiceVersion
26+
crd *apiextv1.CustomResourceDefinition
27+
og *v1.OperatorGroup
28+
etcdcluster1 *unstructured.Unstructured
29+
etcdcluster2 *unstructured.Unstructured
30+
etcdcluster3 *unstructured.Unstructured
31+
)
32+
33+
BeforeEach(func() {
34+
sch, err := action.NewScheme()
35+
Expect(err).To(BeNil())
36+
37+
etcdclusterGVK := schema.GroupVersionKind{
38+
Group: "etcd.database.coreos.com",
39+
Version: "v1beta2",
40+
Kind: "EtcdCluster",
41+
}
42+
43+
sch.AddKnownTypeWithName(etcdclusterGVK, &unstructured.Unstructured{})
44+
sch.AddKnownTypeWithName(schema.GroupVersionKind{
45+
Group: "etcd.database.coreos.com",
46+
Version: "v1beta2",
47+
Kind: "EtcdClusterList",
48+
}, &unstructured.UnstructuredList{})
49+
50+
operator = &v1.Operator{
51+
ObjectMeta: metav1.ObjectMeta{Name: "etcd.etcd-namespace"},
52+
Status: v1.OperatorStatus{
53+
Components: &v1.Components{
54+
Refs: []v1.RichReference{
55+
{
56+
ObjectReference: &corev1.ObjectReference{
57+
APIVersion: "operators.coreos.com/v1alpha1",
58+
Kind: "ClusterServiceVersion",
59+
Name: "etcdoperator.v0.9.4-clusterwide",
60+
Namespace: "etcd-namespace",
61+
},
62+
},
63+
},
64+
},
65+
},
66+
}
67+
68+
csv = &v1alpha1.ClusterServiceVersion{
69+
ObjectMeta: metav1.ObjectMeta{
70+
Name: "etcdoperator.v0.9.4-clusterwide",
71+
Namespace: "etcd-namespace",
72+
},
73+
Spec: v1alpha1.ClusterServiceVersionSpec{
74+
CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
75+
Owned: []v1alpha1.CRDDescription{
76+
{
77+
Name: "etcdclusters.etcd.database.coreos.com",
78+
Version: "v1beta2",
79+
Kind: "EtcdCluster",
80+
},
81+
},
82+
},
83+
},
84+
Status: v1alpha1.ClusterServiceVersionStatus{Phase: v1alpha1.CSVPhaseSucceeded},
85+
}
86+
87+
og = &v1.OperatorGroup{
88+
ObjectMeta: metav1.ObjectMeta{
89+
Name: "etcd",
90+
Namespace: "etcd-namespace",
91+
},
92+
Status: v1.OperatorGroupStatus{Namespaces: []string{""}},
93+
}
94+
95+
crd = &apiextv1.CustomResourceDefinition{
96+
ObjectMeta: metav1.ObjectMeta{
97+
Name: "etcdclusters.etcd.database.coreos.com",
98+
},
99+
Spec: apiextv1.CustomResourceDefinitionSpec{
100+
Group: "etcd.database.coreos.com",
101+
Names: apiextv1.CustomResourceDefinitionNames{
102+
ListKind: "EtcdClusterList",
103+
},
104+
},
105+
}
106+
etcdcluster1 = &unstructured.Unstructured{}
107+
etcdcluster1.SetGroupVersionKind(etcdclusterGVK)
108+
etcdcluster1.SetNamespace("ns1")
109+
etcdcluster1.SetName("cluster1")
110+
111+
etcdcluster2 = &unstructured.Unstructured{}
112+
etcdcluster2.SetGroupVersionKind(etcdclusterGVK)
113+
etcdcluster2.SetNamespace("ns2")
114+
etcdcluster2.SetName("cluster2")
115+
116+
etcdcluster3 = &unstructured.Unstructured{}
117+
etcdcluster3.SetGroupVersionKind(etcdclusterGVK)
118+
// Empty namespace to simulate cluster-scoped object.
119+
etcdcluster3.SetNamespace("")
120+
etcdcluster3.SetName("cluster3")
121+
122+
cl := fake.NewClientBuilder().
123+
WithObjects(operator, csv, og, crd, etcdcluster1, etcdcluster2, etcdcluster3).
124+
WithScheme(sch).
125+
Build()
126+
cfg.Scheme = sch
127+
cfg.Client = cl
128+
cfg.Namespace = "etcd-namespace"
129+
})
130+
131+
It("should fail due to missing operator", func() {
132+
lister := action.NewOperatorListOperands(&cfg)
133+
_, err := lister.Run(context.TODO(), "missing")
134+
Expect(err.Error()).To(ContainSubstring("package missing.etcd-namespace not found"))
135+
})
136+
137+
It("should fail due to missing operator components", func() {
138+
operator.Status.Components = nil
139+
Expect(cfg.Client.Update(context.TODO(), operator)).To(Succeed())
140+
141+
lister := action.NewOperatorListOperands(&cfg)
142+
_, err := lister.Run(context.TODO(), "etcd")
143+
Expect(err.Error()).To(ContainSubstring("could not find underlying components for operator"))
144+
})
145+
146+
It("should fail due to missing CSV in operator components", func() {
147+
operator.Status.Components = &v1.Components{}
148+
Expect(cfg.Client.Update(context.TODO(), operator)).To(Succeed())
149+
150+
lister := action.NewOperatorListOperands(&cfg)
151+
_, err := lister.Run(context.TODO(), "etcd")
152+
Expect(err.Error()).To(ContainSubstring("could not find underlying CSV for operator"))
153+
})
154+
155+
It("should fail due to missing CSV in cluster", func() {
156+
Expect(cfg.Client.Delete(context.TODO(), csv)).To(Succeed())
157+
158+
lister := action.NewOperatorListOperands(&cfg)
159+
_, err := lister.Run(context.TODO(), "etcd")
160+
Expect(err.Error()).To(ContainSubstring("could not get etcd-namespace/etcdoperator.v0.9.4-clusterwide CSV on cluster"))
161+
})
162+
163+
It("should fail if the CSV has no owned CRDs", func() {
164+
csv.Spec.CustomResourceDefinitions.Owned = nil
165+
Expect(cfg.Client.Update(context.TODO(), csv)).To(Succeed())
166+
167+
lister := action.NewOperatorListOperands(&cfg)
168+
_, err := lister.Run(context.TODO(), "etcd")
169+
Expect(err.Error()).To(ContainSubstring("no owned CustomResourceDefinitions specified on CSV etcd-namespace/etcdoperator.v0.9.4-clusterwide"))
170+
})
171+
172+
It("should fail if the CSV is not in phase Succeeded", func() {
173+
csv.Status.Phase = v1alpha1.CSVPhaseFailed
174+
Expect(cfg.Client.Update(context.TODO(), csv)).To(Succeed())
175+
176+
lister := action.NewOperatorListOperands(&cfg)
177+
_, err := lister.Run(context.TODO(), "etcd")
178+
Expect(err.Error()).To(ContainSubstring("CSV underlying operator is not in a succeeded state"))
179+
})
180+
181+
It("should fail if there is not exactly 1 operator group", func() {
182+
Expect(cfg.Client.Delete(context.TODO(), og)).To(Succeed())
183+
184+
lister := action.NewOperatorListOperands(&cfg)
185+
_, err := lister.Run(context.TODO(), "etcd")
186+
Expect(err.Error()).To(ContainSubstring("unexpected number (0) of operator groups found in namespace etcd"))
187+
})
188+
189+
It("should fail if an owned CRD does not exist", func() {
190+
Expect(cfg.Client.Delete(context.TODO(), crd)).To(Succeed())
191+
192+
lister := action.NewOperatorListOperands(&cfg)
193+
_, err := lister.Run(context.TODO(), "etcd")
194+
Expect(err.Error()).To(ContainSubstring("customresourcedefinitions.apiextensions.k8s.io \"etcdclusters.etcd.database.coreos.com\" not found"))
195+
})
196+
197+
It("should return zero operands when none exist", func() {
198+
Expect(cfg.Client.Delete(context.TODO(), etcdcluster1)).To(Succeed())
199+
Expect(cfg.Client.Delete(context.TODO(), etcdcluster2)).To(Succeed())
200+
Expect(cfg.Client.Delete(context.TODO(), etcdcluster3)).To(Succeed())
201+
202+
lister := action.NewOperatorListOperands(&cfg)
203+
operands, err := lister.Run(context.TODO(), "etcd")
204+
Expect(err).To(BeNil())
205+
Expect(operands.Items).To(HaveLen(0))
206+
})
207+
208+
It("should return operands from all namespaces", func() {
209+
lister := action.NewOperatorListOperands(&cfg)
210+
operands, err := lister.Run(context.TODO(), "etcd")
211+
Expect(err).To(BeNil())
212+
Expect(getObjectNames(*operands)).To(ConsistOf(
213+
types.NamespacedName{Name: "cluster1", Namespace: "ns1"},
214+
types.NamespacedName{Name: "cluster2", Namespace: "ns2"},
215+
types.NamespacedName{Name: "cluster3", Namespace: ""},
216+
))
217+
})
218+
219+
It("should return operands from scoped namespaces", func() {
220+
og.Status.Namespaces = []string{"ns1", "ns2"}
221+
Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed())
222+
223+
lister := action.NewOperatorListOperands(&cfg)
224+
operands, err := lister.Run(context.TODO(), "etcd")
225+
Expect(err).To(BeNil())
226+
Expect(getObjectNames(*operands)).To(ConsistOf(
227+
types.NamespacedName{Name: "cluster1", Namespace: "ns1"},
228+
types.NamespacedName{Name: "cluster2", Namespace: "ns2"},
229+
types.NamespacedName{Name: "cluster3", Namespace: ""},
230+
))
231+
})
232+
233+
It("should return operands from scoped namespace ns1", func() {
234+
og.Status.Namespaces = []string{"ns1"}
235+
Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed())
236+
237+
lister := action.NewOperatorListOperands(&cfg)
238+
operands, err := lister.Run(context.TODO(), "etcd")
239+
Expect(err).To(BeNil())
240+
Expect(getObjectNames(*operands)).To(ConsistOf(
241+
types.NamespacedName{Name: "cluster1", Namespace: "ns1"},
242+
types.NamespacedName{Name: "cluster3", Namespace: ""},
243+
))
244+
})
245+
246+
It("should return operands from scoped namespace ns2", func() {
247+
og.Status.Namespaces = []string{"ns2"}
248+
Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed())
249+
250+
lister := action.NewOperatorListOperands(&cfg)
251+
operands, err := lister.Run(context.TODO(), "etcd")
252+
Expect(err).To(BeNil())
253+
Expect(getObjectNames(*operands)).To(ConsistOf(
254+
types.NamespacedName{Name: "cluster2", Namespace: "ns2"},
255+
types.NamespacedName{Name: "cluster3", Namespace: ""},
256+
))
257+
})
258+
259+
It("should return cluster-scoped operands regardless of operator groups targetnamespaces", func() {
260+
og.Status.Namespaces = []string{"other"}
261+
Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed())
262+
263+
lister := action.NewOperatorListOperands(&cfg)
264+
operands, err := lister.Run(context.TODO(), "etcd")
265+
Expect(err).To(BeNil())
266+
Expect(getObjectNames(*operands)).To(ConsistOf(
267+
types.NamespacedName{Name: "cluster3", Namespace: ""},
268+
))
269+
})
270+
})
271+
272+
func getObjectNames(objects unstructured.UnstructuredList) []types.NamespacedName {
273+
out := []types.NamespacedName{}
274+
for _, u := range objects.Items {
275+
out = append(out, types.NamespacedName{Name: u.GetName(), Namespace: u.GetNamespace()})
276+
}
277+
return out
278+
}

0 commit comments

Comments
 (0)