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  }