github.com/saucelabs/saucectl@v0.175.1/internal/archive/tar/tar.go (about) 1 package tar 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/rs/zerolog/log" 12 "github.com/saucelabs/saucectl/internal/sauceignore" 13 ) 14 15 // Options represents the options applied when archiving files. 16 type Options struct { 17 Permission *Permission 18 } 19 20 // Permission represents the permissions applied when archiving files. 21 type Permission struct { 22 Mode int64 // Permission and mode bits 23 UID int // User ID of owner 24 GID int // Group ID of owner 25 } 26 27 // addFileToArchive adds a file into an archive. 28 func addFileToArchive(fileName string, fileInfo os.FileInfo, rootFolder string, matcher sauceignore.Matcher, opts Options, w *tar.Writer) error { 29 if matcher.Match(strings.Split(fileName, string(os.PathSeparator)), fileInfo.IsDir()) { 30 log.Debug().Str("fileName", fileName).Msg("Ignoring file") 31 return nil 32 } 33 header, err := tar.FileInfoHeader(fileInfo, fileName) 34 if err != nil { 35 return err 36 } 37 38 if opts.Permission != nil { 39 header.Mode = opts.Permission.Mode 40 header.Uid = opts.Permission.UID 41 header.Gid = opts.Permission.GID 42 } 43 44 relName := filepath.Base(fileName) 45 if rootFolder != "" { 46 relName, err = filepath.Rel(rootFolder, fileName) 47 if err != nil { 48 return err 49 } 50 } 51 52 relName = filepath.ToSlash(relName) 53 header.Name = relName 54 55 if fileInfo.Mode().Type() == os.ModeSymlink { 56 linkTarget, err := filepath.EvalSymlinks(fileName) 57 if err != nil { 58 return err 59 } 60 relLinkName, err := filepath.Rel(filepath.Dir(fileName), linkTarget) 61 if err != nil { 62 return err 63 } 64 header.Linkname = relLinkName 65 } 66 67 if err := w.WriteHeader(header); err != nil { 68 return err 69 } 70 71 if fileInfo.IsDir() || fileInfo.Mode().Type() == os.ModeSymlink { 72 return nil 73 } 74 75 srcFile, err := os.Open(fileName) 76 if err != nil { 77 return err 78 } 79 defer srcFile.Close() 80 81 _, err = io.Copy(w, srcFile) 82 if err != nil { 83 return err 84 } 85 return nil 86 } 87 88 // Archive archives the resource and exclude files and folders based on sauceignore logic. 89 func Archive(src string, matcher sauceignore.Matcher, opts Options) (io.Reader, error) { 90 bb := new(bytes.Buffer) 91 w := tar.NewWriter(bb) 92 defer w.Close() 93 94 infoSrc, err := os.Stat(src) 95 if err != nil { 96 return nil, err 97 } 98 99 // Single file addition 100 if !infoSrc.IsDir() { 101 err = addFileToArchive(src, infoSrc, "", matcher, opts, w) 102 if err != nil { 103 return nil, err 104 } 105 return bytes.NewReader(bb.Bytes()), nil 106 } 107 108 walker := func(file string, fileInfo os.FileInfo, err error) error { 109 if err != nil { 110 return err 111 } 112 113 err = addFileToArchive(file, fileInfo, src, matcher, opts, w) 114 if err != nil { 115 return err 116 } 117 return nil 118 } 119 120 if err := filepath.Walk(src, walker); err != nil { 121 return nil, err 122 } 123 return bytes.NewReader(bb.Bytes()), nil 124 }