diff --git a/internal/campaigns/service.go b/internal/campaigns/service.go index 68fc0d0b65..4f6f3ed7d3 100644 --- a/internal/campaigns/service.go +++ b/internal/campaigns/service.go @@ -213,16 +213,18 @@ func (svc *Service) NewRepoFetcher(dir string, cleanArchives bool) RepoFetcher { func (svc *Service) NewWorkspaceCreator(ctx context.Context, dir string, steps []Step) WorkspaceCreator { var workspace workspaceCreatorType + var workspaceCreatorVolumeUid int + if svc.workspace == "volume" { workspace = workspaceCreatorVolume } else if svc.workspace == "bind" { workspace = workspaceCreatorBind } else { - workspace = bestWorkspaceCreator(ctx, steps) + workspace, workspaceCreatorVolumeUid = bestWorkspaceCreator(ctx, steps) } if workspace == workspaceCreatorVolume { - return &dockerVolumeWorkspaceCreator{} + return &dockerVolumeWorkspaceCreator{uid: workspaceCreatorVolumeUid} } return &dockerBindWorkspaceCreator{dir: dir} } diff --git a/internal/campaigns/volume_workspace.go b/internal/campaigns/volume_workspace.go index bd6b5f1a6e..3695278070 100644 --- a/internal/campaigns/volume_workspace.go +++ b/internal/campaigns/volume_workspace.go @@ -3,6 +3,7 @@ package campaigns import ( "bytes" "context" + "fmt" "io/ioutil" "os" @@ -13,7 +14,9 @@ import ( "github.com/sourcegraph/src-cli/internal/version" ) -type dockerVolumeWorkspaceCreator struct{} +type dockerVolumeWorkspaceCreator struct { + uid int +} var _ WorkspaceCreator = &dockerVolumeWorkspaceCreator{} @@ -58,7 +61,7 @@ git commit --quiet --all --allow-empty -m src-action-exec return nil } -func (*dockerVolumeWorkspaceCreator) unzipRepoIntoVolume(ctx context.Context, w *dockerVolumeWorkspace, zip string) error { +func (c *dockerVolumeWorkspaceCreator) unzipRepoIntoVolume(ctx context.Context, w *dockerVolumeWorkspace, zip string) error { // We want to mount that temporary file into a Docker container that has the // workspace volume attached, and unzip it into the volume. common, err := w.DockerRunOpts(ctx, "/work") @@ -66,6 +69,26 @@ func (*dockerVolumeWorkspaceCreator) unzipRepoIntoVolume(ctx context.Context, w return errors.Wrap(err, "generating run options") } + if c.uid != 0 { + owner := fmt.Sprintf("%d:%d", c.uid, c.uid) + + opts := append([]string{"run", "--rm", "--init"}, common...) + opts = append(opts, dockerVolumeWorkspaceImage, "chown", "-R", owner, "/work") + + if out, err := exec.CommandContext(ctx, "docker", opts...).CombinedOutput(); err != nil { + return errors.Wrapf(err, "chown output:\n\n%s\n\n", string(out)) + } + + // LOL/FML: This seems to "fix" the problem of `chown` getting lost between + // the `chown` call above and the unzip below? + opts = append([]string{"run", "--rm", "--init"}, common...) + opts = append(opts, dockerVolumeWorkspaceImage, "touch", "/work/DELETE_ME") + + if out, err := exec.CommandContext(ctx, "docker", opts...).CombinedOutput(); err != nil { + return errors.Wrapf(err, "chown output:\n\n%s\n\n", string(out)) + } + } + opts := append([]string{ "run", "--rm", @@ -79,6 +102,16 @@ func (*dockerVolumeWorkspaceCreator) unzipRepoIntoVolume(ctx context.Context, w return errors.Wrapf(err, "unzip output:\n\n%s\n\n", string(out)) } + // LOL/FML: delete stupid file + if c.uid != 0 { + opts = append([]string{"run", "--rm", "--init"}, common...) + opts = append(opts, dockerVolumeWorkspaceImage, "rm", "-rf", "/work/DELETE_ME") + + if out, err := exec.CommandContext(ctx, "docker", opts...).CombinedOutput(); err != nil { + return errors.Wrapf(err, "chown output:\n\n%s\n\n", string(out)) + } + } + return nil } diff --git a/internal/campaigns/workspace.go b/internal/campaigns/workspace.go index b0b44b3a89..9d694352bd 100644 --- a/internal/campaigns/workspace.go +++ b/internal/campaigns/workspace.go @@ -55,7 +55,7 @@ const ( // bestWorkspaceCreator determines the correct workspace creator to use based on // the environment and campaign to be executed. -func bestWorkspaceCreator(ctx context.Context, steps []Step) workspaceCreatorType { +func bestWorkspaceCreator(ctx context.Context, steps []Step) (workspaceCreatorType, int) { // The basic theory here is that we have two options: bind and volume. Bind // is battle tested and always safe, but can be slow on non-Linux platforms // because bind mounts are slow. Volume is faster on those platforms, but @@ -67,13 +67,13 @@ func bestWorkspaceCreator(ctx context.Context, steps []Step) workspaceCreatorTyp // For the time being, we're only going to consider volume mode on Intel // macOS. if runtime.GOOS != "darwin" || runtime.GOARCH != "amd64" { - return workspaceCreatorBind + return workspaceCreatorBind, 0 } return detectBestWorkspaceCreator(ctx, steps) } -func detectBestWorkspaceCreator(ctx context.Context, steps []Step) workspaceCreatorType { +func detectBestWorkspaceCreator(ctx context.Context, steps []Step) (workspaceCreatorType, int) { // OK, so we're interested in volume mode, but we need to take its // shortcomings around mixed user environments into account. // @@ -109,7 +109,7 @@ func detectBestWorkspaceCreator(ctx context.Context, steps []Step) workspaceCrea // An error here likely indicates that `id` isn't available on the // path. That's OK: let's not make any assumptions at this point // about the image, and we'll default to the always safe option. - return workspaceCreatorBind + return workspaceCreatorBind, 0 } // POSIX specifies the output of `id -u` as the effective UID, @@ -124,14 +124,20 @@ func detectBestWorkspaceCreator(ctx context.Context, steps []Step) workspaceCrea // // TODO: when logging is available at this level, we should log an // error at verbose level to make this easier to debug. - return workspaceCreatorBind + return workspaceCreatorBind, 0 } uids[uid] = struct{}{} if len(uids) > 1 { - return workspaceCreatorBind + return workspaceCreatorBind, 0 } } - return workspaceCreatorVolume + var uid int + for k := range uids { + uid = k + break // fml + } + + return workspaceCreatorVolume, uid }