Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions internal/campaigns/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
Expand Down
37 changes: 35 additions & 2 deletions internal/campaigns/volume_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package campaigns
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"

Expand All @@ -13,7 +14,9 @@ import (
"github.com/sourcegraph/src-cli/internal/version"
)

type dockerVolumeWorkspaceCreator struct{}
type dockerVolumeWorkspaceCreator struct {
uid int
}

var _ WorkspaceCreator = &dockerVolumeWorkspaceCreator{}

Expand Down Expand Up @@ -58,14 +61,34 @@ 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")
if err != nil {
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",
Expand All @@ -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
}

Expand Down
20 changes: 13 additions & 7 deletions internal/campaigns/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
//
Expand Down Expand Up @@ -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,
Expand All @@ -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
}