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 }