1
2
3
4 package localizer_test
5
6 import (
7 "fmt"
8 "os"
9 "path/filepath"
10 "testing"
11
12 "github.com/stretchr/testify/require"
13 . "sigs.k8s.io/kustomize/api/internal/localizer"
14 "sigs.k8s.io/kustomize/api/krusty/localizer"
15 . "sigs.k8s.io/kustomize/api/testutils/localizertest"
16 "sigs.k8s.io/kustomize/kyaml/filesys"
17 )
18
19 const (
20 customSchema = `{
21 "definitions": {
22 "v1alpha1.MyCRD": {
23 "properties": {
24 "apiVersion": {
25 "type": "string"
26 },
27 "kind": {
28 "type": "string"
29 },
30 "metadata": {
31 "type": "object"
32 },
33 "spec": {
34 "properties": {
35 "template": {
36 "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
37 }
38 },
39 "type": "object"
40 },
41 "status": {
42 "properties": {
43 "success": {
44 "type": "boolean"
45 }
46 },
47 "type": "object"
48 }
49 },
50 "type": "object",
51 "x-kubernetes-group-version-kind": [
52 {
53 "group": "example.com",
54 "kind": "MyCRD",
55 "version": "v1alpha1"
56 },
57 {
58 "group": "",
59 "kind": "MyCRD",
60 "version": "v1alpha1"
61 }
62 ]
63 },
64 "io.k8s.api.core.v1.PodTemplateSpec": {
65 "properties": {
66 "metadata": {
67 "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
68 },
69 "spec": {
70 "$ref": "#/definitions/io.k8s.api.core.v1.PodSpec"
71 }
72 },
73 "type": "object"
74 },
75 "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": {
76 "properties": {
77 "name": {
78 "type": "string"
79 }
80 },
81 "type": "object"
82 },
83 "io.k8s.api.core.v1.PodSpec": {
84 "properties": {
85 "containers": {
86 "items": {
87 "$ref": "#/definitions/io.k8s.api.core.v1.Container"
88 },
89 "type": "array",
90 "x-kubernetes-patch-merge-key": "name",
91 "x-kubernetes-patch-strategy": "merge"
92 }
93 },
94 "type": "object"
95 },
96 "io.k8s.api.core.v1.Container": {
97 "properties": {
98 "command": {
99 "items": {
100 "type": "string"
101 },
102 "type": "array"
103 },
104 "image": {
105 "type": "string"
106 },
107 "name": {
108 "type": "string"
109 },
110 "ports": {
111 "items": {
112 "$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort"
113 },
114 "type": "array",
115 "x-kubernetes-list-map-keys": [
116 "containerPort",
117 "protocol"
118 ],
119 "x-kubernetes-list-type": "map",
120 "x-kubernetes-patch-merge-key": "containerPort",
121 "x-kubernetes-patch-strategy": "merge"
122 }
123 },
124 "type": "object"
125 },
126 "io.k8s.api.core.v1.ContainerPort": {
127 "properties": {
128 "containerPort": {
129 "format": "int32",
130 "type": "integer"
131 },
132 "name": {
133 "type": "string"
134 },
135 "protocol": {
136 "type": "string"
137 }
138 },
139 "type": "object"
140 }
141 }
142 }
143 `
144
145 simpleURL = "https://github.com/kubernetes-sigs/kustomize//api/krusty/testdata/localize/simple"
146
147 simpleKustomization = `apiVersion: kustomize.config.k8s.io/v1beta1
148 kind: Kustomization
149 namePrefix: localize-
150 resources:
151 - deployment.yaml
152 - service.yaml
153 `
154
155 simpleDeployment = `apiVersion: apps/v1
156 kind: Deployment
157 metadata:
158 name: test-deployment-simple
159 labels:
160 app: deployment-simple
161 spec:
162 selector:
163 matchLabels:
164 app: simple
165 template:
166 metadata:
167 labels:
168 app: simple
169 spec:
170 containers:
171 - name: nginx
172 image: nginx:1.16
173 ports:
174 - containerPort: 8080
175 `
176 simpleService = `apiVersion: v1
177 kind: Service
178 metadata:
179 name: test-service-simple
180 spec:
181 selector:
182 app: deployment-simple
183 ports:
184 - protocol: TCP
185 port: 80
186 targetPort: 8080
187 `
188
189 remoteHPA = `apiVersion: autoscaling/v2
190 kind: HorizontalPodAutoscaler
191 metadata:
192 name: hpa-deployment
193 spec:
194 scaleTargetRef:
195 apiVersion: apps/v1
196 kind: Deployment
197 name: localize-test-deployment-simple
198 minReplicas: 1
199 maxReplicas: 10`
200
201 urlQuery = "?submodules=0&ref=kustomize/v4.5.7&timeout=300"
202
203 valuesFile = `minecraftServer:
204 difficulty: peaceful
205 `
206 )
207
208 func link(t *testing.T, testDir filesys.ConfirmedDir, links map[string]string) {
209 t.Helper()
210
211 for newLink, file := range links {
212 require.NoError(t, os.Symlink(testDir.Join(file), testDir.Join(newLink)))
213 }
214 }
215
216 func simplePathAndFiles(t *testing.T) (locPath string, files map[string]string) {
217 t.Helper()
218
219 locPath = filepath.Join(LocalizeDir, "github.com",
220 "kubernetes-sigs", "kustomize", "kustomize", "v4.5.7",
221 "api", "krusty", "testdata", "localize", "simple")
222 files = map[string]string{
223 "kustomization.yaml": simpleKustomization,
224 "deployment.yaml": simpleDeployment,
225 "service.yaml": simpleService,
226 }
227 return
228 }
229
230 func remotePathAndFiles(t *testing.T) (locPath string, files map[string]string) {
231 t.Helper()
232
233 locPath = filepath.Join(LocalizeDir, "github.com",
234 "kubernetes-sigs", "kustomize", "master",
235 "api", "krusty", "testdata", "localize", "remote")
236 simplePath, simpleFiles := simplePathAndFiles(t)
237 files = map[string]string{
238 "kustomization.yaml": fmt.Sprintf(`apiVersion: kustomize.config.k8s.io/v1beta1
239 commonLabels:
240 purpose: remoteReference
241 kind: Kustomization
242 resources:
243 - %s
244 - hpa.yaml
245 `, simplePath),
246 "hpa.yaml": remoteHPA,
247 }
248 for path, content := range simpleFiles {
249 files[filepath.Join(simplePath, path)] = content
250 }
251 return
252 }
253
254 func TestWorkingDir(t *testing.T) {
255 files := map[string]string{
256 filepath.Join("target", "kustomization.yaml"): fmt.Sprintf(`resources:
257 - %s
258 `, filepath.Join("..", "base")),
259 filepath.Join("base", "kustomization.yaml"): `resources:
260 - deployment.yaml
261 `,
262 filepath.Join("base", "deployment.yaml"): simpleDeployment,
263 }
264 fsExpected, fsActual, wd := PrepareFs(t, []string{"target", "base"}, files)
265 SetWorkingDir(t, wd.String())
266
267 dst, err := localizer.Run(fsActual, "target", ".", "")
268 require.NoError(t, err)
269 require.Equal(t, wd.Join("localized-target"), dst)
270
271 SetupDir(t, fsExpected, dst, files)
272 CheckFs(t, wd.String(), fsExpected, fsActual)
273 }
274
275 func TestLoaderSymlinks(t *testing.T) {
276
277
278
279
280
281
282
283
284
285
286 fsExpected, fsActual, testDir := PrepareFs(t, []string{"target",
287 filepath.Join("target", "base"),
288 filepath.Join("target", "nested")}, map[string]string{
289 filepath.Join("target", "base", "kustomization.yaml"): `namePrefix: test-
290 `,
291 filepath.Join("target", "nested", "kustomization"): fmt.Sprintf(`resources:
292 - %s
293 - %s
294 `, filepath.Join("..", "file-link"), filepath.Join("..", "base-link")),
295 filepath.Join("target", "nested", "file"): simpleDeployment,
296 })
297 link(t, testDir, map[string]string{
298 "target-link": "target",
299 "base-link": filepath.Join("target", "base"),
300 "file-link": filepath.Join("target", "nested", "file"),
301 filepath.Join("target", "kustomization.yaml"): filepath.Join("target", "nested", "kustomization"),
302 })
303 SetWorkingDir(t, testDir.String())
304
305 dst, err := localizer.Run(fsActual, "target-link", "target", "")
306 require.NoError(t, err)
307 require.Equal(t, testDir.Join("localized-target"), dst)
308
309 SetupDir(t, fsExpected, dst, map[string]string{
310 "kustomization.yaml": fmt.Sprintf(`resources:
311 - %s
312 - base
313 `, filepath.Join("nested", "file")),
314 filepath.Join("base", "kustomization.yaml"): `namePrefix: test-
315 `,
316 filepath.Join("nested", "file"): simpleDeployment,
317 })
318 CheckFs(t, dst, fsExpected, fsActual)
319 }
320
321 func TestRemoteTargetDefaultDst(t *testing.T) {
322 fsExpected, fsActual, testDir := PrepareFs(t, nil, nil)
323 SetWorkingDir(t, testDir.String())
324
325 const target = simpleURL + urlQuery
326 dst, err := localizer.Run(fsActual, target, "", "")
327 require.NoError(t, err)
328 require.Equal(t, testDir.Join("localized-simple-kustomize-v4.5.7"), dst)
329
330 _, files := simplePathAndFiles(t)
331 SetupDir(t, fsExpected,
332 filepath.Join(dst, "api", "krusty", "testdata", "localize", "simple"),
333 files)
334 CheckFs(t, testDir.String(), fsExpected, fsActual)
335 }
336
337 func TestBadArgs(t *testing.T) {
338 badDst := filepath.Join("non-existing", "dst")
339
340 for name, test := range map[string]struct {
341 target string
342 scope string
343 dst string
344 err string
345 }{
346 "target_no_ref": {
347 target: simpleURL,
348 err: `localize remote root "https://github.com/kubernetes-sigs/kustomize//api/krusty/testdata/localize/simple" missing ref query string parameter`,
349 },
350 "non-empty_scope": {
351 target: simpleURL + urlQuery,
352 scope: ".",
353 err: fmt.Sprintf(`invalid localize scope ".": scope "." specified for remote localize target "%s"`, simpleURL+urlQuery),
354 },
355 "dst_in_non-existing_dir": {
356 target: ".",
357 dst: badDst,
358 err: fmt.Sprintf(`invalid localize destination "%s": unable to create localize destination directory: mkdir %s: no such file or directory`, badDst, badDst),
359 },
360 } {
361 t.Run(name, func(t *testing.T) {
362 kust := map[string]string{
363 "kustomization.yaml": "namePrefix: test-",
364 }
365 fsExpected, fsActual, testDir := PrepareFs(t, nil, kust)
366 SetWorkingDir(t, testDir.String())
367
368 _, err := localizer.Run(fsActual, test.target, test.scope, test.dst)
369 require.EqualError(t, err, test.err)
370
371 SetupDir(t, fsExpected, testDir.String(), kust)
372 CheckFs(t, testDir.String(), fsExpected, fsActual)
373 })
374 }
375 }
376
377 func TestRemoteFile(t *testing.T) {
378 const kustf = `apiVersion: kustomize.config.k8s.io/v1beta1
379 kind: Kustomization
380 openapi:
381 path: %s
382 `
383 fsExpected, fsActual, testDir := PrepareFs(t, nil, map[string]string{
384 "kustomization.yaml": fmt.Sprintf(kustf, `https://raw.githubusercontent.com/kubernetes-sigs/kustomize/kustomize/v4.5.7/api/krusty/testdata/customschema.json`),
385 })
386
387 newDir := testDir.Join("dst")
388 dst, err := localizer.Run(fsActual, testDir.String(), "", newDir)
389 require.NoError(t, err)
390 require.Equal(t, newDir, dst)
391
392 localizedPath := filepath.Join(LocalizeDir, "raw.githubusercontent.com",
393 "kubernetes-sigs", "kustomize", "kustomize", "v4.5.7", "api", "krusty",
394 "testdata", "customschema.json")
395 SetupDir(t, fsExpected, dst, map[string]string{
396 "kustomization.yaml": fmt.Sprintf(kustf, localizedPath),
397 localizedPath: customSchema,
398 })
399 CheckFs(t, testDir.String(), fsExpected, fsActual)
400 }
401
402 func TestRemoteRoot(t *testing.T) {
403 fsExpected, fsActual, testDir := PrepareFs(t, nil, map[string]string{
404 "kustomization.yaml": fmt.Sprintf(`resources:
405 - %s
406 `, simpleURL+urlQuery),
407 })
408
409 newDir := testDir.Join("dst")
410 dst, err := localizer.Run(fsActual, testDir.String(), "", newDir)
411 require.NoError(t, err)
412 require.Equal(t, newDir, dst)
413
414 localizedPath, files := simplePathAndFiles(t)
415 SetupDir(t, fsExpected, dst, map[string]string{
416 "kustomization.yaml": fmt.Sprintf(`resources:
417 - %s
418 `, localizedPath),
419 })
420 SetupDir(t, fsExpected, filepath.Join(dst, localizedPath), files)
421 CheckFs(t, testDir.String(), fsExpected, fsActual)
422 }
423
424 func TestNestedRemoteRoots(t *testing.T) {
425 fsExpected, fsActual, testDir := PrepareFs(t, nil, map[string]string{
426
427
428 "kustomization.yaml": `resources:
429 - https://github.com/kubernetes-sigs/kustomize//api/krusty/testdata/localize/remote?submodules=0&ref=master&timeout=300
430 `,
431 })
432
433 newDir := testDir.Join("dst")
434 dst, err := localizer.Run(fsActual, testDir.String(), "", newDir)
435 require.NoError(t, err)
436 require.Equal(t, newDir, dst)
437
438 localizedPath, files := remotePathAndFiles(t)
439 SetupDir(t, fsExpected, dst, map[string]string{
440 "kustomization.yaml": fmt.Sprintf(`resources:
441 - %s
442 `, localizedPath),
443 })
444 SetupDir(t, fsExpected, filepath.Join(dst, localizedPath), files)
445 CheckFs(t, testDir.String(), fsExpected, fsActual)
446 }
447
448 func TestResourcesRepoNotFile(t *testing.T) {
449 const repo = "https://github.com/kubernetes-sigs/kustomize" + urlQuery
450 kustomization := map[string]string{
451 "kustomization.yaml": fmt.Sprintf(`resources:
452 - %s
453 `, repo),
454 }
455 fsExpected, fsActual, testDir := PrepareFs(t, nil, kustomization)
456
457 _, err := localizer.Run(fsActual, testDir.String(), "", testDir.Join("dst"))
458
459 fileErr := fmt.Sprintf(`invalid resource at file "%s"`, repo)
460 rootErr := fmt.Sprintf(`unable to localize root "%s": unable to find one of 'kustomization.yaml', 'kustomization.yml' or 'Kustomization'`, repo)
461 var actualErr PathLocalizeError
462 require.ErrorAs(t, err, &actualErr)
463 require.Equal(t, repo, actualErr.Path)
464 require.ErrorContains(t, actualErr.FileError, fileErr)
465 require.ErrorContains(t, actualErr.RootError, rootErr)
466
467 SetupDir(t, fsExpected, testDir.String(), kustomization)
468 CheckFs(t, testDir.String(), fsExpected, fsActual)
469 }
470
471 func TestRemoteRootNoRef(t *testing.T) {
472 const root = simpleURL + "?submodules=0&timeout=300"
473 kustomization := map[string]string{
474 "kustomization.yaml": fmt.Sprintf(`resources:
475 - %s
476 `, root),
477 }
478 fsExpected, fsActual, testDir := PrepareFs(t, nil, kustomization)
479
480 _, err := localizer.Run(fsActual, testDir.String(), "", testDir.Join("dst"))
481
482 const fileErr = "invalid file reference: URL is a git repository"
483 rootErr := fmt.Sprintf(`localize remote root "%s" missing ref query string parameter`, root)
484 var actualErr PathLocalizeError
485 require.ErrorAs(t, err, &actualErr)
486 require.Equal(t, root, actualErr.Path)
487 require.EqualError(t, actualErr.FileError, fileErr)
488 require.EqualError(t, actualErr.RootError, rootErr)
489
490 SetupDir(t, fsExpected, testDir.String(), kustomization)
491 CheckFs(t, testDir.String(), fsExpected, fsActual)
492 }
493
494 func TestExistingCacheDir(t *testing.T) {
495 const remoteFile = `https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/api/krusty/testdata/localize/simple/deployment.yaml`
496 file := map[string]string{
497 "kustomization.yaml": fmt.Sprintf(`resources:
498 - %s
499 `, remoteFile),
500 filepath.Join(LocalizeDir, "file"): "existing",
501 }
502 fsExpected, fsActual, testDir := PrepareFs(t, []string{LocalizeDir}, file)
503
504 _, err := localizer.Run(fsActual, testDir.String(), "", testDir.Join("dst"))
505 require.ErrorContains(t, err, fmt.Sprintf(`already contains localized-files needed to store file "%s"`, remoteFile))
506
507 SetupDir(t, fsExpected, testDir.String(), file)
508 CheckFs(t, testDir.String(), fsExpected, fsActual)
509 }
510
511 func TestHelmNestedHome(t *testing.T) {
512 files := map[string]string{
513 "kustomization.yaml": fmt.Sprintf(`helmGlobals:
514 chartHome: %s
515 `, filepath.Join("nested", "dirs", "home")),
516 filepath.Join("nested", "dirs", "home", "name", "values.yaml"): `
517 minecraftServer:
518 difficulty: peaceful
519 `,
520 }
521 fsExpected, fsActual, testDir := PrepareFs(t, []string{
522 filepath.Join("nested", "dirs", "home", "name"),
523 }, files)
524
525 newDir := testDir.Join("dst")
526 dst, err := localizer.Run(fsActual, testDir.String(), "", newDir)
527 require.NoError(t, err)
528 require.Equal(t, newDir, dst)
529
530 SetupDir(t, fsExpected, dst, files)
531 CheckFs(t, dst, fsExpected, fsActual)
532 }
533
534 func TestHelmLinkedHome(t *testing.T) {
535
536
537
538
539
540
541
542
543 fsExpected, fsActual, scope := PrepareFs(t, []string{
544 "target",
545 filepath.Join("home", "name"),
546 },
547 map[string]string{
548 filepath.Join("target", "Kustomization"): `helmCharts:
549 - name: name
550 valuesFile: myValues.yaml
551 helmGlobals:
552 chartHome: home-link
553 `,
554 filepath.Join("target", "myValues.yaml"): valuesFile,
555 filepath.Join("home", "name", "values.yaml"): valuesFile,
556 })
557 link(t, scope, map[string]string{
558 filepath.Join("target", "home-link"): "home",
559 })
560
561 newDir := scope.Join("dst")
562 dst, err := localizer.Run(fsActual, scope.Join("target"), scope.String(), newDir)
563 require.NoError(t, err)
564 require.Equal(t, newDir, dst)
565
566 SetupDir(t, fsExpected, dst, map[string]string{
567 filepath.Join("target", "Kustomization"): fmt.Sprintf(`helmCharts:
568 - name: name
569 valuesFile: myValues.yaml
570 helmGlobals:
571 chartHome: %s
572 `, filepath.Join("..", "home")),
573 filepath.Join("target", "myValues.yaml"): valuesFile,
574 filepath.Join("home", "name", "values.yaml"): valuesFile,
575 })
576 CheckFs(t, dst, fsExpected, fsActual)
577 }
578
579 func TestHelmLinkedDefaultHome(t *testing.T) {
580
581
582
583
584
585
586 fsExpected, fsActual, target := PrepareFs(t, []string{
587 filepath.Join("home", "default"),
588 filepath.Join("home", "same"),
589 }, map[string]string{
590 "kustomization.yaml": fmt.Sprintf(`helmCharts:
591 - name: default
592 helmChartInflationGenerator:
593 - chartHome: %s
594 chartName: same
595 `, filepath.Join("home", "..", "charts")),
596 filepath.Join("home", "default", "values.yaml"): valuesFile,
597 filepath.Join("home", "same", "values.yaml"): valuesFile,
598 })
599 link(t, target, map[string]string{"charts": "home"})
600
601 newDir := target.Join("dst")
602 dst, err := localizer.Run(fsActual, target.String(), "", newDir)
603 require.NoError(t, err)
604 require.Equal(t, newDir, dst)
605
606 SetupDir(t, fsExpected, dst, map[string]string{
607 "kustomization.yaml": `helmChartInflationGenerator:
608 - chartHome: charts
609 chartName: same
610 helmCharts:
611 - name: default
612 `,
613 filepath.Join("charts", "default", "values.yaml"): valuesFile,
614 filepath.Join("charts", "same", "values.yaml"): valuesFile,
615 })
616 CheckFs(t, dst, fsExpected, fsActual)
617 }
618
619 func TestHelmHomeEscapesScope(t *testing.T) {
620
621
622
623
624
625
626
627
628 fsExpected, fsActual, testDir := PrepareFs(t, []string{
629 "dir",
630 filepath.Join("target", "home"),
631 }, map[string]string{
632 "file": valuesFile,
633 filepath.Join("target", "kustomization.yaml"): `helmGlobals:
634 chartHome: home
635 `,
636 })
637 link(t, testDir, map[string]string{
638 filepath.Join("target", "home", "dir-link"): "dir",
639 filepath.Join("target", "home", "file-link"): "file",
640 })
641
642 newDir := testDir.Join("dst")
643 dst, err := localizer.Run(fsActual, testDir.Join("target"), "", newDir)
644 require.NoError(t, err)
645 require.Equal(t, newDir, dst)
646
647 SetupDir(t, fsExpected, dst, map[string]string{
648 "kustomization.yaml": `helmGlobals:
649 chartHome: home
650 `,
651 })
652 require.NoError(t, fsExpected.Mkdir(filepath.Join(dst, "home")))
653 CheckFs(t, dst, fsExpected, fsActual)
654 }
655
656 func TestSymlinkedFileSource(t *testing.T) {
657
658
659
660
661 fsExpected, fsActual, target := PrepareFs(t, nil, map[string]string{
662 "kustomization.yaml": `configMapGenerator:
663 - files:
664 - filename-used-as-key-in-configMap
665 `,
666 "different-key": "properties",
667 })
668 link(t, target, map[string]string{
669 "filename-used-as-key-in-configMap": "different-key",
670 })
671
672 newDir := target.Join("dst")
673 dst, err := localizer.Run(fsActual, target.String(), "", newDir)
674 require.NoError(t, err)
675 require.Equal(t, newDir, dst)
676
677 SetupDir(t, fsExpected, dst, map[string]string{
678 "kustomization.yaml": `configMapGenerator:
679 - files:
680 - different-key
681 `,
682 "different-key": "properties",
683 })
684 CheckFs(t, dst, fsExpected, fsActual)
685 }
686
View as plain text