1 package binding
2
3 import (
4 "fmt"
5 "testing"
6
7 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
8 "github.com/devfile/library/v2/pkg/devfile/parser"
9 devfileCtx "github.com/devfile/library/v2/pkg/devfile/parser/context"
10 "github.com/devfile/library/v2/pkg/testingutil/filesystem"
11 "github.com/golang/mock/gomock"
12 "github.com/google/go-cmp/cmp"
13 "github.com/google/go-cmp/cmp/cmpopts"
14 appsv1 "k8s.io/api/apps/v1"
15 "k8s.io/apimachinery/pkg/api/meta"
16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18 "k8s.io/apimachinery/pkg/runtime/schema"
19
20 servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
21
22 "github.com/redhat-developer/odo/pkg/kclient"
23 odoTestingUtil "github.com/redhat-developer/odo/pkg/testingutil"
24 )
25
26 const deploymentKind = "Deployment"
27
28 var deploymentGVK = appsv1.SchemeGroupVersion.WithKind(deploymentKind)
29 var deploymentApiVersion, _ = deploymentGVK.ToAPIVersionAndKind()
30
31 const clusterKind = "Cluster"
32
33 var clusterGV = schema.GroupVersion{
34 Group: "postgresql.k8s.enterprisedb.io",
35 Version: "v1",
36 }
37 var clusterGVK = clusterGV.WithKind(clusterKind)
38 var clusterGVR = clusterGV.WithResource("clusters")
39 var ClusterAPIVersion, _ = clusterGVK.ToAPIVersionAndKind()
40
41 func TestBindingClient_GetFlags(t *testing.T) {
42 type args struct {
43 flags map[string]string
44 }
45 tests := []struct {
46 name string
47 args args
48 want map[string]string
49 }{
50 {
51 name: "service and name flags are present",
52 args: args{flags: map[string]string{"service": "redisService", "name": "mybinding", "v": "9"}},
53 want: map[string]string{"service": "redisService", "name": "mybinding"},
54 },
55 {
56 name: "only one flag is present",
57 args: args{map[string]string{"service": "redisService", "v": "9"}},
58 want: map[string]string{"service": "redisService"},
59 },
60 {
61 name: "no relevant flags are present",
62 args: args{map[string]string{"v": "9"}},
63 want: map[string]string{},
64 },
65 }
66 for _, tt := range tests {
67 t.Run(tt.name, func(t *testing.T) {
68 o := &BindingClient{}
69 got := o.GetFlags(tt.args.flags)
70 if diff := cmp.Diff(tt.want, got); diff != "" {
71 t.Errorf("BindingClient.GetFlags() mismatch (-want +got):\n%s", diff)
72 }
73 })
74 }
75 }
76
77 func TestBindingClient_GetServiceInstances(t *testing.T) {
78 ns := "my-ns"
79
80 var clusterUnstructured unstructured.Unstructured
81
82 clusterUnstructured.SetGroupVersionKind(clusterGVK)
83 clusterUnstructured.SetName("postgres-cluster")
84
85 serviceBindingInstance := servicebinding.BindableKinds{
86 TypeMeta: metav1.TypeMeta{
87 Kind: "BindableKinds",
88 APIVersion: "v1alpha1",
89 },
90 ObjectMeta: metav1.ObjectMeta{
91 Name: "bindable-kinds",
92 },
93 Status: []servicebinding.BindableKindsStatus{
94 {
95 Group: "redis.redis.opstreelabs.in",
96 Kind: "Redis",
97 Version: "v1beta1",
98 },
99 {
100 Group: "postgresql.k8s.enterprisedb.io",
101 Kind: "Cluster",
102 Version: "v1",
103 },
104 },
105 }
106 type fields struct {
107 kubernetesClient func(ctrl *gomock.Controller) kclient.ClientInterface
108 }
109 type args struct {
110 namespace string
111 }
112 tests := []struct {
113 name string
114 fields fields
115 args args
116 want map[string]unstructured.Unstructured
117 wantErr bool
118 }{
119 {
120 name: "obtained service instances",
121 fields: fields{
122 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
123 client := kclient.NewMockClientInterface(ctrl)
124 client.EXPECT().IsServiceBindingSupported().Return(true, nil)
125 client.EXPECT().GetBindableKinds().Return(serviceBindingInstance, nil)
126 client.EXPECT().GetBindableKindStatusRestMapping(serviceBindingInstance.Status).Return([]*meta.RESTMapping{
127 {Resource: clusterGVR, GroupVersionKind: clusterGVK},
128 }, nil)
129
130 client.EXPECT().ListDynamicResources("", clusterGVR, "").Return(&unstructured.UnstructuredList{Items: []unstructured.Unstructured{clusterUnstructured}}, nil)
131 return client
132 },
133 },
134 want: map[string]unstructured.Unstructured{
135 "postgres-cluster (Cluster.postgresql.k8s.enterprisedb.io)": clusterUnstructured,
136 },
137 wantErr: false,
138 },
139 {
140 name: "obtained service instances from specific namespace",
141 fields: fields{
142 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
143 client := kclient.NewMockClientInterface(ctrl)
144 client.EXPECT().IsServiceBindingSupported().Return(true, nil)
145 client.EXPECT().GetBindableKinds().Return(serviceBindingInstance, nil)
146 client.EXPECT().GetBindableKindStatusRestMapping(serviceBindingInstance.Status).Return([]*meta.RESTMapping{
147 {Resource: clusterGVR, GroupVersionKind: clusterGVK},
148 }, nil)
149
150 client.EXPECT().ListDynamicResources(ns, clusterGVR, "").Return(&unstructured.UnstructuredList{Items: []unstructured.Unstructured{clusterUnstructured}}, nil)
151 return client
152 },
153 },
154 args: args{
155 namespace: ns,
156 },
157 want: map[string]unstructured.Unstructured{
158 "postgres-cluster (Cluster.postgresql.k8s.enterprisedb.io)": clusterUnstructured,
159 },
160 wantErr: false,
161 },
162 {
163 name: "do not fail if no bindable kind service was found",
164 fields: fields{
165 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
166 client := kclient.NewMockClientInterface(ctrl)
167 client.EXPECT().IsServiceBindingSupported().Return(true, nil)
168 client.EXPECT().GetBindableKinds().Return(serviceBindingInstance, nil)
169 client.EXPECT().GetBindableKindStatusRestMapping(serviceBindingInstance.Status).Return(nil, nil)
170 return client
171 },
172 },
173 want: map[string]unstructured.Unstructured{},
174 wantErr: false,
175 },
176 {
177 name: "do not fail if no instances of the bindable kind services was found",
178 fields: fields{kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
179 client := kclient.NewMockClientInterface(ctrl)
180 client.EXPECT().IsServiceBindingSupported().Return(true, nil)
181 client.EXPECT().GetBindableKinds().Return(serviceBindingInstance, nil)
182 client.EXPECT().GetBindableKindStatusRestMapping(serviceBindingInstance.Status).Return([]*meta.RESTMapping{
183 {Resource: clusterGVR, GroupVersionKind: clusterGVK},
184 }, nil)
185
186 client.EXPECT().ListDynamicResources("", clusterGVR, "").Return(&unstructured.UnstructuredList{Items: nil}, nil)
187 return client
188 }},
189 want: map[string]unstructured.Unstructured{},
190 wantErr: false,
191 },
192 {
193 name: "error out if the servicebinding CRD is not found",
194 fields: fields{kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
195 client := kclient.NewMockClientInterface(ctrl)
196 client.EXPECT().IsServiceBindingSupported().Return(false, nil)
197 return client
198 }},
199 want: nil,
200 wantErr: true,
201 },
202 }
203 for _, tt := range tests {
204 t.Run(tt.name, func(t *testing.T) {
205 ctrl := gomock.NewController(t)
206 o := &BindingClient{
207 kubernetesClient: tt.fields.kubernetesClient(ctrl),
208 }
209 got, err := o.GetServiceInstances(tt.args.namespace)
210 if (err != nil) != tt.wantErr {
211 t.Errorf("GetServiceInstances() error = %v, wantErr %v", err, tt.wantErr)
212 return
213 }
214 if diff := cmp.Diff(tt.want, got); diff != "" {
215 t.Errorf("BindingClient.GetServiceInstances() mismatch (-want +got):\n%s", diff)
216 }
217 })
218 }
219 }
220
221 func TestBindingClient_AddBindingToDevfile(t *testing.T) {
222 bindingName := "my-nodejs-app-cluster-sample"
223 ns := "my-ns"
224
225 var clusterUnstructured unstructured.Unstructured
226 clusterUnstructured.SetGroupVersionKind(clusterGVK)
227 clusterUnstructured.SetName("cluster-sample")
228
229 serviceBindingRef := servicebinding.Service{
230 Id: &bindingName,
231 NamespacedRef: servicebinding.NamespacedRef{
232 Ref: servicebinding.Ref{
233 Group: clusterGVK.Group,
234 Version: clusterGVK.Version,
235 Kind: clusterGVK.Kind,
236 Name: clusterUnstructured.GetName(),
237 Resource: "clusters",
238 },
239 },
240 }
241
242 serviceBindingRefWithNs := servicebinding.Service{
243 Id: &bindingName,
244 NamespacedRef: servicebinding.NamespacedRef{
245 Ref: servicebinding.Ref{
246 Group: clusterGVK.Group,
247 Version: clusterGVK.Version,
248 Kind: clusterGVK.Kind,
249 Name: clusterUnstructured.GetName(),
250 Resource: "clusters",
251 },
252 Namespace: &ns,
253 },
254 }
255
256 type fields struct {
257 kubernetesClient func(ctrl *gomock.Controller) kclient.ClientInterface
258 }
259 type args struct {
260 bindingName string
261 bindAsFiles bool
262 namingStrategy string
263 namespace string
264 unstructuredService unstructured.Unstructured
265 obj parser.DevfileObj
266 }
267 tests := []struct {
268 name string
269 fields fields
270 args args
271 want *parser.DevfileObj
272 wantErr bool
273 }{
274 {
275 name: "successfully add binding",
276 fields: fields{
277 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
278 client := kclient.NewMockClientInterface(ctrl)
279 client.EXPECT().NewServiceBindingServiceObject("", clusterUnstructured, bindingName).Return(serviceBindingRef, nil)
280 client.EXPECT().GetDeploymentAPIVersion().Return(deploymentGVK, nil)
281 return client
282 },
283 },
284 args: args{
285 bindingName: bindingName,
286 bindAsFiles: false,
287 unstructuredService: clusterUnstructured,
288 obj: odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs()),
289 },
290 want: getDevfileObjWithServiceBinding(bindingName, "", false, ""),
291 wantErr: false,
292 },
293 {
294 name: "successfully add binding with namespace",
295 fields: fields{
296 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
297 client := kclient.NewMockClientInterface(ctrl)
298 client.EXPECT().NewServiceBindingServiceObject(ns, clusterUnstructured, bindingName).Return(serviceBindingRefWithNs, nil)
299 client.EXPECT().GetDeploymentAPIVersion().Return(deploymentGVK, nil)
300 return client
301 },
302 },
303 args: args{
304 bindingName: bindingName,
305 bindAsFiles: false,
306 namespace: ns,
307 unstructuredService: clusterUnstructured,
308 obj: odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs()),
309 },
310 want: getDevfileObjWithServiceBinding(bindingName, ns, false, ""),
311 wantErr: false,
312 },
313 {
314 name: "successfully added binding for a Service Binding bound as files",
315 fields: fields{
316 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
317 client := kclient.NewMockClientInterface(ctrl)
318 client.EXPECT().NewServiceBindingServiceObject("", clusterUnstructured, bindingName).Return(serviceBindingRef, nil)
319 client.EXPECT().GetDeploymentAPIVersion().Return(deploymentGVK, nil)
320 return client
321 },
322 },
323 args: args{
324 bindingName: bindingName,
325 bindAsFiles: true,
326 unstructuredService: clusterUnstructured,
327 obj: odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs()),
328 },
329 want: getDevfileObjWithServiceBinding(bindingName, "", true, ""),
330 wantErr: false,
331 },
332 {
333 name: "successfully added binding for a Service Binding bound as files and namespace",
334 fields: fields{
335 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
336 client := kclient.NewMockClientInterface(ctrl)
337 client.EXPECT().NewServiceBindingServiceObject(ns, clusterUnstructured, bindingName).Return(serviceBindingRefWithNs, nil)
338 client.EXPECT().GetDeploymentAPIVersion().Return(deploymentGVK, nil)
339 return client
340 },
341 },
342 args: args{
343 bindingName: bindingName,
344 bindAsFiles: true,
345 namespace: ns,
346 unstructuredService: clusterUnstructured,
347 obj: odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs()),
348 },
349 want: getDevfileObjWithServiceBinding(bindingName, ns, true, ""),
350 wantErr: false,
351 },
352 {
353 name: "successfully added binding for a Service Binding with naming strategy",
354 fields: fields{
355 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
356 client := kclient.NewMockClientInterface(ctrl)
357 client.EXPECT().NewServiceBindingServiceObject("", clusterUnstructured, bindingName).Return(serviceBindingRef, nil)
358 client.EXPECT().GetDeploymentAPIVersion().Return(deploymentGVK, nil)
359 return client
360 },
361 },
362 args: args{
363 bindingName: bindingName,
364 bindAsFiles: true,
365 namingStrategy: "uppercase",
366 unstructuredService: clusterUnstructured,
367 obj: odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs()),
368 },
369 want: getDevfileObjWithServiceBinding(bindingName, "", true, "uppercase"),
370 wantErr: false,
371 },
372 {
373 name: "successfully added binding for a Service Binding with naming strategy and namespace",
374 fields: fields{
375 kubernetesClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
376 client := kclient.NewMockClientInterface(ctrl)
377 client.EXPECT().NewServiceBindingServiceObject(ns, clusterUnstructured, bindingName).Return(serviceBindingRefWithNs, nil)
378 client.EXPECT().GetDeploymentAPIVersion().Return(deploymentGVK, nil)
379 return client
380 },
381 },
382 args: args{
383 bindingName: bindingName,
384 bindAsFiles: true,
385 namespace: ns,
386 namingStrategy: "uppercase",
387 unstructuredService: clusterUnstructured,
388 obj: odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs()),
389 },
390 want: getDevfileObjWithServiceBinding(bindingName, ns, true, "uppercase"),
391 wantErr: false,
392 },
393 }
394 for _, tt := range tests {
395 t.Run(tt.name, func(t *testing.T) {
396 ctrl := gomock.NewController(t)
397 o := &BindingClient{
398 kubernetesClient: tt.fields.kubernetesClient(ctrl),
399 }
400 got, err := o.AddBindingToDevfile(
401 tt.args.obj.GetMetadataName(),
402 tt.args.bindingName,
403 tt.args.bindAsFiles,
404 tt.args.namespace,
405 tt.args.namingStrategy,
406 tt.args.unstructuredService,
407 tt.args.obj,
408 )
409 if (err != nil) != tt.wantErr {
410 t.Errorf("AddBindingToDevfile() error = %v, wantErr %v", err, tt.wantErr)
411 return
412 }
413 if diff := cmp.Diff(*tt.want, got,
414 cmp.AllowUnexported(devfileCtx.DevfileCtx{}),
415 cmpopts.IgnoreInterfaces(struct{ filesystem.Filesystem }{})); diff != "" {
416 t.Errorf("BindingClient.AddBindingToDevfile() mismatch (-want +got):\n%s", diff)
417 }
418 })
419 }
420 }
421
422 func getDevfileObjWithServiceBinding(bindingName string, ns string, bindAsFiles bool, namingStrategy string) *parser.DevfileObj {
423 obj := odoTestingUtil.GetTestDevfileObj(filesystem.NewFakeFs())
424 _ = obj.Data.AddComponents([]v1alpha2.Component{{
425 Name: bindingName,
426 ComponentUnion: v1alpha2.ComponentUnion{
427 Kubernetes: &v1alpha2.KubernetesComponent{
428 K8sLikeComponent: v1alpha2.K8sLikeComponent{
429 BaseComponent: v1alpha2.BaseComponent{},
430 K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{
431 Inlined: getServiceBindingInlinedContent(ns, bindAsFiles, namingStrategy),
432 },
433 },
434 },
435 },
436 }})
437 return &obj
438 }
439
440 func getServiceBindingInlinedContent(ns string, bindAsFiles bool, namingStrategy string) string {
441 h := fmt.Sprintf(`apiVersion: binding.operators.coreos.com/v1alpha1
442 kind: ServiceBinding
443 metadata:
444 creationTimestamp: null
445 name: my-nodejs-app-cluster-sample
446 spec:
447 application:
448 group: apps
449 kind: Deployment
450 name: my-nodejs-app-app
451 version: v1
452 bindAsFiles: %v
453 detectBindingResources: true`, bindAsFiles)
454
455 fNoNamespace := `
456 services:
457 - group: postgresql.k8s.enterprisedb.io
458 id: my-nodejs-app-cluster-sample
459 kind: Cluster
460 name: cluster-sample
461 resource: clusters
462 version: v1
463 status:
464 secret: ""
465 `
466
467 fNamespace := fmt.Sprintf(`
468 services:
469 - group: postgresql.k8s.enterprisedb.io
470 id: my-nodejs-app-cluster-sample
471 kind: Cluster
472 name: cluster-sample
473 namespace: %s
474 resource: clusters
475 version: v1
476 status:
477 secret: ""
478 `, ns)
479
480 if namingStrategy != "" {
481 if ns != "" {
482 return fmt.Sprintf(`%s
483 namingStrategy: %s%s`, h, namingStrategy, fNamespace)
484 }
485 return fmt.Sprintf(`%s
486 namingStrategy: %s%s`, h, namingStrategy, fNoNamespace)
487 }
488
489 if ns != "" {
490 return h + fNamespace
491 }
492
493 return h + fNoNamespace
494 }
495
View as plain text