github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/archive/artifacts.go (about) 1 package archive 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/evergreen-ci/evergreen/util" 13 "github.com/mongodb/grip" 14 "github.com/mongodb/grip/slogger" 15 "github.com/pkg/errors" 16 ) 17 18 // TarContentsFile represents a tar file on disk. 19 type TarContentsFile struct { 20 path string 21 info os.FileInfo 22 } 23 24 // BuildArchive reads the rootPath directory into the tar.Writer, 25 // taking included and excluded strings into account. 26 // Returns the number of files that were added to the archive 27 func BuildArchive(tarWriter *tar.Writer, rootPath string, includes []string, 28 excludes []string, log *slogger.Logger) (int, error) { 29 30 pathsToAdd := make(chan TarContentsFile) 31 done := make(chan bool) 32 errChan := make(chan error) 33 numFilesArchived := 0 34 go func(outputChan chan TarContentsFile) { 35 for _, includePattern := range includes { 36 dir, filematch := filepath.Split(includePattern) 37 dir = filepath.Join(rootPath, dir) 38 exists, err := util.FileExists(dir) 39 if err != nil { 40 errChan <- err 41 } 42 if !exists { 43 continue 44 } 45 46 var walk filepath.WalkFunc 47 48 if filematch == "**" { 49 walk = func(path string, info os.FileInfo, err error) error { 50 outputChan <- TarContentsFile{path, info} 51 return nil 52 } 53 grip.Warning(filepath.Walk(dir, walk)) 54 } else if strings.Contains(filematch, "**") { 55 globSuffix := filematch[2:] 56 walk = func(path string, info os.FileInfo, err error) error { 57 if strings.HasSuffix(filepath.Base(path), globSuffix) { 58 outputChan <- TarContentsFile{path, info} 59 } 60 return nil 61 } 62 grip.Warning(filepath.Walk(dir, walk)) 63 } else { 64 walk = func(path string, info os.FileInfo, err error) error { 65 a, b := filepath.Split(path) 66 if filepath.Clean(a) == filepath.Clean(dir) { 67 match, err := filepath.Match(filematch, b) 68 if err != nil { 69 errChan <- err 70 } 71 if match { 72 outputChan <- TarContentsFile{path, info} 73 } 74 } 75 return nil 76 } 77 grip.Warning(filepath.Walk(rootPath, walk)) 78 } 79 } 80 close(outputChan) 81 return 82 }(pathsToAdd) 83 84 go func(inputChan chan TarContentsFile) { 85 processed := map[string]bool{} 86 FileChanLoop: 87 for file := range inputChan { 88 var intarball string 89 // Tarring symlinks doesn't work reliably right now, so if the file is 90 // a symlink, leave intarball path intact but write from the file 91 // underlying the symlink. 92 if file.info.Mode()&os.ModeSymlink > 0 { 93 symlinkPath, err := filepath.EvalSymlinks(file.path) 94 if err != nil { 95 log.Logf(slogger.WARN, "Could not follow symlink %v, ignoring", file.path) 96 continue 97 } else { 98 log.Logf(slogger.INFO, "Following symlink in %v, got: %v", file.path, symlinkPath) 99 symlinkFileInfo, err := os.Stat(symlinkPath) 100 if err != nil { 101 log.Logf(slogger.WARN, "Failed to get underlying file `%v` for symlink %v, ignoring", symlinkPath, file.path) 102 continue 103 } 104 105 intarball = strings.Replace(file.path, "\\", "/", -1) 106 file.path = symlinkPath 107 file.info = symlinkFileInfo 108 } 109 } else { 110 intarball = strings.Replace(file.path, "\\", "/", -1) 111 } 112 rootPathPrefix := strings.Replace(rootPath, "\\", "/", -1) 113 intarball = strings.Replace(intarball, "\\", "/", -1) 114 intarball = strings.Replace(intarball, rootPathPrefix, "", 1) 115 intarball = filepath.Clean(intarball) 116 intarball = strings.Replace(intarball, "\\", "/", -1) 117 118 //strip any leading slash from the tarball header path 119 intarball = strings.TrimLeft(intarball, "/") 120 121 log.Logf(slogger.INFO, "Adding to tarball: %s", intarball) 122 if _, hasKey := processed[intarball]; hasKey { 123 continue 124 } else { 125 processed[intarball] = true 126 } 127 if file.info.IsDir() { 128 continue 129 } 130 131 _, fileName := filepath.Split(file.path) 132 for _, ignore := range excludes { 133 if match, _ := filepath.Match(ignore, fileName); match { 134 continue FileChanLoop 135 } 136 } 137 138 hdr := new(tar.Header) 139 hdr.Name = strings.TrimPrefix(intarball, rootPathPrefix) 140 hdr.Mode = int64(file.info.Mode()) 141 hdr.Size = file.info.Size() 142 hdr.ModTime = file.info.ModTime() 143 144 numFilesArchived++ 145 err := tarWriter.WriteHeader(hdr) 146 if err != nil { 147 errChan <- errors.Wrapf(err, "Error writing header for %v", intarball) 148 return 149 } 150 151 in, err := os.Open(file.path) 152 if err != nil { 153 errChan <- errors.Wrapf(err, "Error opening %v", file.path) 154 return 155 } 156 157 amountWrote, err := io.Copy(tarWriter, in) 158 if err != nil { 159 grip.Debug(in.Close()) 160 errChan <- errors.Wrapf(err, "Error writing into tar for %v", file.path) 161 return 162 } 163 164 if amountWrote != hdr.Size { 165 grip.Debug(in.Close()) 166 errChan <- errors.Errorf(`Error writing to archive for %v: 167 header size %v but wrote %v`, 168 intarball, hdr.Size, amountWrote) 169 return 170 } 171 grip.Debug(in.Close()) 172 grip.Warning(tarWriter.Flush()) 173 } 174 done <- true 175 }(pathsToAdd) 176 177 select { 178 case <-done: 179 return numFilesArchived, nil 180 case err := <-errChan: 181 return numFilesArchived, err 182 } 183 } 184 185 // Extract unpacks the tar.Reader into rootPath. 186 func Extract(tarReader *tar.Reader, rootPath string) error { 187 for { 188 hdr, err := tarReader.Next() 189 if err == io.EOF { 190 return nil //reached end of archive, we are done. 191 } 192 if err != nil { 193 return errors.WithStack(err) 194 } 195 196 if hdr.Typeflag == tar.TypeDir { 197 // this tar entry is a directory - need to mkdir it 198 localDir := fmt.Sprintf("%v/%v", rootPath, hdr.Name) 199 err = os.MkdirAll(localDir, 0755) 200 if err != nil { 201 return errors.WithStack(err) 202 } 203 } else if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { 204 // this tar entry is a regular file (not a dir or link) 205 // first, ensure the file's parent directory exists 206 localFile := fmt.Sprintf("%v/%v", rootPath, hdr.Name) 207 dir := filepath.Dir(localFile) 208 err = os.MkdirAll(dir, 0755) 209 if err != nil { 210 return errors.WithStack(err) 211 } 212 213 // Now create the file itself, and write in the contents. 214 215 // Not using 'defer f.Close()' because this is in a loop, 216 // and we don't want to wait for the whole archive to finish to 217 // close the files - so each is closed explicitly. 218 219 f, err := os.Create(localFile) 220 if err != nil { 221 return errors.WithStack(err) 222 } 223 224 _, err = io.Copy(f, tarReader) 225 if err != nil { 226 grip.CatchError(f.Close()) 227 return errors.WithStack(err) 228 } 229 230 // File's permissions should match what was in the archive 231 err = os.Chmod(f.Name(), os.FileMode(int32(hdr.Mode))) 232 if err != nil { 233 grip.CatchError(f.Close()) 234 return errors.WithStack(err) 235 } 236 grip.CatchError(f.Close()) 237 } else { 238 return errors.New("Unknown file type in archive.") 239 } 240 } 241 } 242 243 // TarGzReader returns a file, gzip reader, and tar reader for the given path. 244 // The tar reader wraps the gzip reader, which wraps the file. 245 func TarGzReader(path string) (f, gz io.ReadCloser, tarReader *tar.Reader, err error) { 246 f, err = os.Open(path) 247 if err != nil { 248 return nil, nil, nil, errors.WithStack(err) 249 } 250 gz, err = gzip.NewReader(f) 251 if err != nil { 252 defer f.Close() 253 return nil, nil, nil, errors.WithStack(err) 254 } 255 tarReader = tar.NewReader(gz) 256 return f, gz, tarReader, nil 257 } 258 259 // TarGzWriter returns a file, gzip writer, and tarWriter for the path. 260 // The tar writer wraps the gzip writer, which wraps the file. 261 func TarGzWriter(path string) (f, gz io.WriteCloser, tarWriter *tar.Writer, err error) { 262 f, err = os.Create(path) 263 if err != nil { 264 return nil, nil, nil, errors.WithStack(err) 265 } 266 gz = gzip.NewWriter(f) 267 tarWriter = tar.NewWriter(gz) 268 return f, gz, tarWriter, nil 269 }