github.1485827954.workers.dev/nektos/act@v0.2.63/pkg/filecollector/file_collector.go (about)

     1  package filecollector
     2  
     3  import (
     4  	"archive/tar"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/fs"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	git "github.com/go-git/go-git/v5"
    15  	"github.com/go-git/go-git/v5/plumbing/filemode"
    16  	"github.com/go-git/go-git/v5/plumbing/format/gitignore"
    17  	"github.com/go-git/go-git/v5/plumbing/format/index"
    18  )
    19  
    20  type Handler interface {
    21  	WriteFile(path string, fi fs.FileInfo, linkName string, f io.Reader) error
    22  }
    23  
    24  type TarCollector struct {
    25  	TarWriter *tar.Writer
    26  	UID       int
    27  	GID       int
    28  	DstDir    string
    29  }
    30  
    31  func (tc TarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
    32  	// create a new dir/file header
    33  	header, err := tar.FileInfoHeader(fi, linkName)
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	// update the name to correctly reflect the desired destination when untaring
    39  	header.Name = path.Join(tc.DstDir, fpath)
    40  	header.Mode = int64(fi.Mode())
    41  	header.ModTime = fi.ModTime()
    42  	header.Uid = tc.UID
    43  	header.Gid = tc.GID
    44  
    45  	// write the header
    46  	if err := tc.TarWriter.WriteHeader(header); err != nil {
    47  		return err
    48  	}
    49  
    50  	// this is a symlink no reader provided
    51  	if f == nil {
    52  		return nil
    53  	}
    54  
    55  	// copy file data into tar writer
    56  	if _, err := io.Copy(tc.TarWriter, f); err != nil {
    57  		return err
    58  	}
    59  	return nil
    60  }
    61  
    62  type CopyCollector struct {
    63  	DstDir string
    64  }
    65  
    66  func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
    67  	fdestpath := filepath.Join(cc.DstDir, fpath)
    68  	if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
    69  		return err
    70  	}
    71  	if linkName != "" {
    72  		return os.Symlink(linkName, fdestpath)
    73  	}
    74  	df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY, fi.Mode())
    75  	if err != nil {
    76  		return err
    77  	}
    78  	defer df.Close()
    79  	if _, err := io.Copy(df, f); err != nil {
    80  		return err
    81  	}
    82  	return nil
    83  }
    84  
    85  type FileCollector struct {
    86  	Ignorer   gitignore.Matcher
    87  	SrcPath   string
    88  	SrcPrefix string
    89  	Fs        Fs
    90  	Handler   Handler
    91  }
    92  
    93  type Fs interface {
    94  	Walk(root string, fn filepath.WalkFunc) error
    95  	OpenGitIndex(path string) (*index.Index, error)
    96  	Open(path string) (io.ReadCloser, error)
    97  	Readlink(path string) (string, error)
    98  }
    99  
   100  type DefaultFs struct {
   101  }
   102  
   103  func (*DefaultFs) Walk(root string, fn filepath.WalkFunc) error {
   104  	return filepath.Walk(root, fn)
   105  }
   106  
   107  func (*DefaultFs) OpenGitIndex(path string) (*index.Index, error) {
   108  	r, err := git.PlainOpen(path)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	i, err := r.Storer.Index()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return i, nil
   117  }
   118  
   119  func (*DefaultFs) Open(path string) (io.ReadCloser, error) {
   120  	return os.Open(path)
   121  }
   122  
   123  func (*DefaultFs) Readlink(path string) (string, error) {
   124  	return os.Readlink(path)
   125  }
   126  
   127  //nolint:gocyclo
   128  func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc {
   129  	i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...)))
   130  	return func(file string, fi os.FileInfo, err error) error {
   131  		if err != nil {
   132  			return err
   133  		}
   134  		if ctx != nil {
   135  			select {
   136  			case <-ctx.Done():
   137  				return fmt.Errorf("copy cancelled")
   138  			default:
   139  			}
   140  		}
   141  
   142  		sansPrefix := strings.TrimPrefix(file, fc.SrcPrefix)
   143  		split := strings.Split(sansPrefix, string(filepath.Separator))
   144  		// The root folders should be skipped, submodules only have the last path component set to "." by filepath.Walk
   145  		if fi.IsDir() && len(split) > 0 && split[len(split)-1] == "." {
   146  			return nil
   147  		}
   148  		var entry *index.Entry
   149  		if i != nil {
   150  			entry, err = i.Entry(strings.Join(split[len(submodulePath):], "/"))
   151  		} else {
   152  			err = index.ErrEntryNotFound
   153  		}
   154  		if err != nil && fc.Ignorer != nil && fc.Ignorer.Match(split, fi.IsDir()) {
   155  			if fi.IsDir() {
   156  				if i != nil {
   157  					ms, err := i.Glob(strings.Join(append(split[len(submodulePath):], "**"), "/"))
   158  					if err != nil || len(ms) == 0 {
   159  						return filepath.SkipDir
   160  					}
   161  				} else {
   162  					return filepath.SkipDir
   163  				}
   164  			} else {
   165  				return nil
   166  			}
   167  		}
   168  		if err == nil && entry.Mode == filemode.Submodule {
   169  			err = fc.Fs.Walk(file, fc.CollectFiles(ctx, split))
   170  			if err != nil {
   171  				return err
   172  			}
   173  			return filepath.SkipDir
   174  		}
   175  		path := filepath.ToSlash(sansPrefix)
   176  
   177  		// return on non-regular files (thanks to [kumo](https://medium.com/@komuw/just-like-you-did-fbdd7df829d3) for this suggested update)
   178  		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
   179  			linkName, err := fc.Fs.Readlink(file)
   180  			if err != nil {
   181  				return fmt.Errorf("unable to readlink '%s': %w", file, err)
   182  			}
   183  			return fc.Handler.WriteFile(path, fi, linkName, nil)
   184  		} else if !fi.Mode().IsRegular() {
   185  			return nil
   186  		}
   187  
   188  		// open file
   189  		f, err := fc.Fs.Open(file)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		defer f.Close()
   194  
   195  		if ctx != nil {
   196  			// make io.Copy cancellable by closing the file
   197  			cpctx, cpfinish := context.WithCancel(ctx)
   198  			defer cpfinish()
   199  			go func() {
   200  				select {
   201  				case <-cpctx.Done():
   202  				case <-ctx.Done():
   203  					f.Close()
   204  				}
   205  			}()
   206  		}
   207  
   208  		return fc.Handler.WriteFile(path, fi, "", f)
   209  	}
   210  }