1
2
3
4 package target_test
5
6 import (
7 "encoding/base64"
8 "fmt"
9 "reflect"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 "sigs.k8s.io/kustomize/api/ifc"
16 . "sigs.k8s.io/kustomize/api/internal/target"
17 "sigs.k8s.io/kustomize/api/internal/utils"
18 "sigs.k8s.io/kustomize/api/pkg/loader"
19 "sigs.k8s.io/kustomize/api/provider"
20 "sigs.k8s.io/kustomize/api/resmap"
21 "sigs.k8s.io/kustomize/api/resource"
22 kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
23 "sigs.k8s.io/kustomize/api/types"
24 )
25
26
27
28
29 func TestLoadKustFile(t *testing.T) {
30 for name, test := range map[string]struct {
31 fileNames []string
32 kustFileName, errMsg string
33 }{
34 "missing": {
35 fileNames: []string{"kustomization"},
36 errMsg: `unable to find one of 'kustomization.yaml', 'kustomization.yml' or 'Kustomization' in directory '/'`,
37 },
38 "multiple": {
39 fileNames: []string{"kustomization.yaml", "Kustomization"},
40 errMsg: `Found multiple kustomization files under: /
41 `,
42 },
43 "valid": {
44 fileNames: []string{"kustomization.yml", "kust"},
45 kustFileName: "kustomization.yml",
46 },
47 } {
48 t.Run(name, func(t *testing.T) {
49 th := kusttest_test.MakeHarness(t)
50 fSys := th.GetFSys()
51 for _, file := range test.fileNames {
52 require.NoError(t, fSys.WriteFile(file, []byte(fmt.Sprintf("namePrefix: test-%s", file))))
53 }
54
55 content, fileName, err := LoadKustFile(loader.NewFileLoaderAtCwd(fSys))
56 if test.kustFileName != "" {
57 require.NoError(t, err)
58 require.Equal(t, fmt.Sprintf("namePrefix: test-%s", test.kustFileName), string(content))
59 require.Equal(t, test.kustFileName, fileName)
60 } else {
61 require.EqualError(t, err, test.errMsg)
62 }
63 })
64 }
65 }
66
67 func TestLoad(t *testing.T) {
68 th := kusttest_test.MakeHarness(t)
69 expectedTypeMeta := types.TypeMeta{
70 APIVersion: "kustomize.config.k8s.io/v1beta1",
71 Kind: "Kustomization",
72 }
73
74 testCases := map[string]struct {
75 errContains string
76 content string
77 k types.Kustomization
78 }{
79 "empty": {
80
81 k: types.Kustomization{
82 TypeMeta: expectedTypeMeta,
83 },
84 errContains: "kustomization.yaml is empty",
85 },
86 "nonsenseLatin": {
87 errContains: "found a tab character that violates indentation",
88 content: `
89 Lorem ipsum dolor sit amet, consectetur
90 adipiscing elit, sed do eiusmod tempor
91 incididunt ut labore et dolore magna aliqua.
92 Ut enim ad minim veniam, quis nostrud
93 exercitation ullamco laboris nisi ut
94 aliquip ex ea commodo consequat.
95 `,
96 },
97 "simple": {
98 content: `
99 commonLabels:
100 app: nginx
101 `,
102 k: types.Kustomization{
103 TypeMeta: expectedTypeMeta,
104 CommonLabels: map[string]string{"app": "nginx"},
105 },
106 },
107 "commented": {
108 content: `
109 # Licensed to the Blah Blah Software Foundation
110 # ...
111 # yada yada yada.
112
113 commonLabels:
114 app: nginx
115 `,
116 k: types.Kustomization{
117 TypeMeta: expectedTypeMeta,
118 CommonLabels: map[string]string{"app": "nginx"},
119 },
120 },
121 }
122
123 kt := makeKustTargetWithRf(
124 t, th.GetFSys(), "/", provider.NewDefaultDepProvider())
125 for tn, tc := range testCases {
126 t.Run(tn, func(t *testing.T) {
127 th.WriteK("/", tc.content)
128 err := kt.Load()
129 if tc.errContains != "" {
130 require.NotNilf(t, err, "expected error containing: `%s`", tc.errContains)
131 require.Contains(t, err.Error(), tc.errContains)
132 } else {
133 require.Nilf(t, err, "got error: %v", err)
134 k := kt.Kustomization()
135 require.Condition(t, func() bool {
136 return reflect.DeepEqual(tc.k, k)
137 }, "expected %v, got %v", tc.k, k)
138 }
139 })
140 }
141 }
142
143 func TestMakeCustomizedResMap(t *testing.T) {
144 th := kusttest_test.MakeHarness(t)
145 th.WriteK("/whatever", `namePrefix: foo-
146 nameSuffix: -bar
147 namespace: ns1
148 commonLabels:
149 app: nginx
150 commonAnnotations:
151 note: This is a test annotation
152 resources:
153 - deployment.yaml
154 - namespace.yaml
155 generatorOptions:
156 disableNameSuffixHash: false
157 configMapGenerator:
158 - name: literalConfigMap
159 literals:
160 - DB_USERNAME=admin
161 - DB_PASSWORD=somepw
162 secretGenerator:
163 - name: secret
164 literals:
165 - DB_USERNAME=admin
166 - DB_PASSWORD=somepw
167 type: Opaque
168 patchesJson6902:
169 - target:
170 group: apps
171 version: v1
172 kind: Deployment
173 name: dply1
174 path: jsonpatch.json
175 `)
176 th.WriteF("/whatever/deployment.yaml", `
177 apiVersion: apps/v1
178 metadata:
179 name: dply1
180 kind: Deployment
181 `)
182 th.WriteF("/whatever/namespace.yaml", `
183 apiVersion: v1
184 kind: Namespace
185 metadata:
186 name: ns1
187 `)
188 th.WriteF("/whatever/jsonpatch.json", `[
189 {"op": "add", "path": "/spec/replica", "value": "3"}
190 ]`)
191
192 pvd := provider.NewDefaultDepProvider()
193 resFactory := pvd.GetResourceFactory()
194
195 resources := []*resource.Resource{
196 resFactory.FromMapWithName("dply1", map[string]interface{}{
197 "apiVersion": "apps/v1",
198 "kind": "Deployment",
199 "metadata": map[string]interface{}{
200 "name": "foo-dply1-bar",
201 "namespace": "ns1",
202 "labels": map[string]interface{}{
203 "app": "nginx",
204 },
205 "annotations": map[string]interface{}{
206 "note": "This is a test annotation",
207 },
208 },
209 "spec": map[string]interface{}{
210 "replica": "3",
211 "selector": map[string]interface{}{
212 "matchLabels": map[string]interface{}{
213 "app": "nginx",
214 },
215 },
216 "template": map[string]interface{}{
217 "metadata": map[string]interface{}{
218 "annotations": map[string]interface{}{
219 "note": "This is a test annotation",
220 },
221 "labels": map[string]interface{}{
222 "app": "nginx",
223 },
224 },
225 },
226 },
227 }),
228 resFactory.FromMapWithName("ns1", map[string]interface{}{
229 "apiVersion": "v1",
230 "kind": "Namespace",
231 "metadata": map[string]interface{}{
232 "name": "ns1",
233 "labels": map[string]interface{}{
234 "app": "nginx",
235 },
236 "annotations": map[string]interface{}{
237 "note": "This is a test annotation",
238 },
239 },
240 }),
241 resFactory.FromMapWithName("literalConfigMap",
242 map[string]interface{}{
243 "apiVersion": "v1",
244 "kind": "ConfigMap",
245 "metadata": map[string]interface{}{
246 "name": "foo-literalConfigMap-bar-g5f6t456f5",
247 "namespace": "ns1",
248 "labels": map[string]interface{}{
249 "app": "nginx",
250 },
251 "annotations": map[string]interface{}{
252 "note": "This is a test annotation",
253 },
254 },
255 "data": map[string]interface{}{
256 "DB_USERNAME": "admin",
257 "DB_PASSWORD": "somepw",
258 },
259 }),
260 resFactory.FromMapWithName("secret",
261 map[string]interface{}{
262 "apiVersion": "v1",
263 "kind": "Secret",
264 "metadata": map[string]interface{}{
265 "name": "foo-secret-bar-82c2g5f8f6",
266 "namespace": "ns1",
267 "labels": map[string]interface{}{
268 "app": "nginx",
269 },
270 "annotations": map[string]interface{}{
271 "note": "This is a test annotation",
272 },
273 },
274 "type": ifc.SecretTypeOpaque,
275 "data": map[string]interface{}{
276 "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
277 "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
278 },
279 }),
280 }
281
282 expected := resmap.New()
283 for _, r := range resources {
284 if err := expected.Append(r); err != nil {
285 t.Fatalf("unexpected error %v", err)
286 }
287 }
288 expected.RemoveBuildAnnotations()
289 expYaml, err := expected.AsYaml()
290 require.NoError(t, err)
291
292 kt := makeKustTargetWithRf(t, th.GetFSys(), "/whatever", pvd)
293 require.NoError(t, kt.Load())
294 actual, err := kt.MakeCustomizedResMap()
295 require.NoError(t, err)
296 actual.RemoveBuildAnnotations()
297 actYaml, err := actual.AsYaml()
298 require.NoError(t, err)
299 assert.Equal(t, string(expYaml), string(actYaml))
300 }
301
302 func TestConfigurationsOverrideDefault(t *testing.T) {
303 th := kusttest_test.MakeHarness(t)
304 th.WriteK("/merge-config", `namePrefix: foo-
305 nameSuffix: -bar
306 namespace: ns1
307 resources:
308 - deployment.yaml
309 - config.yaml
310 - secret.yaml
311 configurations:
312 - name-prefix-rules.yaml
313 - name-suffix-rules.yaml
314 `)
315 th.WriteF("/merge-config/name-prefix-rules.yaml", `
316 namePrefix:
317 - path: metadata/name
318 group: apps
319 version: v1
320 kind: Deployment
321 - path: metadata/name
322 version: v1
323 kind: Secret
324 `)
325 th.WriteF("/merge-config/name-suffix-rules.yaml", `
326 nameSuffix:
327 - path: metadata/name
328 version: v1
329 kind: ConfigMap
330 - path: metadata/name
331 group: apps
332 version: v1
333 kind: Deployment
334 `)
335 th.WriteF("/merge-config/deployment.yaml", `
336 apiVersion: apps/v1
337 metadata:
338 name: deployment1
339 kind: Deployment
340 `)
341 th.WriteF("/merge-config/config.yaml", `
342 apiVersion: v1
343 kind: ConfigMap
344 metadata:
345 name: config
346 `)
347 th.WriteF("/merge-config/secret.yaml", `
348 apiVersion: v1
349 kind: Secret
350 metadata:
351 name: secret
352 `)
353
354 pvd := provider.NewDefaultDepProvider()
355 resFactory := pvd.GetResourceFactory()
356
357 resources := []*resource.Resource{
358 resFactory.FromMapWithName("deployment1", map[string]interface{}{
359 "apiVersion": "apps/v1",
360 "kind": "Deployment",
361 "metadata": map[string]interface{}{
362 "name": "foo-deployment1-bar",
363 "namespace": "ns1",
364 },
365 }), resFactory.FromMapWithName("config", map[string]interface{}{
366 "apiVersion": "v1",
367 "kind": "ConfigMap",
368 "metadata": map[string]interface{}{
369 "name": "config-bar",
370 "namespace": "ns1",
371 },
372 }), resFactory.FromMapWithName("secret", map[string]interface{}{
373 "apiVersion": "v1",
374 "kind": "Secret",
375 "metadata": map[string]interface{}{
376 "name": "foo-secret",
377 "namespace": "ns1",
378 },
379 }),
380 }
381
382 expected := resmap.New()
383 for _, r := range resources {
384 err := expected.Append(r)
385 require.NoError(t, err)
386 }
387 expected.RemoveBuildAnnotations()
388 expYaml, err := expected.AsYaml()
389 require.NoError(t, err)
390
391 kt := makeKustTargetWithRf(t, th.GetFSys(), "/merge-config", pvd)
392 require.NoError(t, kt.Load())
393 actual, err := kt.MakeCustomizedResMap()
394 require.NoError(t, err)
395 actual.RemoveBuildAnnotations()
396 actYaml, err := actual.AsYaml()
397 require.NoError(t, err)
398 require.Equal(t, string(expYaml), string(actYaml))
399 }
400
401 func TestDuplicateExternalGeneratorsForbidden(t *testing.T) {
402 th := kusttest_test.MakeHarness(t)
403 th.WriteK("/generator", `generators:
404 - |-
405 apiVersion: generators.example/v1
406 kind: ManifestGenerator
407 metadata:
408 name: ManifestGenerator
409 annotations:
410 config.kubernetes.io/function: |
411 container:
412 image: ManifestGenerator:latest
413 spec:
414 image: 'someimage:12345'
415 configPath: config.json
416 - |-
417 apiVersion: generators.example/v1
418 kind: ManifestGenerator
419 metadata:
420 name: ManifestGenerator
421 annotations:
422 config.kubernetes.io/function: |
423 container:
424 image: ManifestGenerator:latest
425 spec:
426 image: 'someimage:12345'
427 configPath: another_config.json
428 `)
429 _, err := makeAndLoadKustTarget(t, th.GetFSys(), "/generator").AccumulateTarget()
430 require.Error(t, err)
431 assert.Contains(t, err.Error(), "may not add resource with an already registered id: ManifestGenerator.v1.generators.example/ManifestGenerator")
432 }
433
434 func TestDuplicateExternalTransformersForbidden(t *testing.T) {
435 th := kusttest_test.MakeHarness(t)
436 th.WriteK("/transformer", `transformers:
437 - |-
438 apiVersion: transformers.example.co/v1
439 kind: ValueAnnotator
440 metadata:
441 name: notImportantHere
442 annotations:
443 config.kubernetes.io/function: |
444 container:
445 image: example.docker.com/my-functions/valueannotator:1.0.0
446 value: 'pass'
447 - |-
448 apiVersion: transformers.example.co/v1
449 kind: ValueAnnotator
450 metadata:
451 name: notImportantHere
452 annotations:
453 config.kubernetes.io/function: |
454 container:
455 image: example.docker.com/my-functions/valueannotator:1.0.0
456 value: 'fail'
457 `)
458 _, err := makeAndLoadKustTarget(t, th.GetFSys(), "/transformer").AccumulateTarget()
459 require.Error(t, err)
460 assert.Contains(t, err.Error(), "may not add resource with an already registered id: ValueAnnotator.v1.transformers.example.co/notImportantHere")
461 }
462
463 func TestErrorMessageForMalformedYAML(t *testing.T) {
464
465
466
467 testcases := map[string]struct {
468 loaderNewReturnsError error
469 shouldShowLoadError bool
470 }{
471 "shouldShowLoadError": {
472 loaderNewReturnsError: utils.NewErrTimeOut(time.Second, "git init"),
473 shouldShowLoadError: true,
474 },
475 "shouldNotShowLoadError": {
476 loaderNewReturnsError: NewErrMissingKustomization("/should-fail/resources.yaml"),
477 shouldShowLoadError: false,
478 },
479 }
480
481 th := kusttest_test.MakeHarness(t)
482 th.WriteF("/should-fail/kustomization.yaml", `resources:
483 - resources.yaml
484 `)
485 th.WriteF("/should-fail/resources.yaml", `<!DOCTYPE html>
486 <html class="html-devise-layout ui-light-gray" lang="en">
487 <head prefix="og: http://ogp.me/ns#">
488 <meta charset="utf-8">
489 `)
490
491 for name, tc := range testcases {
492 t.Run(name, func(subT *testing.T) {
493 ldrWrapper := func(baseLoader ifc.Loader) ifc.Loader {
494 return loaderNewThrowsError{
495 baseLoader: baseLoader,
496 newReturnsError: tc.loaderNewReturnsError,
497 }
498 }
499 _, err := makeAndLoadKustTargetWithLoaderOverride(t, th.GetFSys(), "/should-fail", ldrWrapper).AccumulateTarget()
500 require.Error(t, err)
501 errString := err.Error()
502 assert.Contains(t, errString, "accumulating resources from 'resources.yaml'")
503 assert.Contains(t, errString, "MalformedYAMLError: yaml: line 3: mapping values are not allowed in this context")
504 if tc.shouldShowLoadError {
505 assert.Regexp(t, `hit \w+ timeout running '`, errString)
506 } else {
507 assert.NotRegexp(t, `hit \w+ timeout running '`, errString)
508 }
509 })
510 }
511 }
512
513
514
515 type loaderNewThrowsError struct {
516 baseLoader ifc.Loader
517 newReturnsError error
518 }
519
520 func (l loaderNewThrowsError) Repo() string {
521 return l.baseLoader.Repo()
522 }
523
524 func (l loaderNewThrowsError) Root() string {
525 return l.baseLoader.Root()
526 }
527
528 func (l loaderNewThrowsError) New(_ string) (ifc.Loader, error) {
529 return nil, l.newReturnsError
530 }
531
532 func (l loaderNewThrowsError) Load(location string) ([]byte, error) {
533 return l.baseLoader.Load(location)
534 }
535
536 func (l loaderNewThrowsError) Cleanup() error {
537 return l.baseLoader.Cleanup()
538 }
539
View as plain text