package cache import ( "archive/tar" "compress/gzip" "errors" "fmt" "io" "os" "path/filepath" "strings" ) // Tar takes a source and variable writers and walks 'source' writing each file // found to the tar writer; the purpose for accepting multiple writers is to allow // for multiple outputs (for example a file, or md5 hash) func Tar(src string, writers ...io.Writer) error { if _, err := os.Stat(src); err != nil { return fmt.Errorf("unable to tar files - %v", err.Error()) } mw := io.MultiWriter(writers...) gzw := gzip.NewWriter(mw) defer gzw.Close() tw := tar.NewWriter(gzw) defer tw.Close() // walk path return filepath.Walk(src, func(file string, fi os.FileInfo, err error) error { if err != nil { return err } if !fi.Mode().IsRegular() { return nil } header, err := tar.FileInfoHeader(fi, fi.Name()) if err != nil { return err } // update the name to correctly reflect the desired destination when untaring header.Name = strings.TrimPrefix(strings.Replace(file, src, "", -1), string(filepath.Separator)) if err := tw.WriteHeader(header); err != nil { return err } f, err := os.Open(file) if err != nil { return err } if _, err := io.Copy(tw, f); err != nil { f.Close() return err } f.Close() return nil }) } // Untar takes a destination path and a reader; a tar reader loops over the tarfile // creating the file structure at 'dst' along the way, and writing any files func Untar(dst string, r io.Reader) error { gzr, err := gzip.NewReader(r) if err != nil { return err } defer gzr.Close() tr := tar.NewReader(gzr) for { header, err := tr.Next() if errors.Is(err, io.EOF) || header == nil { break } if err != nil { return err } target := filepath.Join(dst, header.Name) switch header.Typeflag { // if its a dir and it doesn't exist create it case tar.TypeDir: if _, err := os.Stat(target); err != nil { if err := os.MkdirAll(target, 0755); err != nil { return err } } // if it's a file create it case tar.TypeReg: f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) if err != nil { return err } if _, err := io.Copy(f, tr); err != nil { return err } f.Close() } } return nil }