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 }