diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go index 22c2008..7f0a577 100644 --- a/pkg/container/docker_run.go +++ b/pkg/container/docker_run.go @@ -12,6 +12,7 @@ import ( "path/filepath" "regexp" "runtime" + "strconv" "strings" "github.com/go-git/go-billy/v5/helper/polyfill" @@ -125,6 +126,13 @@ func (cr *containerReference) Start(attach bool) common.Executor { cr.attach().IfBool(attach), cr.start(), cr.wait().IfBool(attach), + cr.tryReadUID(), + cr.tryReadGID(), + func(ctx context.Context) error { + // If this fails, then folders have wrong permissions on non root container + _ = cr.Exec([]string{"chown", "-R", fmt.Sprintf("%d:%d", cr.UID, cr.GID), cr.input.WorkingDir}, nil, "0", "")(ctx) + return nil + }, ).IfNot(common.Dryrun), ) } @@ -154,8 +162,12 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common. func (cr *containerReference) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor { return common.NewPipelineExecutor( common.NewInfoExecutor("%sdocker cp src=%s dst=%s", logPrefix, srcPath, destPath), - cr.Exec([]string{"mkdir", "-p", destPath}, nil, "", ""), cr.copyDir(destPath, srcPath, useGitIgnore), + func(ctx context.Context) error { + // If this fails, then folders have wrong permissions on non root container + _ = cr.Exec([]string{"chown", "-R", fmt.Sprintf("%d:%d", cr.UID, cr.GID), destPath}, nil, "0", "")(ctx) + return nil + }, ).IfNot(common.Dryrun) } @@ -211,6 +223,8 @@ type containerReference struct { cli client.APIClient id string input *NewContainerInput + UID int + GID int } func GetDockerClient(ctx context.Context) (cli client.APIClient, err error) { @@ -581,6 +595,47 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo } } +func (cr *containerReference) tryReadID(opt string, cbk func(id int)) common.Executor { + return func(ctx context.Context) error { + idResp, err := cr.cli.ContainerExecCreate(ctx, cr.id, types.ExecConfig{ + Cmd: []string{"id", opt}, + AttachStdout: true, + AttachStderr: true, + }) + if err != nil { + return nil + } + + resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, types.ExecStartCheck{}) + if err != nil { + return nil + } + defer resp.Close() + + sid, err := resp.Reader.ReadString('\n') + if err != nil { + return nil + } + exp := regexp.MustCompile(`\d+\n`) + found := exp.FindString(sid) + id, err := strconv.ParseInt(found[:len(found)-1], 10, 32) + if err != nil { + return nil + } + cbk(int(id)) + + return nil + } +} + +func (cr *containerReference) tryReadUID() common.Executor { + return cr.tryReadID("-u", func(id int) { cr.UID = id }) +} + +func (cr *containerReference) tryReadGID() common.Executor { + return cr.tryReadID("-g", func(id int) { cr.GID = id }) +} + func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, idResp types.IDResponse, user string, workdir string) error { logger := common.Logger(ctx) @@ -670,6 +725,9 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno SrcPrefix: srcPrefix, Handler: &tarCollector{ TarWriter: tw, + UID: cr.UID, + GID: cr.GID, + DstDir: dstPath[1:], }, } @@ -686,7 +744,7 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno if err != nil { return fmt.Errorf("failed to seek tar archive: %w", err) } - err = cr.cli.CopyToContainer(ctx, cr.id, dstPath, tarFile, types.CopyToContainerOptions{}) + err = cr.cli.CopyToContainer(ctx, cr.id, "/", tarFile, types.CopyToContainerOptions{}) if err != nil { return fmt.Errorf("failed to copy content to container: %w", err) } @@ -705,6 +763,8 @@ func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) c Name: file.Name, Mode: file.Mode, Size: int64(len(file.Body)), + Uid: cr.UID, + Gid: cr.GID, } if err := tw.WriteHeader(hdr); err != nil { return err diff --git a/pkg/container/file_collector.go b/pkg/container/file_collector.go index 84821aa..8c8c62b 100644 --- a/pkg/container/file_collector.go +++ b/pkg/container/file_collector.go @@ -23,9 +23,12 @@ type fileCollectorHandler interface { type tarCollector struct { TarWriter *tar.Writer + UID int + GID int + DstDir string } -func (tc tarCollector) WriteFile(path 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 { @@ -33,9 +36,11 @@ func (tc tarCollector) WriteFile(path string, fi fs.FileInfo, linkName string, f } // update the name to correctly reflect the desired destination when untaring - header.Name = path + header.Name = path.Join(tc.DstDir, fpath) header.Mode = int64(fi.Mode()) header.ModTime = fi.ModTime() + header.Uid = tc.UID + header.Gid = tc.GID // write the header if err := tc.TarWriter.WriteHeader(header); err != nil { @@ -138,7 +143,7 @@ func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []strin } } if err == nil && entry.Mode == filemode.Submodule { - err = filepath.Walk(fi.Name(), fc.collectFiles(ctx, split)) + err = fc.Fs.Walk(fi.Name(), fc.collectFiles(ctx, split)) if err != nil { return err } diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 3afc364..acbff9b 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -180,7 +180,6 @@ func (rc *RunContext) startJobContainer() common.Executor { rc.JobContainer.Start(false), rc.JobContainer.UpdateFromImageEnv(&rc.Env), rc.JobContainer.UpdateFromEnv("/etc/environment", &rc.Env), - rc.JobContainer.Exec([]string{"mkdir", "-m", "0777", "-p", ActPath}, rc.Env, "root", ""), rc.JobContainer.Copy(ActPath+"/", &container.FileEntry{ Name: "workflow/event.json", Mode: 0644, diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go index 2129c43..c116bc5 100644 --- a/pkg/runner/runner_test.go +++ b/pkg/runner/runner_test.go @@ -158,6 +158,7 @@ func TestRunEvent(t *testing.T) { {workdir, "container-hostname", "push", "", platforms}, {workdir, "remote-action-docker", "push", "", platforms}, {workdir, "remote-action-js", "push", "", platforms}, + {workdir, "remote-action-js", "push", "", map[string]string{"ubuntu-latest": "ghcr.io/catthehacker/ubuntu:runner-latest"}}, // Test if this works with non root container {workdir, "matrix", "push", "", platforms}, {workdir, "matrix-include-exclude", "push", "", platforms}, {workdir, "commands", "push", "", platforms},