github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/scripts/make-tarball.go (about)

     1  /*
     2  Archive
     3  
     4  Provides a single "MakeTarball" function to create tar (tar.gz)
     5  archives. Uses go libraries rather than calling out to GNU tar or
     6  similar.  Is more cross platform and makes it easy to prefix all
     7  contents inside of a directory that does not exist in the source.
     8  */
     9  package main
    10  
    11  import (
    12  	"archive/tar"
    13  	"compress/gzip"
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"regexp"
    20  	"strings"
    21  )
    22  
    23  // inspired by https://gist.github.com/jonmorehouse/9060515
    24  
    25  type archiveWorkUnit struct {
    26  	path string
    27  	stat os.FileInfo
    28  }
    29  
    30  func getContents(paths []string, exclusions []string) <-chan archiveWorkUnit {
    31  	var matchers []*regexp.Regexp
    32  	for _, pattern := range exclusions {
    33  		matchers = append(matchers, regexp.MustCompile(pattern))
    34  	}
    35  
    36  	output := make(chan archiveWorkUnit)
    37  
    38  	go func() {
    39  		for _, path := range paths {
    40  			err := filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
    41  				if err != nil {
    42  					return err
    43  				}
    44  
    45  				if info.IsDir() {
    46  					return nil
    47  				}
    48  
    49  				for _, exclude := range matchers {
    50  					if exclude.MatchString(p) {
    51  						return nil
    52  					}
    53  				}
    54  				output <- archiveWorkUnit{
    55  					path: p,
    56  					stat: info,
    57  				}
    58  				return nil
    59  			})
    60  
    61  			if err != nil {
    62  				panic(fmt.Sprintf("caught error walking file system: %+v", err))
    63  			}
    64  		}
    65  		close(output)
    66  	}()
    67  
    68  	return output
    69  }
    70  
    71  func addFile(tw *tar.Writer, prefix string, unit archiveWorkUnit) error {
    72  	fn, err := filepath.EvalSymlinks(unit.path)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	file, err := os.Open(fn)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	defer file.Close()
    82  	// now lets create the header as needed for this file within the tarball
    83  	header := new(tar.Header)
    84  	header.Name = filepath.Join(prefix, unit.path)
    85  	header.Size = unit.stat.Size()
    86  	header.Mode = int64(unit.stat.Mode())
    87  	header.ModTime = unit.stat.ModTime()
    88  	// write the header to the tarball archive
    89  	if err := tw.WriteHeader(header); err != nil {
    90  		return err
    91  	}
    92  	// copy the file data to the tarball
    93  	if _, err := io.Copy(tw, file); err != nil {
    94  		return err
    95  	}
    96  
    97  	// fmt.Printf("DEBUG: added %s to archive\n", header.Name)
    98  	return nil
    99  }
   100  
   101  func makeTarball(fileName, prefix string, paths []string, exclude []string) error {
   102  	// set up the output file
   103  	file, err := os.Create(fileName)
   104  	if err != nil {
   105  		return fmt.Errorf("problem creating file %s: %v", fileName, err)
   106  	}
   107  	defer file.Close()
   108  
   109  	// set up the  gzip writer
   110  	gw := gzip.NewWriter(file)
   111  	defer gw.Close()
   112  	tw := tar.NewWriter(gw)
   113  	defer tw.Close()
   114  
   115  	fmt.Println("creating archive:", fileName)
   116  
   117  	for unit := range getContents(paths, exclude) {
   118  		err := addFile(tw, prefix, unit)
   119  		if err != nil {
   120  			return fmt.Errorf("error adding path: %s [%+v]: %+v",
   121  				unit.path, unit, err)
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  type stringSlice []string
   129  
   130  func (i *stringSlice) Set(v string) error { *i = append(*i, v); return nil }
   131  func (i *stringSlice) String() string     { return strings.Join([]string(*i), ", ") }
   132  
   133  func main() {
   134  	var (
   135  		name     string
   136  		prefix   string
   137  		items    stringSlice
   138  		excludes stringSlice
   139  	)
   140  
   141  	flag.Var(&items, "item", "specify item to add to the archive")
   142  	flag.Var(&excludes, "exclude", "regular expressions to exclude files")
   143  	flag.StringVar(&name, "name", "archive.tar.gz", "full path to the archive")
   144  	flag.StringVar(&prefix, "prefix", "", "prefix of path within the archive")
   145  	flag.Parse()
   146  
   147  	if err := makeTarball(name, prefix, items, excludes); err != nil {
   148  		fmt.Printf("ERROR: %+v\n", err)
   149  		os.Exit(1)
   150  	}
   151  
   152  	fmt.Println("created archive:", name)
   153  }