-
Notifications
You must be signed in to change notification settings - Fork 69
campaigns: handle non-root volume use cases more gracefully #434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
45cc850
Revert "campaigns: temporary switch back to bind for non-root (#437)"
LawnGnome 29e03a2
RIDICULOUS: Set chown in workspace volume and create temp file
mrnugget 93cf49d
Detect UID and GID in the workspace creator.
LawnGnome 86d00e2
Randomise the dummy file.
LawnGnome d899444
Fix rebase mistake.
LawnGnome 4f18647
Fix warning.
LawnGnome a85a05c
Fix error handling.
LawnGnome ecc6478
Various test updates.
LawnGnome bc5eb0b
Various test updates.
LawnGnome da6b79d
Fix step handling in integration test.
LawnGnome 38ae7bc
Add missing test case.
LawnGnome 3bde896
Fix invalid error.
LawnGnome bf93c94
Add unit tests.
LawnGnome d81cded
Explain logic.
LawnGnome c1b7e32
Simplify per @mrnugget.
LawnGnome 3562380
Fix expect error when the command is not on the PATH.
LawnGnome 9f8b853
Remove lint warnings.
LawnGnome e512c4b
Define and use expect.Success.
LawnGnome File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package docker | ||
|
|
||
| import "sync" | ||
|
|
||
| // ImageCache is a cache of metadata about Docker images, indexed by name. | ||
| type ImageCache struct { | ||
| images map[string]Image | ||
| imagesMu sync.Mutex | ||
| } | ||
|
|
||
| // NewImageCache creates a new image cache. | ||
| func NewImageCache() *ImageCache { | ||
| return &ImageCache{ | ||
| images: make(map[string]Image), | ||
| } | ||
| } | ||
|
|
||
| // Get returns the image cache entry for the given Docker image. The name may be | ||
| // anything the Docker command line will accept as an image name: this will | ||
| // generally be IMAGE or IMAGE:TAG. | ||
| func (ic *ImageCache) Get(name string) Image { | ||
| ic.imagesMu.Lock() | ||
| defer ic.imagesMu.Unlock() | ||
|
|
||
| if image, ok := ic.images[name]; ok { | ||
| return image | ||
| } | ||
|
|
||
| image := &image{name: name} | ||
| ic.images[name] = image | ||
| return image | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package docker | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestImageCache(t *testing.T) { | ||
| cache := NewImageCache() | ||
| if cache == nil { | ||
| t.Error("unexpected nil cache") | ||
| } | ||
|
|
||
| have := cache.Get("foo") | ||
| if have == nil { | ||
| t.Error("unexpected nil error") | ||
| } | ||
| if name := have.(*image).name; name != "foo" { | ||
| t.Errorf("invalid name: have=%q want=%q", name, "foo") | ||
| } | ||
|
|
||
| again := cache.Get("foo") | ||
| if have != again { | ||
| t.Errorf("invalid memoisation: first=%v second=%v", have, again) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| package docker | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
| "sync" | ||
|
|
||
| "github.com/pkg/errors" | ||
|
|
||
| "github.com/sourcegraph/src-cli/internal/exec" | ||
| ) | ||
|
|
||
| // UIDGID represents a UID:GID pair. | ||
| type UIDGID struct { | ||
| UID int | ||
| GID int | ||
| } | ||
|
|
||
| func (ug UIDGID) String() string { | ||
| return fmt.Sprintf("%d:%d", ug.UID, ug.GID) | ||
| } | ||
|
|
||
| // Root is a root:root user. | ||
| var Root = UIDGID{UID: 0, GID: 0} | ||
|
|
||
| // Image represents a Docker image, hopefully stored in the local cache. | ||
| type Image interface { | ||
| Digest(context.Context) (string, error) | ||
| Ensure(context.Context) error | ||
| UIDGID(context.Context) (UIDGID, error) | ||
| } | ||
|
|
||
| type image struct { | ||
| name string | ||
|
|
||
| // There are lots of once fields below: basically, we're going to try fairly | ||
| // hard to prevent performing the same operations on the same image over and | ||
| // over, since some of them are expensive. | ||
|
|
||
| digest string | ||
| digestErr error | ||
| digestOnce sync.Once | ||
|
|
||
| ensureErr error | ||
| ensureOnce sync.Once | ||
|
|
||
| uidGid UIDGID | ||
| uidGidErr error | ||
| uidGidOnce sync.Once | ||
| } | ||
|
|
||
| // Digest gets and returns the content digest for the image. Note that this is | ||
| // different from the "distribution digest" (which is what you can use to | ||
| // specify an image to `docker run`, as in `my/image@sha256:xxx`). We need to | ||
| // use the content digest because the distribution digest is only computed for | ||
| // images that have been pulled from or pushed to a registry. See | ||
| // https://windsock.io/explaining-docker-image-ids/ under "A Final Twist" for a | ||
| // good explanation. | ||
| func (image *image) Digest(ctx context.Context) (string, error) { | ||
| image.digestOnce.Do(func() { | ||
| image.digest, image.digestErr = func() (string, error) { | ||
| if err := image.Ensure(ctx); err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| // TODO!(sqs): is image id the right thing to use here? it is NOT | ||
| // the digest. but the digest is not calculated for all images | ||
| // (unless they are pulled/pushed from/to a registry), see | ||
| // https://github.com/moby/moby/issues/32016. | ||
| out, err := exec.CommandContext(ctx, "docker", "image", "inspect", "--format", "{{.Id}}", "--", image.name).CombinedOutput() | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "inspecting docker image: %s", string(bytes.TrimSpace(out))) | ||
| } | ||
| id := string(bytes.TrimSpace(out)) | ||
| if id == "" { | ||
| return "", errors.Errorf("unexpected empty docker image content ID for %q", image.name) | ||
| } | ||
| return id, nil | ||
| }() | ||
| }) | ||
|
|
||
| return image.digest, image.digestErr | ||
| } | ||
|
|
||
| // Ensure ensures that the image has been pulled by Docker. Note that it does | ||
| // not attempt to pull a newer version of the image if it exists locally. | ||
| func (image *image) Ensure(ctx context.Context) error { | ||
| image.ensureOnce.Do(func() { | ||
| image.ensureErr = func() error { | ||
| // docker image inspect will return a non-zero exit code if the image and | ||
| // tag don't exist locally, regardless of the format. | ||
| if err := exec.CommandContext(ctx, "docker", "image", "inspect", "--format", "1", image.name).Run(); err != nil { | ||
| // Let's try pulling the image. | ||
| if err := exec.CommandContext(ctx, "docker", "image", "pull", image.name).Run(); err != nil { | ||
| return errors.Wrap(err, "pulling image") | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| }() | ||
| }) | ||
|
|
||
| return image.ensureErr | ||
| } | ||
|
|
||
| // UIDGID returns the user and group the container is configured to run as. | ||
| func (image *image) UIDGID(ctx context.Context) (UIDGID, error) { | ||
| image.uidGidOnce.Do(func() { | ||
| image.uidGid, image.uidGidErr = func() (UIDGID, error) { | ||
| stdout := new(bytes.Buffer) | ||
|
|
||
| // Digest also implicitly means Ensure has been called. | ||
| digest, err := image.Digest(ctx) | ||
| if err != nil { | ||
| return UIDGID{}, errors.Wrap(err, "getting digest") | ||
| } | ||
|
|
||
| args := []string{ | ||
| "run", | ||
| "--rm", | ||
| "--entrypoint", "/bin/sh", | ||
| digest, | ||
| "-c", "id -u; id -g", | ||
| } | ||
| cmd := exec.CommandContext(ctx, "docker", args...) | ||
| cmd.Stdout = stdout | ||
|
|
||
| if err := cmd.Run(); err != nil { | ||
| return UIDGID{}, errors.Wrap(err, "running id") | ||
| } | ||
|
|
||
| // POSIX specifies the output of `id -u` as the effective UID, | ||
| // terminated by a newline. `id -g` is the same, just for the GID. | ||
| raw := strings.TrimSpace(stdout.String()) | ||
| var res UIDGID | ||
| _, err = fmt.Sscanf(raw, "%d\n%d", &res.UID, &res.GID) | ||
| if err != nil { | ||
| return res, errors.Wrapf(err, "malformed uid/gid: %q", raw) | ||
| } | ||
| return res, nil | ||
| }() | ||
| }) | ||
|
|
||
| return image.uidGid, image.uidGidErr | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.