1
2
3
4 package loader
5
6 import (
7 "bytes"
8 "fmt"
9 "io"
10 "net/http"
11 "os"
12 "path"
13 "path/filepath"
14 "reflect"
15 "strings"
16 "testing"
17
18 "github.com/stretchr/testify/require"
19 "sigs.k8s.io/kustomize/api/ifc"
20 "sigs.k8s.io/kustomize/api/internal/git"
21 "sigs.k8s.io/kustomize/api/konfig"
22 "sigs.k8s.io/kustomize/kyaml/filesys"
23 )
24
25 func TestIsRemoteFile(t *testing.T) {
26 cases := map[string]struct {
27 url string
28 valid bool
29 }{
30 "https file": {
31 "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/helloWorld/configMap.yaml",
32 true,
33 },
34 "malformed https": {
35
36 "https:/raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/helloWorld/configMap.yaml",
37 true,
38 },
39 "https dir": {
40 "https://github.com/kubernetes-sigs/kustomize//examples/helloWorld/",
41 true,
42 },
43 "no scheme": {
44 "github.com/kubernetes-sigs/kustomize//examples/helloWorld/",
45 false,
46 },
47 "ssh": {
48 "ssh://git@github.com/kubernetes-sigs/kustomize.git",
49 false,
50 },
51 "local": {
52 "pod.yaml",
53 false,
54 },
55 }
56 for name, test := range cases {
57 test := test
58 t.Run(name, func(t *testing.T) {
59 require.Equal(t, test.valid, IsRemoteFile(test.url))
60 })
61 }
62 }
63
64 type testData struct {
65 path string
66 expectedContent string
67 }
68
69 var testCases = []testData{
70 {
71 path: "foo/project/fileA.yaml",
72 expectedContent: "fileA content",
73 },
74 {
75 path: "foo/project/subdir1/fileB.yaml",
76 expectedContent: "fileB content",
77 },
78 {
79 path: "foo/project/subdir2/fileC.yaml",
80 expectedContent: "fileC content",
81 },
82 {
83 path: "foo/project/fileD.yaml",
84 expectedContent: "fileD content",
85 },
86 }
87
88 func MakeFakeFs(td []testData) filesys.FileSystem {
89 fSys := filesys.MakeFsInMemory()
90 for _, x := range td {
91 fSys.WriteFile(x.path, []byte(x.expectedContent))
92 }
93 return fSys
94 }
95
96 func makeLoader() *FileLoader {
97 return NewLoaderOrDie(
98 RestrictionRootOnly, MakeFakeFs(testCases), filesys.Separator)
99 }
100
101 func TestLoaderLoad(t *testing.T) {
102 require := require.New(t)
103
104 l1 := makeLoader()
105 repo := l1.Repo()
106 require.Empty(repo)
107 require.Equal("/", l1.Root())
108
109 for _, x := range testCases {
110 b, err := l1.Load(x.path)
111 require.NoError(err)
112
113 if !reflect.DeepEqual([]byte(x.expectedContent), b) {
114 t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
115 }
116 }
117 l2, err := l1.New("foo/project")
118 require.NoError(err)
119
120 repo = l2.Repo()
121 require.Empty(repo)
122 require.Equal("/foo/project", l2.Root())
123
124 for _, x := range testCases {
125 b, err := l2.Load(strings.TrimPrefix(x.path, "foo/project/"))
126 require.NoError(err)
127
128 if !reflect.DeepEqual([]byte(x.expectedContent), b) {
129 t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
130 }
131 }
132 l2, err = l1.New("foo/project/")
133 require.NoError(err)
134 require.Equal("/foo/project", l2.Root())
135 }
136
137 func TestLoaderNewSubDir(t *testing.T) {
138 require := require.New(t)
139
140 l1, err := makeLoader().New("foo/project")
141 require.NoError(err)
142
143 l2, err := l1.New("subdir1")
144 require.NoError(err)
145 require.Equal("/foo/project/subdir1", l2.Root())
146
147 x := testCases[1]
148 b, err := l2.Load("fileB.yaml")
149 require.NoError(err)
150
151 if !reflect.DeepEqual([]byte(x.expectedContent), b) {
152 t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
153 }
154 }
155
156 func TestLoaderBadRelative(t *testing.T) {
157 require := require.New(t)
158
159 l1, err := makeLoader().New("foo/project/subdir1")
160 require.NoError(err)
161 require.Equal("/foo/project/subdir1", l1.Root())
162
163
164 l2, err := l1.New("fileB.yaml")
165 require.Error(err)
166
167
168 l2, err = l1.New(filesys.SelfDir)
169 require.Error(err)
170
171
172 l2, err = l1.New("../subdir1")
173 require.Error(err)
174
175
176 l2, err = l1.New("..")
177 require.Error(err)
178
179
180 l2, err = l1.New("/foo/project")
181 require.Error(err)
182
183
184 l2, err = l1.New("/")
185 require.Error(err)
186
187
188 l2, err = l1.New("../subdir2")
189 require.NoError(err)
190 require.Equal("/foo/project/subdir2", l2.Root())
191
192 x := testCases[2]
193 b, err := l2.Load("fileC.yaml")
194 require.NoError(err)
195 if !reflect.DeepEqual([]byte(x.expectedContent), b) {
196 t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
197 }
198
199
200
201 l1, err = l2.New("../subdir1")
202 require.Error(err)
203 }
204
205 func TestNewEmptyLoader(t *testing.T) {
206 _, err := makeLoader().New("")
207 require.Error(t, err)
208 }
209
210 func TestNewRemoteLoaderDoesNotExist(t *testing.T) {
211 _, err := makeLoader().New("https://example.com/org/repo")
212 require.ErrorContains(t, err, "fetch")
213 }
214
215 func TestLoaderLocalScheme(t *testing.T) {
216
217
218 t.Run("file", func(t *testing.T) {
219 fSys, dir := setupOnDisk(t)
220 parts := []string{
221 "ssh:",
222 "resource.yaml",
223 }
224 require.NoError(t, fSys.Mkdir(dir.Join(parts[0])))
225 const content = "resource config"
226 require.NoError(t, fSys.WriteFile(
227 dir.Join(filepath.Join(parts...)),
228 []byte(content),
229 ))
230 actualContent, err := NewLoaderOrDie(RestrictionRootOnly,
231 fSys,
232 dir.String(),
233 ).Load(strings.Join(parts, "//"))
234 require.NoError(t, err)
235 require.Equal(t, content, string(actualContent))
236 })
237 t.Run("directory", func(t *testing.T) {
238 fSys, dir := setupOnDisk(t)
239 parts := []string{
240 "https:",
241 "root",
242 }
243 require.NoError(t, fSys.MkdirAll(dir.Join(filepath.Join(parts...))))
244 ldr, err := NewLoaderOrDie(RestrictionRootOnly,
245 fSys,
246 dir.String(),
247 ).New(strings.Join(parts, "//"))
248 require.NoError(t, err)
249 require.Empty(t, ldr.Repo())
250 })
251 }
252
253 const (
254 contentOk = "hi there, i'm OK data"
255 contentExteriorData = "i am data from outside the root"
256 )
257
258
259
260
261
262
263
264
265
266 func commonSetupForLoaderRestrictionTest(t *testing.T) (string, filesys.FileSystem) {
267 t.Helper()
268 fSys, tmpDir := setupOnDisk(t)
269 dir := tmpDir.String()
270
271 fSys.Mkdir(filepath.Join(dir, "base"))
272
273 fSys.WriteFile(
274 filepath.Join(dir, "base", "okayData"), []byte(contentOk))
275
276 fSys.WriteFile(
277 filepath.Join(dir, "exteriorData"), []byte(contentExteriorData))
278
279 os.Symlink(
280 filepath.Join(dir, "base", "okayData"),
281 filepath.Join(dir, "base", "symLinkToOkayData"))
282 os.Symlink(
283 filepath.Join(dir, "exteriorData"),
284 filepath.Join(dir, "base", "symLinkToExteriorData"))
285 return dir, fSys
286 }
287
288
289
290 func doSanityChecksAndDropIntoBase(
291 t *testing.T, l ifc.Loader) ifc.Loader {
292 t.Helper()
293 require := require.New(t)
294
295 data, err := l.Load(path.Join("base", "okayData"))
296 require.NoError(err)
297 require.Equal(contentOk, string(data))
298
299 data, err = l.Load("exteriorData")
300 require.NoError(err)
301 require.Equal(contentExteriorData, string(data))
302
303
304 l, err = l.New("base")
305 require.NoError(err)
306
307
308 data, err = l.Load("okayData")
309 require.NoError(err)
310 require.Equal(contentOk, string(data))
311
312
313 data, err = l.Load("symLinkToOkayData")
314 require.NoError(err)
315 require.Equal(contentOk, string(data))
316
317 return l
318 }
319
320 func TestRestrictionRootOnlyInRealLoader(t *testing.T) {
321 require := require.New(t)
322 dir, fSys := commonSetupForLoaderRestrictionTest(t)
323
324 var l ifc.Loader
325
326 l = NewLoaderOrDie(RestrictionRootOnly, fSys, dir)
327
328 l = doSanityChecksAndDropIntoBase(t, l)
329
330
331 _, err := l.Load("symLinkToExteriorData")
332 require.Error(err)
333 require.Contains(err.Error(), "is not in or below")
334
335
336
337 _, err = l.Load("../exteriorData")
338 require.Error(err)
339 require.Contains(err.Error(), "is not in or below")
340 }
341
342 func TestRestrictionNoneInRealLoader(t *testing.T) {
343 dir, fSys := commonSetupForLoaderRestrictionTest(t)
344
345 var l ifc.Loader
346
347 l = NewLoaderOrDie(RestrictionNone, fSys, dir)
348
349 l = doSanityChecksAndDropIntoBase(t, l)
350
351
352 _, err := l.Load("symLinkToExteriorData")
353 require.NoError(t, err)
354
355
356 _, err = l.Load("../exteriorData")
357 require.NoError(t, err)
358 }
359
360 func splitOnNthSlash(v string, n int) (string, string) {
361 left := ""
362 for i := 0; i < n; i++ {
363 k := strings.Index(v, "/")
364 if k < 0 {
365 break
366 }
367 left += v[:k+1]
368 v = v[k+1:]
369 }
370 return left[:len(left)-1], v
371 }
372
373 func TestSplit(t *testing.T) {
374 p := "a/b/c/d/e/f/g"
375 if left, right := splitOnNthSlash(p, 2); left != "a/b" || right != "c/d/e/f/g" {
376 t.Fatalf("got left='%s', right='%s'", left, right)
377 }
378 if left, right := splitOnNthSlash(p, 3); left != "a/b/c" || right != "d/e/f/g" {
379 t.Fatalf("got left='%s', right='%s'", left, right)
380 }
381 if left, right := splitOnNthSlash(p, 6); left != "a/b/c/d/e/f" || right != "g" {
382 t.Fatalf("got left='%s', right='%s'", left, right)
383 }
384 }
385
386 func TestNewLoaderAtGitClone(t *testing.T) {
387 require := require.New(t)
388
389 rootURL := "github.com/someOrg/someRepo"
390 pathInRepo := "foo/base"
391 url := rootURL + "/" + pathInRepo
392 coRoot := "/tmp"
393 fSys := filesys.MakeFsInMemory()
394 fSys.MkdirAll(coRoot)
395 fSys.MkdirAll(coRoot + "/" + pathInRepo)
396 fSys.WriteFile(
397 coRoot+"/"+pathInRepo+"/"+
398 konfig.DefaultKustomizationFileName(),
399 []byte(`
400 whatever
401 `))
402
403 repoSpec, err := git.NewRepoSpecFromURL(url)
404 require.NoError(err)
405
406 l, err := newLoaderAtGitClone(
407 repoSpec, fSys, nil,
408 git.DoNothingCloner(filesys.ConfirmedDir(coRoot)))
409 require.NoError(err)
410 repo := l.Repo()
411 require.Equal(coRoot, repo)
412 require.Equal(coRoot+"/"+pathInRepo, l.Root())
413
414 _, err = l.New(url)
415 require.Error(err)
416
417 _, err = l.New(rootURL + "/" + "foo")
418 require.Error(err)
419
420 pathInRepo = "foo/overlay"
421 fSys.MkdirAll(coRoot + "/" + pathInRepo)
422 url = rootURL + "/" + pathInRepo
423 l2, err := l.New(url)
424 require.NoError(err)
425
426 repo = l2.Repo()
427 require.Equal(coRoot, repo)
428 require.Equal(coRoot+"/"+pathInRepo, l2.Root())
429 }
430
431 func TestLoaderDisallowsLocalBaseFromRemoteOverlay(t *testing.T) {
432 require := require.New(t)
433
434
435 topDir := "/whatever"
436 cloneRoot := topDir + "/someClone"
437 fSys := filesys.MakeFsInMemory()
438 fSys.MkdirAll(topDir + "/highBase")
439 fSys.MkdirAll(cloneRoot + "/foo/base")
440 fSys.MkdirAll(cloneRoot + "/foo/overlay")
441
442 var l1 ifc.Loader
443
444
445
446 l1 = NewLoaderOrDie(
447 RestrictionRootOnly, fSys, cloneRoot+"/foo/overlay")
448 require.Equal(cloneRoot+"/foo/overlay", l1.Root())
449
450 l2, err := l1.New("../base")
451 require.NoError(nil)
452 require.Equal(cloneRoot+"/foo/base", l2.Root())
453
454 l3, err := l2.New("../../../highBase")
455 require.NoError(err)
456 require.Equal(topDir+"/highBase", l3.Root())
457
458
459
460
461
462
463
464
465
466
467
468
469 repoSpec, err := git.NewRepoSpecFromURL(
470 "github.com/someOrg/someRepo/foo/overlay")
471 require.NoError(err)
472
473 l1, err = newLoaderAtGitClone(
474 repoSpec, fSys, nil,
475 git.DoNothingCloner(filesys.ConfirmedDir(cloneRoot)))
476 require.NoError(err)
477 require.Equal(cloneRoot+"/foo/overlay", l1.Root())
478
479
480 l2, err = l1.New("../base")
481 require.NoError(err)
482 repo := l2.Repo()
483 require.Empty(repo)
484 require.Equal(cloneRoot+"/foo/base", l2.Root())
485
486
487 _, err = l2.New("../../../highBase")
488 require.Error(err)
489 require.Contains(err.Error(),
490 "base '/whatever/highBase' is outside '/whatever/someClone'")
491 }
492
493 func TestLoaderDisallowsRemoteBaseExitRepo(t *testing.T) {
494 fSys, dir := setupOnDisk(t)
495
496 repo := dir.Join("repo")
497 require.NoError(t, fSys.Mkdir(repo))
498
499 base := filepath.Join(repo, "base")
500 require.NoError(t, os.Symlink(dir.String(), base))
501
502 repoSpec, err := git.NewRepoSpecFromURL("https://github.com/org/repo/base")
503 require.NoError(t, err)
504
505 _, err = newLoaderAtGitClone(repoSpec, fSys, nil, git.DoNothingCloner(filesys.ConfirmedDir(repo)))
506 require.Error(t, err)
507 require.Contains(t, err.Error(), fmt.Sprintf("%q refers to directory outside of repo %q", base, repo))
508 }
509
510 func TestLocalLoaderReferencingGitBase(t *testing.T) {
511 require := require.New(t)
512
513 topDir := "/whatever"
514 cloneRoot := topDir + "/someClone"
515 fSys := filesys.MakeFsInMemory()
516 fSys.MkdirAll(topDir)
517 fSys.MkdirAll(cloneRoot + "/foo/base")
518
519 l1 := newLoaderAtConfirmedDir(
520 RestrictionRootOnly, filesys.ConfirmedDir(topDir), fSys, nil,
521 git.DoNothingCloner(filesys.ConfirmedDir(cloneRoot)))
522 require.Equal(topDir, l1.Root())
523
524 l2, err := l1.New("github.com/someOrg/someRepo/foo/base")
525 require.NoError(err)
526 repo := l2.Repo()
527 require.Equal(cloneRoot, repo)
528 require.Equal(cloneRoot+"/foo/base", l2.Root())
529 }
530
531 func TestRepoDirectCycleDetection(t *testing.T) {
532 require := require.New(t)
533
534 topDir := "/cycles"
535 cloneRoot := topDir + "/someClone"
536 fSys := filesys.MakeFsInMemory()
537 fSys.MkdirAll(topDir)
538 fSys.MkdirAll(cloneRoot)
539
540 l1 := newLoaderAtConfirmedDir(
541 RestrictionRootOnly, filesys.ConfirmedDir(topDir), fSys, nil,
542 git.DoNothingCloner(filesys.ConfirmedDir(cloneRoot)))
543 p1 := "github.com/someOrg/someRepo/foo"
544 rs1, err := git.NewRepoSpecFromURL(p1)
545 require.NoError(err)
546
547 l1.repoSpec = rs1
548 _, err = l1.New(p1)
549 require.Error(err)
550 require.Contains(err.Error(), "cycle detected")
551 }
552
553 func TestRepoIndirectCycleDetection(t *testing.T) {
554 require := require.New(t)
555
556 topDir := "/cycles"
557 cloneRoot := topDir + "/someClone"
558 fSys := filesys.MakeFsInMemory()
559 fSys.MkdirAll(topDir)
560 fSys.MkdirAll(cloneRoot)
561
562 l0 := newLoaderAtConfirmedDir(
563 RestrictionRootOnly, filesys.ConfirmedDir(topDir), fSys, nil,
564 git.DoNothingCloner(filesys.ConfirmedDir(cloneRoot)))
565
566 p1 := "github.com/someOrg/someRepo1"
567 p2 := "github.com/someOrg/someRepo2"
568
569 l1, err := l0.New(p1)
570 require.NoError(err)
571
572 l2, err := l1.New(p2)
573 require.NoError(err)
574
575 _, err = l2.New(p1)
576 require.Error(err)
577 require.Contains(err.Error(), "cycle detected")
578 }
579
580
581 type fakeRoundTripper func(req *http.Request) *http.Response
582
583 func (f fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
584 return f(req), nil
585 }
586
587 func makeFakeHTTPClient(fn fakeRoundTripper) *http.Client {
588 return &http.Client{
589 Transport: fn,
590 }
591 }
592
593
594 func TestLoaderHTTP(t *testing.T) {
595 require := require.New(t)
596
597 var testCasesFile = []testData{
598 {
599 path: "http/file.yaml",
600 expectedContent: "file content",
601 },
602 }
603
604 l1 := NewLoaderOrDie(
605 RestrictionRootOnly, MakeFakeFs(testCasesFile), filesys.Separator)
606 require.Equal("/", l1.Root())
607
608 for _, x := range testCasesFile {
609 b, err := l1.Load(x.path)
610 require.NoError(err)
611 if !reflect.DeepEqual([]byte(x.expectedContent), b) {
612 t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
613 }
614 }
615
616 var testCasesHTTP = []testData{
617 {
618 path: "http://example.com/resource.yaml",
619 expectedContent: "http content",
620 },
621 {
622 path: "https://example.com/resource.yaml",
623 expectedContent: "https content",
624 },
625 }
626
627 for _, x := range testCasesHTTP {
628 hc := makeFakeHTTPClient(func(req *http.Request) *http.Response {
629 u := req.URL.String()
630 require.Equal(x.path, u)
631 return &http.Response{
632 StatusCode: 200,
633 Body: io.NopCloser(bytes.NewBufferString(x.expectedContent)),
634 Header: make(http.Header),
635 }
636 })
637 l2 := l1
638 l2.http = hc
639 b, err := l2.Load(x.path)
640 require.NoError(err)
641 if !reflect.DeepEqual([]byte(x.expectedContent), b) {
642 t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
643 }
644 }
645
646 var testCaseUnsupported = []testData{
647 {
648 path: "httpsnotreal://example.com/resource.yaml",
649 expectedContent: "invalid",
650 },
651 }
652 for _, x := range testCaseUnsupported {
653 hc := makeFakeHTTPClient(func(req *http.Request) *http.Response {
654 t.Fatalf("unexpected request to URL %s", req.URL.String())
655 return nil
656 })
657 l2 := l1
658 l2.http = hc
659 _, err := l2.Load(x.path)
660 require.Error(err)
661 }
662 }
663
664
665
666
667
668 func setupOnDisk(t *testing.T) (filesys.FileSystem, filesys.ConfirmedDir) {
669 t.Helper()
670
671 fSys := filesys.MakeFsOnDisk()
672 dir, err := filesys.NewTmpConfirmedDir()
673 require.NoError(t, err)
674 t.Cleanup(func() {
675 _ = fSys.RemoveAll(dir.String())
676 })
677 return fSys, dir
678 }
679
View as plain text