diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go index bf220e67f41..c61301b5e18 100644 --- a/pkg/container/docker_run.go +++ b/pkg/container/docker_run.go @@ -35,6 +35,7 @@ import ( "golang.org/x/term" "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/filecollector" ) // NewContainer creates a reference to a container @@ -735,12 +736,12 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno ignorer = gitignore.NewMatcher(ps) } - fc := &fileCollector{ - Fs: &defaultFs{}, + fc := &filecollector.FileCollector{ + Fs: &filecollector.DefaultFs{}, Ignorer: ignorer, SrcPath: srcPath, SrcPrefix: srcPrefix, - Handler: &tarCollector{ + Handler: &filecollector.TarCollector{ TarWriter: tw, UID: cr.UID, GID: cr.GID, @@ -748,7 +749,7 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno }, } - err = filepath.Walk(srcPath, fc.collectFiles(ctx, []string{})) + err = filepath.Walk(srcPath, fc.CollectFiles(ctx, []string{})) if err != nil { return err } diff --git a/pkg/container/host_environment.go b/pkg/container/host_environment.go index 91dae4c57fe..8130414d58b 100644 --- a/pkg/container/host_environment.go +++ b/pkg/container/host_environment.go @@ -21,6 +21,7 @@ import ( "golang.org/x/term" "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/filecollector" "github.com/nektos/act/pkg/lookpath" ) @@ -65,7 +66,7 @@ func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, ta return err } tr := tar.NewReader(tarStream) - cp := ©Collector{ + cp := &filecollector.CopyCollector{ DstDir: destPath, } for { @@ -104,16 +105,16 @@ func (e *HostEnvironment) CopyDir(destPath string, srcPath string, useGitIgnore ignorer = gitignore.NewMatcher(ps) } - fc := &fileCollector{ - Fs: &defaultFs{}, + fc := &filecollector.FileCollector{ + Fs: &filecollector.DefaultFs{}, Ignorer: ignorer, SrcPath: srcPath, SrcPrefix: srcPrefix, - Handler: ©Collector{ + Handler: &filecollector.CopyCollector{ DstDir: destPath, }, } - return filepath.Walk(srcPath, fc.collectFiles(ctx, []string{})) + return filepath.Walk(srcPath, fc.CollectFiles(ctx, []string{})) } } @@ -126,21 +127,21 @@ func (e *HostEnvironment) GetContainerArchive(ctx context.Context, srcPath strin if err != nil { return nil, err } - tc := &tarCollector{ + tc := &filecollector.TarCollector{ TarWriter: tw, } if fi.IsDir() { - srcPrefix := filepath.Dir(srcPath) + srcPrefix := srcPath if !strings.HasSuffix(srcPrefix, string(filepath.Separator)) { srcPrefix += string(filepath.Separator) } - fc := &fileCollector{ - Fs: &defaultFs{}, + fc := &filecollector.FileCollector{ + Fs: &filecollector.DefaultFs{}, SrcPath: srcPath, SrcPrefix: srcPrefix, Handler: tc, } - err = filepath.Walk(srcPath, fc.collectFiles(ctx, []string{})) + err = filepath.Walk(srcPath, fc.CollectFiles(ctx, []string{})) if err != nil { return nil, err } diff --git a/pkg/container/host_environment_test.go b/pkg/container/host_environment_test.go index 67787d950a7..2614a2f8285 100644 --- a/pkg/container/host_environment_test.go +++ b/pkg/container/host_environment_test.go @@ -1,4 +1,71 @@ package container +import ( + "archive/tar" + "context" + "io" + "os" + "path" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + // Type assert HostEnvironment implements ExecutionsEnvironment var _ ExecutionsEnvironment = &HostEnvironment{} + +func TestCopyDir(t *testing.T) { + dir, err := os.MkdirTemp("", "test-host-env-*") + assert.NoError(t, err) + defer os.RemoveAll(dir) + ctx := context.Background() + e := &HostEnvironment{ + Path: filepath.Join(dir, "path"), + TmpDir: filepath.Join(dir, "tmp"), + ToolCache: filepath.Join(dir, "tool_cache"), + ActPath: filepath.Join(dir, "act_path"), + StdOut: os.Stdout, + Workdir: path.Join("testdata", "scratch"), + } + _ = os.MkdirAll(e.Path, 0700) + _ = os.MkdirAll(e.TmpDir, 0700) + _ = os.MkdirAll(e.ToolCache, 0700) + _ = os.MkdirAll(e.ActPath, 0700) + err = e.CopyDir(e.Workdir, e.Path, true)(ctx) + assert.NoError(t, err) +} + +func TestGetContainerArchive(t *testing.T) { + dir, err := os.MkdirTemp("", "test-host-env-*") + assert.NoError(t, err) + defer os.RemoveAll(dir) + ctx := context.Background() + e := &HostEnvironment{ + Path: filepath.Join(dir, "path"), + TmpDir: filepath.Join(dir, "tmp"), + ToolCache: filepath.Join(dir, "tool_cache"), + ActPath: filepath.Join(dir, "act_path"), + StdOut: os.Stdout, + Workdir: path.Join("testdata", "scratch"), + } + _ = os.MkdirAll(e.Path, 0700) + _ = os.MkdirAll(e.TmpDir, 0700) + _ = os.MkdirAll(e.ToolCache, 0700) + _ = os.MkdirAll(e.ActPath, 0700) + expectedContent := []byte("sdde/7sh") + err = os.WriteFile(filepath.Join(e.Path, "action.yml"), expectedContent, 0600) + assert.NoError(t, err) + archive, err := e.GetContainerArchive(ctx, e.Path) + assert.NoError(t, err) + defer archive.Close() + reader := tar.NewReader(archive) + h, err := reader.Next() + assert.NoError(t, err) + assert.Equal(t, "action.yml", h.Name) + content, err := io.ReadAll(reader) + assert.NoError(t, err) + assert.Equal(t, expectedContent, content) + _, err = reader.Next() + assert.ErrorIs(t, err, io.EOF) +} diff --git a/pkg/container/testdata/scratch/test.txt b/pkg/container/testdata/scratch/test.txt new file mode 100644 index 00000000000..e7cbb71a0d5 --- /dev/null +++ b/pkg/container/testdata/scratch/test.txt @@ -0,0 +1 @@ +testfile \ No newline at end of file diff --git a/pkg/container/file_collector.go b/pkg/filecollector/file_collector.go similarity index 84% rename from pkg/container/file_collector.go rename to pkg/filecollector/file_collector.go index b4be0e886ec..8547bb7c5fa 100644 --- a/pkg/container/file_collector.go +++ b/pkg/filecollector/file_collector.go @@ -1,4 +1,4 @@ -package container +package filecollector import ( "archive/tar" @@ -17,18 +17,18 @@ import ( "github.com/go-git/go-git/v5/plumbing/format/index" ) -type fileCollectorHandler interface { +type Handler interface { WriteFile(path string, fi fs.FileInfo, linkName string, f io.Reader) error } -type tarCollector struct { +type TarCollector struct { TarWriter *tar.Writer UID int GID int DstDir string } -func (tc tarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error { +func (tc TarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error { // create a new dir/file header header, err := tar.FileInfoHeader(fi, linkName) if err != nil { @@ -59,11 +59,11 @@ func (tc tarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, return nil } -type copyCollector struct { +type CopyCollector struct { DstDir string } -func (cc *copyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error { +func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error { fdestpath := filepath.Join(cc.DstDir, fpath) if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil { return err @@ -82,29 +82,29 @@ func (cc *copyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string return nil } -type fileCollector struct { +type FileCollector struct { Ignorer gitignore.Matcher SrcPath string SrcPrefix string - Fs fileCollectorFs - Handler fileCollectorHandler + Fs Fs + Handler Handler } -type fileCollectorFs interface { +type Fs interface { Walk(root string, fn filepath.WalkFunc) error OpenGitIndex(path string) (*index.Index, error) Open(path string) (io.ReadCloser, error) Readlink(path string) (string, error) } -type defaultFs struct { +type DefaultFs struct { } -func (*defaultFs) Walk(root string, fn filepath.WalkFunc) error { +func (*DefaultFs) Walk(root string, fn filepath.WalkFunc) error { return filepath.Walk(root, fn) } -func (*defaultFs) OpenGitIndex(path string) (*index.Index, error) { +func (*DefaultFs) OpenGitIndex(path string) (*index.Index, error) { r, err := git.PlainOpen(path) if err != nil { return nil, err @@ -116,16 +116,16 @@ func (*defaultFs) OpenGitIndex(path string) (*index.Index, error) { return i, nil } -func (*defaultFs) Open(path string) (io.ReadCloser, error) { +func (*DefaultFs) Open(path string) (io.ReadCloser, error) { return os.Open(path) } -func (*defaultFs) Readlink(path string) (string, error) { +func (*DefaultFs) Readlink(path string) (string, error) { return os.Readlink(path) } //nolint:gocyclo -func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc { +func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc { i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...))) return func(file string, fi os.FileInfo, err error) error { if err != nil { @@ -166,7 +166,7 @@ func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []strin } } if err == nil && entry.Mode == filemode.Submodule { - err = fc.Fs.Walk(file, fc.collectFiles(ctx, split)) + err = fc.Fs.Walk(file, fc.CollectFiles(ctx, split)) if err != nil { return err } diff --git a/pkg/container/file_collector_test.go b/pkg/filecollector/file_collector_test.go similarity index 62% rename from pkg/container/file_collector_test.go rename to pkg/filecollector/file_collector_test.go index 241fd34b949..60a8d4dd276 100644 --- a/pkg/container/file_collector_test.go +++ b/pkg/filecollector/file_collector_test.go @@ -1,4 +1,4 @@ -package container +package filecollector import ( "archive/tar" @@ -95,16 +95,16 @@ func TestIgnoredTrackedfile(t *testing.T) { tw := tar.NewWriter(tmpTar) ps, _ := gitignore.ReadPatterns(worktree, []string{}) ignorer := gitignore.NewMatcher(ps) - fc := &fileCollector{ + fc := &FileCollector{ Fs: &memoryFs{Filesystem: fs}, Ignorer: ignorer, SrcPath: "mygitrepo", SrcPrefix: "mygitrepo" + string(filepath.Separator), - Handler: &tarCollector{ + Handler: &TarCollector{ TarWriter: tw, }, } - err := fc.Fs.Walk("mygitrepo", fc.collectFiles(context.Background(), []string{})) + err := fc.Fs.Walk("mygitrepo", fc.CollectFiles(context.Background(), []string{})) assert.NoError(t, err, "successfully collect files") tw.Close() _, _ = tmpTar.Seek(0, io.SeekStart) @@ -115,3 +115,58 @@ func TestIgnoredTrackedfile(t *testing.T) { _, err = tr.Next() assert.ErrorIs(t, err, io.EOF, "tar must only contain one element") } + +func TestSymlinks(t *testing.T) { + fs := memfs.New() + _ = fs.MkdirAll("mygitrepo/.git", 0o777) + dotgit, _ := fs.Chroot("mygitrepo/.git") + worktree, _ := fs.Chroot("mygitrepo") + repo, _ := git.Init(filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()), worktree) + // This file shouldn't be in the tar + f, err := worktree.Create(".env") + assert.NoError(t, err) + _, err = f.Write([]byte("test=val1\n")) + assert.NoError(t, err) + f.Close() + err = worktree.Symlink(".env", "test.env") + assert.NoError(t, err) + + w, err := repo.Worktree() + assert.NoError(t, err) + + // .gitignore is in the tar after adding it to the index + _, err = w.Add(".env") + assert.NoError(t, err) + _, err = w.Add("test.env") + assert.NoError(t, err) + + tmpTar, _ := fs.Create("temp.tar") + tw := tar.NewWriter(tmpTar) + ps, _ := gitignore.ReadPatterns(worktree, []string{}) + ignorer := gitignore.NewMatcher(ps) + fc := &FileCollector{ + Fs: &memoryFs{Filesystem: fs}, + Ignorer: ignorer, + SrcPath: "mygitrepo", + SrcPrefix: "mygitrepo" + string(filepath.Separator), + Handler: &TarCollector{ + TarWriter: tw, + }, + } + err = fc.Fs.Walk("mygitrepo", fc.CollectFiles(context.Background(), []string{})) + assert.NoError(t, err, "successfully collect files") + tw.Close() + _, _ = tmpTar.Seek(0, io.SeekStart) + tr := tar.NewReader(tmpTar) + h, err := tr.Next() + files := map[string]tar.Header{} + for err == nil { + files[h.Name] = *h + h, err = tr.Next() + } + + assert.Equal(t, ".env", files[".env"].Name) + assert.Equal(t, "test.env", files["test.env"].Name) + assert.Equal(t, ".env", files["test.env"].Linkname) + assert.ErrorIs(t, err, io.EOF, "tar must be read cleanly to EOF") +}