1
16
17 package cp
18
19 import (
20 "archive/tar"
21 "bytes"
22 "fmt"
23 "io"
24 "net/http"
25 "os"
26 "path/filepath"
27 "reflect"
28 "strings"
29 "testing"
30
31 "github.com/stretchr/testify/assert"
32 "github.com/stretchr/testify/require"
33
34 v1 "k8s.io/api/core/v1"
35 "k8s.io/apimachinery/pkg/api/errors"
36 "k8s.io/apimachinery/pkg/runtime"
37 "k8s.io/apimachinery/pkg/runtime/schema"
38 "k8s.io/cli-runtime/pkg/genericiooptions"
39 "k8s.io/client-go/rest/fake"
40 kexec "k8s.io/kubectl/pkg/cmd/exec"
41 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
42 "k8s.io/kubectl/pkg/scheme"
43 )
44
45 type FileType int
46
47 const (
48 RegularFile FileType = 0
49 SymLink FileType = 1
50 RegexFile FileType = 2
51 )
52
53 func TestExtractFileSpec(t *testing.T) {
54 tests := []struct {
55 spec string
56 expectedPod string
57 expectedNamespace string
58 expectedFile string
59 expectErr bool
60 }{
61 {
62 spec: "namespace/pod:/some/file",
63 expectedPod: "pod",
64 expectedNamespace: "namespace",
65 expectedFile: "/some/file",
66 },
67 {
68 spec: "pod:/some/file",
69 expectedPod: "pod",
70 expectedFile: "/some/file",
71 },
72 {
73 spec: "/some/file",
74 expectedFile: "/some/file",
75 },
76 {
77 spec: ":file:not:exist:in:local:filesystem",
78 expectErr: true,
79 },
80 {
81 spec: "namespace/pod/invalid:/some/file",
82 expectErr: true,
83 },
84 {
85 spec: "pod:/some/filenamewith:in",
86 expectedPod: "pod",
87 expectedFile: "/some/filenamewith:in",
88 },
89 }
90 for _, test := range tests {
91 spec, err := extractFileSpec(test.spec)
92 if test.expectErr && err == nil {
93 t.Errorf("unexpected non-error")
94 continue
95 }
96 if err != nil && !test.expectErr {
97 t.Errorf("unexpected error: %v", err)
98 continue
99 }
100 if spec.PodName != test.expectedPod {
101 t.Errorf("expected: %s, saw: %s", test.expectedPod, spec.PodName)
102 }
103 if spec.PodNamespace != test.expectedNamespace {
104 t.Errorf("expected: %s, saw: %s", test.expectedNamespace, spec.PodNamespace)
105 }
106 specFile := ""
107 if spec.File != nil {
108 specFile = spec.File.String()
109 }
110 if specFile != test.expectedFile {
111 t.Errorf("expected: %s, saw: %s", test.expectedFile, specFile)
112 }
113 }
114 }
115
116 func TestGetPrefix(t *testing.T) {
117 remoteSeparator := '/'
118 osSeparator := os.PathSeparator
119 tests := []struct {
120 input string
121 expected string
122 }{
123 {
124 input: "%[1]cfoo%[1]cbar",
125 expected: "foo%[1]cbar",
126 },
127 {
128 input: "foo%[1]cbar",
129 expected: "foo%[1]cbar",
130 },
131 }
132 for _, test := range tests {
133 outRemote := newRemotePath(fmt.Sprintf(test.input, remoteSeparator)).StripSlashes()
134 expectedRemote := fmt.Sprintf(test.expected, remoteSeparator)
135 if outRemote.String() != expectedRemote {
136 t.Errorf("remote expected: %s, saw: %s", expectedRemote, outRemote.String())
137 }
138 outLocal := newLocalPath(fmt.Sprintf(test.input, osSeparator)).StripSlashes()
139 expectedLocal := fmt.Sprintf(test.expected, osSeparator)
140 if outLocal.String() != expectedLocal {
141 t.Errorf("local expected: %s, saw: %s", expectedLocal, outLocal.String())
142 }
143 }
144 }
145
146 func TestStripPathShortcuts(t *testing.T) {
147 tests := []struct {
148 name string
149 input string
150 expected string
151 }{
152 {
153 name: "test single path shortcut prefix",
154 input: "../foo/bar",
155 expected: "foo/bar",
156 },
157 {
158 name: "test single path shortcut prefix",
159 input: `..\foo\bar`,
160 expected: "foo/bar",
161 },
162 {
163 name: "test multiple path shortcuts",
164 input: "../../foo/bar",
165 expected: "foo/bar",
166 },
167 {
168 name: "test multiple path shortcuts",
169 input: `..\..\foo\bar`,
170 expected: "foo/bar",
171 },
172 {
173 name: "test multiple path shortcuts with absolute path",
174 input: "/tmp/one/two/../../foo/bar",
175 expected: "tmp/foo/bar",
176 },
177 {
178 name: "test multiple path shortcuts with absolute path",
179 input: `\tmp\one\two\..\..\foo\bar`,
180 expected: "tmp/foo/bar",
181 },
182 {
183 name: "test multiple path shortcuts with no named directory",
184 input: "../../",
185 expected: "",
186 },
187 {
188 name: "test multiple path shortcuts with no named directory",
189 input: `..\..\`,
190 expected: "",
191 },
192 {
193 name: "test multiple path shortcuts with no named directory and no trailing slash",
194 input: "../..",
195 expected: "",
196 },
197 {
198 name: "test multiple path shortcuts with no named directory and no trailing slash",
199 input: `..\..`,
200 expected: "",
201 },
202 {
203 name: "test multiple path shortcuts with absolute path and filename containing leading dots",
204 input: "/tmp/one/two/../../foo/..bar",
205 expected: "tmp/foo/..bar",
206 },
207 {
208 name: "test multiple path shortcuts with absolute path and filename containing leading dots",
209 input: `\tmp\one\two\..\..\foo\..bar`,
210 expected: "tmp/foo/..bar",
211 },
212 {
213 name: "test multiple path shortcuts with no named directory and filename containing leading dots",
214 input: "../...foo",
215 expected: "...foo",
216 },
217 {
218 name: "test multiple path shortcuts with no named directory and filename containing leading dots",
219 input: `..\...foo`,
220 expected: "...foo",
221 },
222 {
223 name: "test filename containing leading dots",
224 input: "...foo",
225 expected: "...foo",
226 },
227 {
228 name: "test root directory",
229 input: "/",
230 expected: "",
231 },
232 {
233 name: "test root directory",
234 input: `\`,
235 expected: "",
236 },
237 }
238
239 for i, test := range tests {
240 out := newRemotePath(test.input).StripShortcuts()
241 if out.String() != test.expected {
242 t.Errorf("expected[%d]: %s, saw: %s", i, test.expected, out)
243 }
244 }
245 }
246 func TestIsDestRelative(t *testing.T) {
247 tests := []struct {
248 base string
249 dest string
250 relative bool
251 }{
252 {
253 base: "/dir",
254 dest: "/dir/../link",
255 relative: false,
256 },
257 {
258 base: "/dir",
259 dest: "/dir/../../link",
260 relative: false,
261 },
262 {
263 base: "/dir",
264 dest: "/link",
265 relative: false,
266 },
267 {
268 base: "/dir",
269 dest: "/dir/link",
270 relative: true,
271 },
272 {
273 base: "/dir",
274 dest: "/dir/int/../link",
275 relative: true,
276 },
277 {
278 base: "dir",
279 dest: "dir/link",
280 relative: true,
281 },
282 {
283 base: "dir",
284 dest: "dir/int/../link",
285 relative: true,
286 },
287 {
288 base: "dir",
289 dest: "dir/../../link",
290 relative: false,
291 },
292 }
293
294 for i, test := range tests {
295 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
296 if test.relative != isRelative(newLocalPath(test.base), newLocalPath(test.dest)) {
297 t.Errorf("unexpected result for: base %q, dest %q", test.base, test.dest)
298 }
299 })
300 }
301 }
302
303 func checkErr(t *testing.T, err error) {
304 if err != nil {
305 t.Errorf("unexpected error: %v", err)
306 t.FailNow()
307 }
308 }
309
310 func TestTarUntar(t *testing.T) {
311 dir, err := os.MkdirTemp("", "input")
312 checkErr(t, err)
313 dir = dir + "/"
314
315 dir2, err := os.MkdirTemp("", "output")
316 checkErr(t, err)
317 dir2 = dir2 + "/"
318
319 dir3, err := os.MkdirTemp("", "dir")
320 checkErr(t, err)
321
322 defer func() {
323 os.RemoveAll(dir)
324 os.RemoveAll(dir2)
325 os.RemoveAll(dir3)
326 }()
327
328 files := []struct {
329 name string
330 nameList []string
331 data string
332 omitted bool
333 fileType FileType
334 }{
335 {
336 name: "foo",
337 data: "foobarbaz",
338 fileType: RegularFile,
339 },
340 {
341 name: "dir/blah",
342 data: "bazblahfoo",
343 fileType: RegularFile,
344 },
345 {
346 name: "some/other/directory",
347 data: "with more data here",
348 fileType: RegularFile,
349 },
350 {
351 name: "blah",
352 data: "same file name different data",
353 fileType: RegularFile,
354 },
355 {
356 name: "gakki",
357 data: "tmp/gakki",
358 omitted: true,
359 fileType: SymLink,
360 },
361 {
362 name: "relative_to_dest",
363 data: dir2 + "/foo",
364 omitted: true,
365 fileType: SymLink,
366 },
367 {
368 name: "tricky_relative",
369 data: dir3 + "/xyz",
370 omitted: true,
371 fileType: SymLink,
372 },
373 {
374 name: "absolute_path",
375 data: "/tmp/gakki",
376 omitted: true,
377 fileType: SymLink,
378 },
379 {
380 name: "blah*",
381 nameList: []string{"blah1", "blah2"},
382 data: "regexp file name",
383 fileType: RegexFile,
384 },
385 }
386
387 for _, file := range files {
388 completePath := dir + file.name
389 if err := os.MkdirAll(filepath.Dir(completePath), 0755); err != nil {
390 t.Fatalf("unexpected error: %v", err)
391 }
392 if file.fileType == RegularFile {
393 createTmpFile(t, completePath, file.data)
394 } else if file.fileType == SymLink {
395 err := os.Symlink(file.data, completePath)
396 if err != nil {
397 t.Fatalf("unexpected error: %v", err)
398 }
399 } else if file.fileType == RegexFile {
400 for _, fileName := range file.nameList {
401 createTmpFile(t, dir+fileName, file.data)
402 }
403 } else {
404 t.Fatalf("unexpected file type: %v", file)
405 }
406 }
407
408 opts := NewCopyOptions(genericiooptions.NewTestIOStreamsDiscard())
409
410 writer := &bytes.Buffer{}
411 if err := makeTar(newLocalPath(dir), newRemotePath(dir), writer); err != nil {
412 t.Fatalf("unexpected error: %v", err)
413 }
414
415 reader := bytes.NewBuffer(writer.Bytes())
416 if err := opts.untarAll("", "", "", remotePath{}, newLocalPath(dir2), reader); err != nil {
417 t.Fatalf("unexpected error: %v", err)
418 }
419
420 for _, file := range files {
421 absPath := dir2 + strings.TrimPrefix(dir, os.TempDir())
422 filePath := absPath + file.name
423
424 if file.fileType == RegularFile {
425 cmpFileData(t, filePath, file.data)
426 } else if file.fileType == SymLink {
427 dest, err := os.Readlink(filePath)
428 if file.omitted {
429 if err != nil && strings.Contains(err.Error(), "no such file or directory") {
430 continue
431 }
432 t.Fatalf("expected to omit symlink for %s", filePath)
433 }
434 if err != nil {
435 t.Fatalf("unexpected error: %v", err)
436 }
437
438 if file.data != dest {
439 t.Fatalf("expected: %s, saw: %s", file.data, dest)
440 }
441 } else if file.fileType == RegexFile {
442 for _, fileName := range file.nameList {
443 cmpFileData(t, dir+fileName, file.data)
444 }
445 } else {
446 t.Fatalf("unexpected file type: %v", file)
447 }
448 }
449 }
450
451 func TestTarUntarWrongPrefix(t *testing.T) {
452 dir, err := os.MkdirTemp("", "input")
453 checkErr(t, err)
454 dir = dir + "/"
455
456 dir2, err := os.MkdirTemp("", "output")
457 checkErr(t, err)
458
459 defer func() {
460 os.RemoveAll(dir)
461 os.RemoveAll(dir2)
462 }()
463
464 completePath := dir + "foo"
465 if err := os.MkdirAll(filepath.Dir(completePath), 0755); err != nil {
466 t.Fatalf("unexpected error: %v", err)
467 }
468 createTmpFile(t, completePath, "sample data")
469
470 opts := NewCopyOptions(genericiooptions.NewTestIOStreamsDiscard())
471
472 writer := &bytes.Buffer{}
473 if err := makeTar(newLocalPath(dir), newRemotePath(dir), writer); err != nil {
474 t.Fatalf("unexpected error: %v", err)
475 }
476
477 reader := bytes.NewBuffer(writer.Bytes())
478 err = opts.untarAll("", "", "verylongprefix-showing-the-tar-was-tempered-with", remotePath{}, newLocalPath(dir2), reader)
479 if err == nil || !strings.Contains(err.Error(), "tar contents corrupted") {
480 t.Fatalf("unexpected error: %v", err)
481 }
482 }
483
484 func TestTarDestinationName(t *testing.T) {
485 dir, err := os.MkdirTemp(os.TempDir(), "input")
486 dir2, err2 := os.MkdirTemp(os.TempDir(), "output")
487 if err != nil || err2 != nil {
488 t.Errorf("unexpected error: %v | %v", err, err2)
489 t.FailNow()
490 }
491 defer func() {
492 if err := os.RemoveAll(dir); err != nil {
493 t.Errorf("Unexpected error cleaning up: %v", err)
494 }
495 if err := os.RemoveAll(dir2); err != nil {
496 t.Errorf("Unexpected error cleaning up: %v", err)
497 }
498 }()
499
500 files := []struct {
501 name string
502 data string
503 }{
504 {
505 name: "foo",
506 data: "foobarbaz",
507 },
508 {
509 name: "dir/blah",
510 data: "bazblahfoo",
511 },
512 {
513 name: "some/other/directory",
514 data: "with more data here",
515 },
516 {
517 name: "blah",
518 data: "same file name different data",
519 },
520 }
521
522
523 for _, file := range files {
524 completePath := dir + "/" + file.name
525 if err := os.MkdirAll(filepath.Dir(completePath), 0755); err != nil {
526 t.Errorf("unexpected error: %v", err)
527 t.FailNow()
528 }
529 createTmpFile(t, completePath, file.data)
530 }
531
532 reader, writer := io.Pipe()
533 go func() {
534 if err := makeTar(newLocalPath(dir), newRemotePath(dir2), writer); err != nil {
535 t.Errorf("unexpected error: %v", err)
536 }
537 }()
538
539 tarReader := tar.NewReader(reader)
540 for {
541 hdr, err := tarReader.Next()
542 if err == io.EOF {
543 break
544 } else if err != nil {
545 t.Errorf("unexpected error: %v", err)
546 t.FailNow()
547 }
548
549 if !strings.HasPrefix(hdr.Name, filepath.Base(dir2)) {
550 t.Errorf("expected %q as destination filename prefix, saw: %q", filepath.Base(dir2), hdr.Name)
551 }
552 }
553 }
554
555 func TestBadTar(t *testing.T) {
556 dir, err := os.MkdirTemp(os.TempDir(), "dest")
557 if err != nil {
558 t.Errorf("unexpected error: %v ", err)
559 t.FailNow()
560 }
561 defer os.RemoveAll(dir)
562
563
564 var buf bytes.Buffer
565 tw := tar.NewWriter(&buf)
566 var files = []struct {
567 name string
568 body string
569 }{
570 {"/prefix/foo/bar/../../home/bburns/names.txt", "Down and back"},
571 }
572 for _, file := range files {
573 hdr := &tar.Header{
574 Name: file.name,
575 Mode: 0600,
576 Size: int64(len(file.body)),
577 }
578 if err := tw.WriteHeader(hdr); err != nil {
579 t.Errorf("unexpected error: %v ", err)
580 t.FailNow()
581 }
582 if _, err := tw.Write([]byte(file.body)); err != nil {
583 t.Errorf("unexpected error: %v ", err)
584 t.FailNow()
585 }
586 }
587 if err := tw.Close(); err != nil {
588 t.Errorf("unexpected error: %v ", err)
589 t.FailNow()
590 }
591
592 opts := NewCopyOptions(genericiooptions.NewTestIOStreamsDiscard())
593 if err := opts.untarAll("", "", "/prefix", remotePath{}, newLocalPath(dir), &buf); err != nil {
594 t.Errorf("unexpected error: %v ", err)
595 t.FailNow()
596 }
597
598 for _, file := range files {
599 _, err := os.Stat(dir + filepath.Clean(file.name[len("/prefix"):]))
600 if err != nil {
601 t.Errorf("Error finding file: %v", err)
602 }
603 }
604 }
605
606 func TestCopyToPod(t *testing.T) {
607 tf := cmdtesting.NewTestFactory().WithNamespace("test")
608 ns := scheme.Codecs.WithoutConversion()
609 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
610
611 tf.Client = &fake.RESTClient{
612 GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
613 NegotiatedSerializer: ns,
614 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
615 responsePod := &v1.Pod{}
616 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, responsePod))))}, nil
617 }),
618 }
619
620 tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
621 ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
622
623 cmd := NewCmdCp(tf, ioStreams)
624
625 srcFile, err := os.MkdirTemp("", "test")
626 if err != nil {
627 t.Errorf("unexpected error: %v", err)
628 t.FailNow()
629 }
630 defer os.RemoveAll(srcFile)
631
632 tests := map[string]struct {
633 src string
634 dest string
635 expectedErr bool
636 }{
637 "copy to directory": {
638 src: srcFile,
639 dest: "/tmp/",
640 expectedErr: false,
641 },
642 "copy to root": {
643 src: srcFile,
644 dest: "/",
645 expectedErr: false,
646 },
647 "copy to empty file name": {
648 src: srcFile,
649 dest: "",
650 expectedErr: true,
651 },
652 "copy unexisting file": {
653 src: filepath.Join(srcFile, "nope"),
654 dest: "/tmp",
655 expectedErr: true,
656 },
657 }
658
659 for name, test := range tests {
660 opts := NewCopyOptions(ioStreams)
661 opts.Complete(tf, cmd, []string{test.src, fmt.Sprintf("pod-ns/pod-name:%s", test.dest)})
662 t.Run(name, func(t *testing.T) {
663 err = opts.Run()
664
665
666
667 if test.expectedErr && errors.IsNotFound(err) {
668 t.Errorf("expected error but didn't get one")
669 }
670 if !test.expectedErr && !errors.IsNotFound(err) {
671 t.Errorf("unexpected error: %v", err)
672 }
673 })
674 }
675 }
676
677 func TestCopyToPodNoPreserve(t *testing.T) {
678 tf := cmdtesting.NewTestFactory().WithNamespace("test")
679 ns := scheme.Codecs.WithoutConversion()
680 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
681
682 tf.Client = &fake.RESTClient{
683 GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
684 NegotiatedSerializer: ns,
685 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
686 responsePod := &v1.Pod{}
687 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, responsePod))))}, nil
688 }),
689 }
690
691 tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
692 ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
693
694 cmd := NewCmdCp(tf, ioStreams)
695
696 srcFile, err := os.MkdirTemp("", "test")
697 if err != nil {
698 t.Errorf("unexpected error: %v", err)
699 t.FailNow()
700 }
701 defer os.RemoveAll(srcFile)
702
703 tests := map[string]struct {
704 expectedCmd []string
705 nopreserve bool
706 }{
707 "copy to pod no preserve user and permissions": {
708 expectedCmd: []string{"tar", "--no-same-permissions", "--no-same-owner", "-xmf", "-", "-C", "."},
709 nopreserve: true,
710 },
711 "copy to pod preserve user and permissions": {
712 expectedCmd: []string{"tar", "-xmf", "-", "-C", "."},
713 nopreserve: false,
714 },
715 }
716 opts := NewCopyOptions(ioStreams)
717 src := fileSpec{
718 File: newLocalPath(srcFile),
719 }
720 dest := fileSpec{
721 PodNamespace: "pod-ns",
722 PodName: "pod-name",
723 File: newRemotePath("foo"),
724 }
725 opts.Complete(tf, cmd, nil)
726
727 for name, test := range tests {
728 t.Run(name, func(t *testing.T) {
729 options := &kexec.ExecOptions{}
730 opts.NoPreserve = test.nopreserve
731 err = opts.copyToPod(src, dest, options)
732 if !(reflect.DeepEqual(test.expectedCmd, options.Command)) {
733 t.Errorf("expected cmd: %v, got: %v", test.expectedCmd, options.Command)
734 }
735 })
736 }
737 }
738
739 func TestValidate(t *testing.T) {
740 tests := []struct {
741 name string
742 args []string
743 expectedErr bool
744 }{
745 {
746 name: "Validate Succeed",
747 args: []string{"1", "2"},
748 expectedErr: false,
749 },
750 {
751 name: "Validate Fail",
752 args: []string{"1", "2", "3"},
753 expectedErr: true,
754 },
755 }
756 ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
757 opts := NewCopyOptions(ioStreams)
758
759 for _, test := range tests {
760 t.Run(test.name, func(t *testing.T) {
761 opts.args = test.args
762 err := opts.Validate()
763 if (err != nil) != test.expectedErr {
764 t.Errorf("expected error: %v, saw: %v, error: %v", test.expectedErr, err != nil, err)
765 }
766 })
767 }
768 }
769
770 func TestUntar(t *testing.T) {
771 testdir, err := os.MkdirTemp("", "test-untar")
772 require.NoError(t, err)
773 defer os.RemoveAll(testdir)
774 t.Logf("Test base: %s", testdir)
775
776 basedir := testdir + "/" + "base"
777
778 type file struct {
779 path string
780 linkTarget string
781 expected string
782 }
783 files := []file{{
784
785 path: basedir + "/" + "abs",
786 expected: basedir + basedir + "/" + "abs",
787 }, {
788 path: testdir + "/" + "abs-out",
789 expected: basedir + testdir + "/" + "abs-out",
790 }, {
791 path: basedir + "/" + "nested/nest-abs",
792 expected: basedir + basedir + "/" + "nested/nest-abs",
793 }, {
794 path: basedir + "/" + "nested/../../nest-abs-out",
795 expected: basedir + testdir + "/" + "nest-abs-out",
796 }, {
797 path: "relative",
798 expected: basedir + "/" + "relative",
799 }, {
800 path: "../unrelative",
801 expected: "",
802 }, {
803 path: `..\unrelative-windows`,
804 expected: "",
805 }, {
806 path: "nested/nest-rel",
807 expected: basedir + "/" + "nested/nest-rel",
808 }, {
809 path: "nested/../../nest-unrelative",
810 expected: "",
811 }, {
812 path: `nested\..\..\nest-unrelative`,
813 expected: "",
814 }}
815
816 links := []file{}
817 for _, f := range files {
818 links = append(links, file{
819 path: f.path + "-innerlink",
820 linkTarget: "link-target",
821 expected: "",
822 }, file{
823 path: f.path + "-innerlink-abs",
824 linkTarget: basedir + "/" + "link-target",
825 expected: "",
826 }, file{
827 path: f.path + "-backlink",
828 linkTarget: ".." + "/" + "link-target",
829 expected: "",
830 }, file{
831 path: f.path + "-outerlink-abs",
832 linkTarget: testdir + "/" + "link-target",
833 expected: "",
834 })
835
836 if f.expected != "" {
837
838 outerlink, _ := filepath.Rel(f.expected, testdir)
839 links = append(links, file{
840 path: f.path + "outerlink",
841 linkTarget: outerlink + "/" + "link-target",
842 expected: "",
843 })
844 }
845 }
846 files = append(files, links...)
847
848
849 files = append(files,
850 file{
851 path: "nested/again/back-link",
852 linkTarget: "../../nested",
853 expected: "",
854 },
855 file{
856 path: "nested/again/back-link/../../../back-link-file",
857 expected: basedir + "/" + "back-link-file",
858 })
859
860
861 files = append(files,
862 file{
863 path: "nested/back-link-first",
864 linkTarget: "../",
865 expected: "",
866 },
867 file{
868 path: "nested/back-link-first/back-link-second",
869 linkTarget: "../",
870 expected: "",
871 })
872
873 files = append(files,
874 file{
875 path: "direct/dir/",
876 expected: "",
877 })
878
879 buf := &bytes.Buffer{}
880 tw := tar.NewWriter(buf)
881 expectations := map[string]bool{}
882 for _, f := range files {
883 if f.expected != "" {
884 expectations[f.expected] = false
885 }
886 if f.linkTarget == "" {
887 hdr := &tar.Header{
888 Name: f.path,
889 Mode: 0666,
890 Size: int64(len(f.path)),
891 }
892 require.NoError(t, tw.WriteHeader(hdr), f.path)
893 if !strings.HasSuffix(f.path, "/") {
894 _, err := tw.Write([]byte(f.path))
895 require.NoError(t, err, f.path)
896 }
897 } else {
898 hdr := &tar.Header{
899 Name: f.path,
900 Mode: int64(0777 | os.ModeSymlink),
901 Typeflag: tar.TypeSymlink,
902 Linkname: f.linkTarget,
903 }
904 require.NoError(t, tw.WriteHeader(hdr), f.path)
905 }
906 }
907 tw.Close()
908
909
910 output := (*testWriter)(t)
911 opts := NewCopyOptions(genericiooptions.IOStreams{In: &bytes.Buffer{}, Out: output, ErrOut: output})
912
913 require.NoError(t, opts.untarAll("", "", "", remotePath{}, newLocalPath(basedir), buf))
914
915 filepath.Walk(testdir, func(path string, info os.FileInfo, err error) error {
916 if err != nil {
917 return err
918 }
919 if info.IsDir() {
920 return nil
921 }
922 if _, ok := expectations[path]; !ok {
923 t.Errorf("Unexpected file at %s", path)
924 } else {
925 expectations[path] = true
926 }
927 return nil
928 })
929 for path, found := range expectations {
930 if !found {
931 t.Errorf("Missing expected file %s", path)
932 }
933 }
934 }
935
936 func TestUntar_SingleFile(t *testing.T) {
937 testdir, err := os.MkdirTemp("", "test-untar")
938 require.NoError(t, err)
939 defer os.RemoveAll(testdir)
940
941 dest := testdir + "/" + "target"
942
943 buf := &bytes.Buffer{}
944 tw := tar.NewWriter(buf)
945
946 const (
947 srcName = "source"
948 content = "file contents"
949 )
950 hdr := &tar.Header{
951 Name: srcName,
952 Mode: 0666,
953 Size: int64(len(content)),
954 }
955 require.NoError(t, tw.WriteHeader(hdr))
956 _, err = tw.Write([]byte(content))
957 require.NoError(t, err)
958 tw.Close()
959
960
961 output := (*testWriter)(t)
962 opts := NewCopyOptions(genericiooptions.IOStreams{In: &bytes.Buffer{}, Out: output, ErrOut: output})
963
964 require.NoError(t, opts.untarAll("", "", srcName, remotePath{}, newLocalPath(dest), buf))
965 cmpFileData(t, dest, content)
966 }
967
968 func createTmpFile(t *testing.T, filepath, data string) {
969 f, err := os.Create(filepath)
970 if err != nil {
971 t.Fatalf("unexpected error: %v", err)
972 }
973 defer f.Close()
974 if _, err := io.Copy(f, bytes.NewBuffer([]byte(data))); err != nil {
975 t.Fatalf("unexpected error: %v", err)
976 }
977 if err := f.Close(); err != nil {
978 t.Fatal(err)
979 }
980 }
981
982 func cmpFileData(t *testing.T, filePath, data string) {
983 actual, err := os.ReadFile(filePath)
984 require.NoError(t, err)
985 assert.EqualValues(t, data, actual)
986 }
987
988 type testWriter testing.T
989
990 func (t *testWriter) Write(p []byte) (n int, err error) {
991 t.Logf(string(p))
992 return len(p), nil
993 }
994
View as plain text