1 package snapshot_test
2
3 import (
4 "encoding/json"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10
11 amb "github.com/datawire/ambassador/v2/pkg/api/getambassador.io/v3alpha1"
12 "github.com/datawire/ambassador/v2/pkg/kates"
13 "github.com/datawire/ambassador/v2/pkg/kates/k8s_resource_types"
14 snapshotTypes "github.com/datawire/ambassador/v2/pkg/snapshot/v1"
15 "github.com/datawire/dlib/dlog"
16 )
17
18 func getModuleSpec(t *testing.T, rawconfig string) amb.UntypedDict {
19 moduleConfig := amb.UntypedDict{}
20 if err := json.Unmarshal([]byte(rawconfig), &moduleConfig); err != nil {
21 t.Fatal(t)
22 }
23 return moduleConfig
24 }
25
26 func TestParseAnnotations(t *testing.T) {
27 mapping := `
28 ---
29 apiVersion: getambassador.io/v3alpha1
30 kind: Mapping
31 name: quote-backend
32 prefix: /backend/
33 service: quote:80
34 `
35
36 svc := &kates.Service{
37 TypeMeta: metav1.TypeMeta{
38 Kind: "Service",
39 },
40 ObjectMeta: metav1.ObjectMeta{
41 Name: "svc",
42 Namespace: "ambassador",
43 Annotations: map[string]string{
44 "getambassador.io/config": mapping,
45 },
46 },
47 }
48
49 svcWithEmptyAnnotation := &kates.Service{
50 TypeMeta: metav1.TypeMeta{
51 Kind: "Service",
52 },
53 ObjectMeta: metav1.ObjectMeta{
54 Name: "svc-empty",
55 Namespace: "ambassador",
56 Annotations: map[string]string{
57 "getambassador.io/config": "",
58 },
59 },
60 }
61
62 svcWithMissingAnnotation := &kates.Service{
63 TypeMeta: metav1.TypeMeta{
64 Kind: "Service",
65 },
66 ObjectMeta: metav1.ObjectMeta{
67 Name: "svc-missing",
68 Namespace: "ambassador",
69 Annotations: map[string]string{},
70 },
71 }
72
73 ingHost := `
74 ---
75 apiVersion: getambassador.io/v3alpha1
76 kind: Mapping
77 name: cool-mapping
78 prefix: /blah/
79 `
80
81 ingress := &k8s_resource_types.Ingress{
82 TypeMeta: metav1.TypeMeta{
83 Kind: "Ingress",
84 },
85 ObjectMeta: metav1.ObjectMeta{
86 Name: "ingress",
87 Namespace: "somens",
88 Annotations: map[string]string{
89 "getambassador.io/config": ingHost,
90 },
91 },
92 }
93
94 ambSvcAnnotations := `
95 ---
96 apiVersion: getambassador.io/v3alpha1
97 kind: Module
98 name: ambassador
99 config:
100 diagnostics:
101 enabled: true
102 ---
103 apiVersion: getambassador.io/v3alpha1
104 kind: KubernetesEndpointResolver
105 name: endpoint`
106
107 ambSvc := &kates.Service{
108 TypeMeta: metav1.TypeMeta{
109 Kind: "Service",
110 },
111 ObjectMeta: metav1.ObjectMeta{
112 Name: "ambassador",
113 Namespace: "ambassador",
114 Annotations: map[string]string{
115 "getambassador.io/config": ambSvcAnnotations,
116 },
117 },
118 }
119
120 unparsedAnnotation := `
121 ---
122 apiVersion: getambassador.io/v3alpha1
123 kind: Mapping
124 name: dont-parse
125 prefix: /blah/`
126
127 ignoredHost := &amb.Host{
128 TypeMeta: metav1.TypeMeta{
129 Kind: "Host",
130 },
131 ObjectMeta: metav1.ObjectMeta{
132 Name: "ambassador",
133 Namespace: "ambassador",
134 Annotations: map[string]string{
135 "getambassador.io/config": unparsedAnnotation,
136 },
137 },
138 }
139
140 ks := &snapshotTypes.KubernetesSnapshot{
141 Services: []*kates.Service{svc, ambSvc, svcWithEmptyAnnotation, svcWithMissingAnnotation},
142 Ingresses: []*snapshotTypes.Ingress{{Ingress: *ingress}},
143 Hosts: []*amb.Host{ignoredHost},
144 }
145
146 ctx := dlog.NewTestContext(t, false)
147
148 err := ks.PopulateAnnotations(ctx)
149 assert.NoError(t, err)
150 assert.Equal(t, len(ks.Services), 4)
151 assert.Equal(t, map[string]snapshotTypes.AnnotationList{
152 "Service/svc.ambassador": {
153 &amb.Mapping{
154 TypeMeta: metav1.TypeMeta{
155 Kind: "Mapping",
156 APIVersion: "getambassador.io/v3alpha1",
157 },
158 ObjectMeta: metav1.ObjectMeta{
159 Name: "quote-backend",
160 Namespace: "ambassador",
161 },
162 Spec: amb.MappingSpec{
163 Prefix: "/backend/",
164 Service: "quote:80",
165 },
166 },
167 },
168 "Ingress/ingress.somens": {
169 &kates.Unstructured{
170 Object: map[string]interface{}{
171 "apiVersion": "getambassador.io/v3alpha1",
172 "kind": "Mapping",
173 "metadata": map[string]interface{}{
174 "name": "cool-mapping",
175 "namespace": "somens",
176 },
177 "spec": map[string]interface{}{
178 "prefix": "/blah/",
179 },
180 "errors": "spec.service in body is required",
181 },
182 },
183 },
184 "Service/ambassador.ambassador": {
185 &amb.Module{
186 TypeMeta: metav1.TypeMeta{
187 Kind: "Module",
188 APIVersion: "getambassador.io/v3alpha1",
189 },
190 ObjectMeta: metav1.ObjectMeta{
191 Name: "ambassador",
192 Namespace: "ambassador",
193 },
194 Spec: amb.ModuleSpec{
195 Config: getModuleSpec(t, `{"diagnostics":{"enabled":true}}`),
196 },
197 },
198 &amb.KubernetesEndpointResolver{
199 TypeMeta: metav1.TypeMeta{
200 Kind: "KubernetesEndpointResolver",
201 APIVersion: "getambassador.io/v3alpha1",
202 },
203 ObjectMeta: metav1.ObjectMeta{
204 Name: "endpoint",
205 Namespace: "ambassador",
206 },
207 },
208 },
209 }, ks.Annotations)
210 }
211
212 func TestConvertAnnotation(t *testing.T) {
213 testcases := map[string]struct {
214 inputString string
215 inputParentNS string
216 inputParentLabels map[string]string
217
218 outputObj kates.Object
219 }{
220 "mapping": {
221 inputString: `
222 ---
223 apiVersion: getambassador.io/v3alpha1
224 kind: Mapping
225 name: cool-mapping
226 prefix: /blah/
227 service: quote:80`,
228 inputParentNS: "somens",
229 inputParentLabels: map[string]string{},
230 outputObj: &amb.Mapping{
231 TypeMeta: metav1.TypeMeta{
232 Kind: "Mapping",
233 APIVersion: "getambassador.io/v3alpha1",
234 },
235 ObjectMeta: metav1.ObjectMeta{
236 Name: "cool-mapping",
237 Namespace: "somens",
238 Labels: map[string]string{},
239 },
240 Spec: amb.MappingSpec{
241 Prefix: "/blah/",
242 Service: "quote:80",
243 },
244 },
245 },
246 "old-group-v0": {
247 inputString: `
248 ---
249 apiVersion: getambassador.io/v3alpha1
250 kind: Mapping
251 name: cool-mapping
252 prefix: /blah/
253 service: quote:80`,
254 inputParentNS: "somens",
255 inputParentLabels: map[string]string{},
256 outputObj: &amb.Mapping{
257 TypeMeta: metav1.TypeMeta{
258 Kind: "Mapping",
259 APIVersion: "getambassador.io/v3alpha1",
260 },
261 ObjectMeta: metav1.ObjectMeta{
262 Name: "cool-mapping",
263 Namespace: "somens",
264 Labels: map[string]string{},
265 },
266 Spec: amb.MappingSpec{
267 Prefix: "/blah/",
268 Service: "quote:80",
269 },
270 },
271 },
272 "old-group-v1": {
273 inputString: `
274 ---
275 apiVersion: getambassador.io/v3alpha1
276 kind: Mapping
277 name: cool-mapping
278 prefix: /blah/
279 service: quote:80`,
280 inputParentNS: "somens",
281 inputParentLabels: map[string]string{},
282 outputObj: &amb.Mapping{
283 TypeMeta: metav1.TypeMeta{
284 Kind: "Mapping",
285 APIVersion: "getambassador.io/v3alpha1",
286 },
287 ObjectMeta: metav1.ObjectMeta{
288 Name: "cool-mapping",
289 Namespace: "somens",
290 Labels: map[string]string{},
291 },
292 Spec: amb.MappingSpec{
293 Prefix: "/blah/",
294 Service: "quote:80",
295 },
296 },
297 },
298 "label-override": {
299 inputString: `
300 ---
301 apiVersion: getambassador.io/v3alpha1
302 kind: Mapping
303 name: cool-mapping
304 metadata_labels:
305 bleep: blorp
306 prefix: /blah/
307 service: quote:80`,
308 inputParentNS: "somens",
309 inputParentLabels: map[string]string{"should": "override"},
310 outputObj: &amb.Mapping{
311 TypeMeta: metav1.TypeMeta{
312 Kind: "Mapping",
313 APIVersion: "getambassador.io/v3alpha1",
314 },
315 ObjectMeta: metav1.ObjectMeta{
316 Name: "cool-mapping",
317 Namespace: "somens",
318 Labels: map[string]string{
319 "bleep": "blorp",
320 },
321 },
322 Spec: amb.MappingSpec{
323 Prefix: "/blah/",
324 Service: "quote:80",
325 },
326 },
327 },
328 "parent-labels": {
329 inputString: `
330 ---
331 apiVersion: getambassador.io/v3alpha1
332 kind: Mapping
333 name: cool-mapping
334 prefix: /blah/
335 service: quote:80`,
336 inputParentNS: "somens",
337 inputParentLabels: map[string]string{
338 "use": "theselabels",
339 },
340 outputObj: &amb.Mapping{
341 TypeMeta: metav1.TypeMeta{
342 Kind: "Mapping",
343 APIVersion: "getambassador.io/v3alpha1",
344 },
345 ObjectMeta: metav1.ObjectMeta{
346 Name: "cool-mapping",
347 Namespace: "somens",
348 Labels: map[string]string{
349 "use": "theselabels",
350 },
351 },
352 Spec: amb.MappingSpec{
353 Prefix: "/blah/",
354 Service: "quote:80",
355 },
356 },
357 },
358 "module": {
359 inputString: `
360 ---
361 apiVersion: getambassador.io/v3alpha1
362 kind: Module
363 name: ambassador
364 config:
365 diagnostics:
366 enabled: true`,
367 inputParentNS: "somens",
368 inputParentLabels: map[string]string{},
369 outputObj: &amb.Module{
370 TypeMeta: metav1.TypeMeta{
371 Kind: "Module",
372 APIVersion: "getambassador.io/v3alpha1",
373 },
374 ObjectMeta: metav1.ObjectMeta{
375 Name: "ambassador",
376 Namespace: "somens",
377 Labels: map[string]string{},
378 },
379 Spec: amb.ModuleSpec{
380 Config: getModuleSpec(t, `{"diagnostics":{"enabled":true}}`),
381 },
382 },
383 },
384 }
385
386 for tcName, tc := range testcases {
387 tc := tc
388 t.Run(tcName, func(t *testing.T) {
389 parentObj := &kates.Unstructured{
390 Object: map[string]interface{}{
391 "apiVersion": "v1",
392 "kind": "Service",
393 "metadata": map[string]interface{}{
394 "name": "parentname",
395 "namespace": tc.inputParentNS,
396 "labels": func() map[string]interface{} {
397 ret := make(map[string]interface{}, len(tc.inputParentLabels))
398 for k, v := range tc.inputParentLabels {
399 ret[k] = v
400 }
401 return ret
402 }(),
403 "annotations": map[string]interface{}{
404 "getambassador.io/config": tc.inputString,
405 },
406 },
407 },
408 }
409
410 ctx := dlog.NewTestContext(t, false)
411
412 objs, err := snapshotTypes.ParseAnnotationResources(parentObj)
413 require.NoError(t, err)
414 require.Len(t, objs, 1)
415 obj, err := snapshotTypes.ValidateAndConvertObject(ctx, objs[0])
416 require.NoError(t, err)
417 assert.Equal(t, tc.outputObj, obj)
418 })
419 }
420 }
421
View as plain text