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  }