github.com/Elemental-core/elementalcore@v0.0.0-20191206075037-63891242267a/internal/build/archive.go (about) 1 // Copyright 2016 The elementalcore Authors 2 // This file is part of the elementalcore library. 3 // 4 // The elementalcore library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The elementalcore library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the elementalcore library. If not, see <http://www.gnu.org/licenses/>. 16 17 package build 18 19 import ( 20 "archive/tar" 21 "archive/zip" 22 "compress/gzip" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "strings" 28 ) 29 30 type Archive interface { 31 // Directory adds a new directory entry to the archive and sets the 32 // directory for subsequent calls to Header. 33 Directory(name string) error 34 35 // Header adds a new file to the archive. The file is added to the directory 36 // set by Directory. The content of the file must be written to the returned 37 // writer. 38 Header(os.FileInfo) (io.Writer, error) 39 40 // Close flushes the archive and closes the underlying file. 41 Close() error 42 } 43 44 func NewArchive(file *os.File) (Archive, string) { 45 switch { 46 case strings.HasSuffix(file.Name(), ".zip"): 47 return NewZipArchive(file), strings.TrimSuffix(file.Name(), ".zip") 48 case strings.HasSuffix(file.Name(), ".tar.gz"): 49 return NewTarballArchive(file), strings.TrimSuffix(file.Name(), ".tar.gz") 50 default: 51 return nil, "" 52 } 53 } 54 55 // AddFile appends an existing file to an archive. 56 func AddFile(a Archive, file string) error { 57 fd, err := os.Open(file) 58 if err != nil { 59 return err 60 } 61 defer fd.Close() 62 fi, err := fd.Stat() 63 if err != nil { 64 return err 65 } 66 w, err := a.Header(fi) 67 if err != nil { 68 return err 69 } 70 if _, err := io.Copy(w, fd); err != nil { 71 return err 72 } 73 return nil 74 } 75 76 // WriteArchive creates an archive containing the given files. 77 func WriteArchive(name string, files []string) (err error) { 78 archfd, err := os.Create(name) 79 if err != nil { 80 return err 81 } 82 83 defer func() { 84 archfd.Close() 85 // Remove the half-written archive on failure. 86 if err != nil { 87 os.Remove(name) 88 } 89 }() 90 archive, basename := NewArchive(archfd) 91 if archive == nil { 92 return fmt.Errorf("unknown archive extension") 93 } 94 fmt.Println(name) 95 if err := archive.Directory(basename); err != nil { 96 return err 97 } 98 for _, file := range files { 99 fmt.Println(" +", filepath.Base(file)) 100 if err := AddFile(archive, file); err != nil { 101 return err 102 } 103 } 104 return archive.Close() 105 } 106 107 type ZipArchive struct { 108 dir string 109 zipw *zip.Writer 110 file io.Closer 111 } 112 113 func NewZipArchive(w io.WriteCloser) Archive { 114 return &ZipArchive{"", zip.NewWriter(w), w} 115 } 116 117 func (a *ZipArchive) Directory(name string) error { 118 a.dir = name + "/" 119 return nil 120 } 121 122 func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) { 123 head, err := zip.FileInfoHeader(fi) 124 if err != nil { 125 return nil, fmt.Errorf("can't make zip header: %v", err) 126 } 127 head.Name = a.dir + head.Name 128 head.Method = zip.Deflate 129 w, err := a.zipw.CreateHeader(head) 130 if err != nil { 131 return nil, fmt.Errorf("can't add zip header: %v", err) 132 } 133 return w, nil 134 } 135 136 func (a *ZipArchive) Close() error { 137 if err := a.zipw.Close(); err != nil { 138 return err 139 } 140 return a.file.Close() 141 } 142 143 type TarballArchive struct { 144 dir string 145 tarw *tar.Writer 146 gzw *gzip.Writer 147 file io.Closer 148 } 149 150 func NewTarballArchive(w io.WriteCloser) Archive { 151 gzw := gzip.NewWriter(w) 152 tarw := tar.NewWriter(gzw) 153 return &TarballArchive{"", tarw, gzw, w} 154 } 155 156 func (a *TarballArchive) Directory(name string) error { 157 a.dir = name + "/" 158 return a.tarw.WriteHeader(&tar.Header{ 159 Name: a.dir, 160 Mode: 0755, 161 Typeflag: tar.TypeDir, 162 }) 163 } 164 165 func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) { 166 head, err := tar.FileInfoHeader(fi, "") 167 if err != nil { 168 return nil, fmt.Errorf("can't make tar header: %v", err) 169 } 170 head.Name = a.dir + head.Name 171 if err := a.tarw.WriteHeader(head); err != nil { 172 return nil, fmt.Errorf("can't add tar header: %v", err) 173 } 174 return a.tarw, nil 175 } 176 177 func (a *TarballArchive) Close() error { 178 if err := a.tarw.Close(); err != nil { 179 return err 180 } 181 if err := a.gzw.Close(); err != nil { 182 return err 183 } 184 return a.file.Close() 185 }