|
4 | 4 | "archive/tar" |
5 | 5 | "bytes" |
6 | 6 | "fmt" |
| 7 | + "io" |
| 8 | + "io/ioutil" |
7 | 9 | "os" |
8 | 10 | "os/user" |
9 | 11 | "path/filepath" |
@@ -657,6 +659,27 @@ func (ts *TargetState) Get(target string) (Entry, error) { |
657 | 659 | return ts.findEntry(targetName) |
658 | 660 | } |
659 | 661 |
|
| 662 | +func (ts *TargetState) AddArchive(r *tar.Reader, destinationDir string, stripComponents int, actuator Actuator) error { |
| 663 | + for { |
| 664 | + header, err := r.Next() |
| 665 | + if err == io.EOF { |
| 666 | + break |
| 667 | + } else if err != nil { |
| 668 | + return err |
| 669 | + } |
| 670 | + switch header.Typeflag { |
| 671 | + case tar.TypeDir, tar.TypeReg, tar.TypeSymlink: |
| 672 | + if err := ts.addArchiveHeader(r, header, destinationDir, stripComponents, actuator); err != nil { |
| 673 | + return err |
| 674 | + } |
| 675 | + case tar.TypeXGlobalHeader: |
| 676 | + default: |
| 677 | + return fmt.Errorf("%s: unspported typeflag '%c'", header.Name, header.Typeflag) |
| 678 | + } |
| 679 | + } |
| 680 | + return nil |
| 681 | +} |
| 682 | + |
660 | 683 | // Populate walks fs from ts.SourceDir to populate ts. |
661 | 684 | func (ts *TargetState) Populate(fs vfs.FS) error { |
662 | 685 | return vfs.Walk(fs, ts.SourceDir, func(path string, info os.FileInfo, _ error) error { |
@@ -741,6 +764,153 @@ func (ts *TargetState) Populate(fs vfs.FS) error { |
741 | 764 | }) |
742 | 765 | } |
743 | 766 |
|
| 767 | +func (ts *TargetState) addArchiveHeader(r *tar.Reader, header *tar.Header, destinationDir string, stripComponents int, actuator Actuator) error { |
| 768 | + targetPath := header.Name |
| 769 | + if stripComponents > 0 { |
| 770 | + targetPath = filepath.Join(strings.Split(targetPath, string(os.PathSeparator))[stripComponents:]...) |
| 771 | + } |
| 772 | + if destinationDir != "" { |
| 773 | + targetPath = filepath.Join(destinationDir, targetPath) |
| 774 | + } else { |
| 775 | + targetPath = filepath.Join(ts.TargetDir, targetPath) |
| 776 | + } |
| 777 | + targetName, err := filepath.Rel(ts.TargetDir, targetPath) |
| 778 | + if err != nil { |
| 779 | + return err |
| 780 | + } |
| 781 | + parentDirSourceName := "" |
| 782 | + entries := ts.Entries |
| 783 | + if parentDirName := filepath.Dir(targetName); parentDirName != "." { |
| 784 | + parentEntry, err := ts.findEntry(parentDirName) |
| 785 | + if err != nil { |
| 786 | + return err |
| 787 | + } |
| 788 | + parentDir, ok := parentEntry.(*Dir) |
| 789 | + if !ok { |
| 790 | + return fmt.Errorf("%s: parent is not a directory", targetName) |
| 791 | + } |
| 792 | + parentDirSourceName = parentDir.sourceName |
| 793 | + entries = parentDir.Entries |
| 794 | + } |
| 795 | + name := filepath.Base(targetName) |
| 796 | + switch header.Typeflag { |
| 797 | + case tar.TypeReg: |
| 798 | + var existingFile *File |
| 799 | + var existingContents []byte |
| 800 | + if entry, ok := entries[name]; ok { |
| 801 | + existingFile, ok = entry.(*File) |
| 802 | + if !ok { |
| 803 | + return fmt.Errorf("%s: already added and not a regular file", targetName) |
| 804 | + } |
| 805 | + existingContents, err = existingFile.Contents() |
| 806 | + if err != nil { |
| 807 | + return err |
| 808 | + } |
| 809 | + } |
| 810 | + perm := os.FileMode(header.Mode) & os.ModePerm |
| 811 | + empty := header.Size == 0 |
| 812 | + sourceName := ParsedSourceFileName{ |
| 813 | + FileName: name, |
| 814 | + Mode: perm, |
| 815 | + Empty: empty, |
| 816 | + }.SourceFileName() |
| 817 | + if parentDirSourceName != "" { |
| 818 | + sourceName = filepath.Join(parentDirSourceName, sourceName) |
| 819 | + } |
| 820 | + contents, err := ioutil.ReadAll(r) |
| 821 | + if err != nil { |
| 822 | + return err |
| 823 | + } |
| 824 | + file := &File{ |
| 825 | + sourceName: sourceName, |
| 826 | + targetName: targetName, |
| 827 | + Empty: empty, |
| 828 | + Perm: perm, |
| 829 | + Template: false, |
| 830 | + contents: contents, |
| 831 | + } |
| 832 | + if existingFile != nil { |
| 833 | + if bytes.Equal(existingFile.contents, file.contents) { |
| 834 | + if existingFile.sourceName == file.sourceName { |
| 835 | + return nil |
| 836 | + } |
| 837 | + return actuator.Rename(filepath.Join(ts.SourceDir, existingFile.sourceName), filepath.Join(ts.SourceDir, file.sourceName)) |
| 838 | + } |
| 839 | + if err := actuator.RemoveAll(filepath.Join(ts.SourceDir, existingFile.sourceName)); err != nil { |
| 840 | + return err |
| 841 | + } |
| 842 | + } |
| 843 | + entries[name] = file |
| 844 | + return actuator.WriteFile(filepath.Join(ts.SourceDir, sourceName), contents, 0666&^ts.Umask, existingContents) |
| 845 | + case tar.TypeDir: |
| 846 | + var existingDir *Dir |
| 847 | + if entry, ok := entries[name]; ok { |
| 848 | + existingDir, ok = entry.(*Dir) |
| 849 | + if !ok { |
| 850 | + return fmt.Errorf("%s: already added and not a directory", targetName) |
| 851 | + } |
| 852 | + } |
| 853 | + perm := os.FileMode(header.Mode) & os.ModePerm |
| 854 | + sourceName := ParsedSourceDirName{ |
| 855 | + DirName: name, |
| 856 | + Perm: perm, |
| 857 | + }.SourceDirName() |
| 858 | + if parentDirSourceName != "" { |
| 859 | + sourceName = filepath.Join(parentDirSourceName, sourceName) |
| 860 | + } |
| 861 | + dir := newDir(sourceName, targetName, perm) |
| 862 | + if existingDir != nil { |
| 863 | + if existingDir.sourceName == dir.sourceName { |
| 864 | + return nil |
| 865 | + } |
| 866 | + return actuator.Rename(filepath.Join(ts.SourceDir, existingDir.sourceName), filepath.Join(ts.SourceDir, dir.sourceName)) |
| 867 | + } |
| 868 | + // FIXME Add a .keep file if the directory is empty |
| 869 | + entries[name] = dir |
| 870 | + return actuator.Mkdir(filepath.Join(ts.SourceDir, sourceName), 0777&^ts.Umask) |
| 871 | + case tar.TypeSymlink: |
| 872 | + var existingSymlink *Symlink |
| 873 | + var existingLinkName string |
| 874 | + if entry, ok := entries[name]; ok { |
| 875 | + existingSymlink, ok = entry.(*Symlink) |
| 876 | + if !ok { |
| 877 | + return fmt.Errorf("%s: already added and not a symlink", targetName) |
| 878 | + } |
| 879 | + existingLinkName, err = existingSymlink.LinkName() |
| 880 | + if err != nil { |
| 881 | + return err |
| 882 | + } |
| 883 | + } |
| 884 | + sourceName := ParsedSourceFileName{ |
| 885 | + FileName: name, |
| 886 | + Mode: os.ModeSymlink, |
| 887 | + }.SourceFileName() |
| 888 | + if parentDirSourceName != "" { |
| 889 | + sourceName = filepath.Join(parentDirSourceName, sourceName) |
| 890 | + } |
| 891 | + symlink := &Symlink{ |
| 892 | + sourceName: sourceName, |
| 893 | + targetName: targetName, |
| 894 | + linkName: header.Linkname, |
| 895 | + } |
| 896 | + if existingSymlink != nil { |
| 897 | + if existingSymlink.linkName == symlink.linkName { |
| 898 | + if existingSymlink.sourceName == symlink.sourceName { |
| 899 | + return nil |
| 900 | + } |
| 901 | + return actuator.Rename(filepath.Join(ts.SourceDir, existingSymlink.sourceName), filepath.Join(ts.SourceDir, symlink.sourceName)) |
| 902 | + } |
| 903 | + if err := actuator.RemoveAll(filepath.Join(ts.SourceDir, existingSymlink.sourceName)); err != nil { |
| 904 | + return err |
| 905 | + } |
| 906 | + } |
| 907 | + entries[name] = symlink |
| 908 | + return actuator.WriteFile(filepath.Join(ts.SourceDir, symlink.sourceName), []byte(symlink.linkName), 0666&^ts.Umask, []byte(existingLinkName)) |
| 909 | + default: |
| 910 | + return fmt.Errorf("%s: unspported typeflag '%c'", header.Name, header.Typeflag) |
| 911 | + } |
| 912 | +} |
| 913 | + |
744 | 914 | func (ts *TargetState) executeTemplate(fs vfs.FS, path string) ([]byte, error) { |
745 | 915 | data, err := fs.ReadFile(path) |
746 | 916 | if err != nil { |
|
0 commit comments