diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index e2c8770117..5f78eda682 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -52,6 +52,7 @@ RUN apt-get update && apt-get install -y \ libapparmor-dev \ libcap-dev \ libdevmapper-dev \ + libnet-dev \ libnl-3-dev \ libprotobuf-c0-dev \ libprotobuf-dev \ @@ -94,11 +95,9 @@ ENV PATH /go/bin:/usr/local/go/bin:$PATH ENV GOPATH /go # Install CRIU for checkpoint/restore support -ENV CRIU_VERSION 2.12.1 -# Install dependancy packages specific to criu -RUN apt-get install libnet-dev -y && \ - mkdir -p /usr/src/criu \ - && curl -sSL https://github.com/xemul/criu/archive/v${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1 \ +ENV CRIU_VERSION 3.6 +RUN mkdir -p /usr/src/criu \ + && curl -sSL https://github.com/checkpoint-restore/criu/archive/v${CRIU_VERSION}.tar.gz | tar -C /usr/src/criu/ -xz --strip-components=1 \ && cd /usr/src/criu \ && make \ && make install-criu diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index 96386e0599..b7ed3b8731 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -7259,6 +7259,9 @@ paths: User: type: "string" description: "The user, and optionally, group to run the exec process inside the container. Format is one of: `user`, `user:group`, `uid`, or `uid:gid`." + WorkingDir: + type: "string" + description: "The working directory for the exec process inside the container." example: AttachStdin: false AttachStdout: true diff --git a/components/engine/api/types/configs.go b/components/engine/api/types/configs.go index 20c19f2132..54d3e39fb9 100644 --- a/components/engine/api/types/configs.go +++ b/components/engine/api/types/configs.go @@ -50,6 +50,7 @@ type ExecConfig struct { Detach bool // Execute in detach mode DetachKeys string // Escape keys for detach Env []string // Environment variables + WorkingDir string // Working directory Cmd []string // Execution commands and args } diff --git a/components/engine/daemon/exec.go b/components/engine/daemon/exec.go index 01670faa5d..83b7de2255 100644 --- a/components/engine/daemon/exec.go +++ b/components/engine/daemon/exec.go @@ -122,6 +122,7 @@ func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (str execConfig.Tty = config.Tty execConfig.Privileged = config.Privileged execConfig.User = config.User + execConfig.WorkingDir = config.WorkingDir linkedEnv, err := d.setupLinkedContainers(cntr) if err != nil { @@ -131,6 +132,9 @@ func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (str if len(execConfig.User) == 0 { execConfig.User = cntr.Config.User } + if len(execConfig.WorkingDir) == 0 { + execConfig.WorkingDir = cntr.Config.WorkingDir + } d.registerExecCommand(cntr, execConfig) @@ -211,7 +215,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R Args: append([]string{ec.Entrypoint}, ec.Args...), Env: ec.Env, Terminal: ec.Tty, - Cwd: c.Config.WorkingDir, + Cwd: ec.WorkingDir, } if p.Cwd == "" { p.Cwd = "/" diff --git a/components/engine/daemon/exec/exec.go b/components/engine/daemon/exec/exec.go index 193d32f022..370b4032c7 100644 --- a/components/engine/daemon/exec/exec.go +++ b/components/engine/daemon/exec/exec.go @@ -31,6 +31,7 @@ type Config struct { Tty bool Privileged bool User string + WorkingDir string Env []string Pid int } diff --git a/components/engine/daemon/graphdriver/lcow/lcow.go b/components/engine/daemon/graphdriver/lcow/lcow.go index 5ec8b8baaa..058c69fd97 100644 --- a/components/engine/daemon/graphdriver/lcow/lcow.go +++ b/components/engine/daemon/graphdriver/lcow/lcow.go @@ -824,7 +824,7 @@ func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) { return 0, fmt.Errorf("lcowdriver: applydiff: svm failed to boot: %s", err) } - // TODO @jhowardmsft - the retries are temporary to overcome platform reliablity issues. + // TODO @jhowardmsft - the retries are temporary to overcome platform reliability issues. // Obviously this will be removed as platform bugs are fixed. retries := 0 for { diff --git a/components/engine/distribution/errors.go b/components/engine/distribution/errors.go index dd6ff0a9aa..355e9da1bd 100644 --- a/components/engine/distribution/errors.go +++ b/components/engine/distribution/errors.go @@ -126,21 +126,25 @@ func TranslatePullError(err error, ref reference.Named) error { // continueOnError returns true if we should fallback to the next endpoint // as a result of this error. -func continueOnError(err error) bool { +func continueOnError(err error, mirrorEndpoint bool) bool { switch v := err.(type) { case errcode.Errors: if len(v) == 0 { return true } - return continueOnError(v[0]) + return continueOnError(v[0], mirrorEndpoint) case ErrNoSupport: - return continueOnError(v.Err) + return continueOnError(v.Err, mirrorEndpoint) case errcode.Error: - return shouldV2Fallback(v) + return mirrorEndpoint || shouldV2Fallback(v) case *client.UnexpectedHTTPResponseError: return true case ImageConfigPullError: - return false + // ImageConfigPullError only happens with v2 images, v1 fallback is + // unnecessary. + // Failures from a mirror endpoint should result in fallback to the + // canonical repo. + return mirrorEndpoint case error: return !strings.Contains(err.Error(), strings.ToLower(syscall.ESRCH.Error())) } diff --git a/components/engine/distribution/errors_test.go b/components/engine/distribution/errors_test.go new file mode 100644 index 0000000000..aa9ef4f424 --- /dev/null +++ b/components/engine/distribution/errors_test.go @@ -0,0 +1,85 @@ +package distribution + +import ( + "errors" + "strings" + "syscall" + "testing" + + "github.com/docker/distribution/registry/api/errcode" + "github.com/docker/distribution/registry/api/v2" + "github.com/docker/distribution/registry/client" +) + +var alwaysContinue = []error{ + &client.UnexpectedHTTPResponseError{}, + + // Some errcode.Errors that don't disprove the existence of a V1 image + errcode.Error{Code: errcode.ErrorCodeUnauthorized}, + errcode.Error{Code: v2.ErrorCodeManifestUnknown}, + errcode.Error{Code: v2.ErrorCodeNameUnknown}, + + errors.New("some totally unexpected error"), +} + +var continueFromMirrorEndpoint = []error{ + ImageConfigPullError{}, + + // Some other errcode.Error that doesn't indicate we should search for a V1 image. + errcode.Error{Code: errcode.ErrorCodeTooManyRequests}, +} + +var neverContinue = []error{ + errors.New(strings.ToLower(syscall.ESRCH.Error())), // No such process +} + +func TestContinueOnError_NonMirrorEndpoint(t *testing.T) { + for _, err := range alwaysContinue { + if !continueOnError(err, false) { + t.Errorf("Should continue from non-mirror endpoint: %T: '%s'", err, err.Error()) + } + } + + for _, err := range continueFromMirrorEndpoint { + if continueOnError(err, false) { + t.Errorf("Should only continue from mirror endpoint: %T: '%s'", err, err.Error()) + } + } +} + +func TestContinueOnError_MirrorEndpoint(t *testing.T) { + errs := []error{} + errs = append(errs, alwaysContinue...) + errs = append(errs, continueFromMirrorEndpoint...) + for _, err := range errs { + if !continueOnError(err, true) { + t.Errorf("Should continue from mirror endpoint: %T: '%s'", err, err.Error()) + } + } +} + +func TestContinueOnError_NeverContinue(t *testing.T) { + for _, isMirrorEndpoint := range []bool{true, false} { + for _, err := range neverContinue { + if continueOnError(err, isMirrorEndpoint) { + t.Errorf("Should never continue: %T: '%s'", err, err.Error()) + } + } + } +} + +func TestContinueOnError_UnnestsErrors(t *testing.T) { + // ContinueOnError should evaluate nested errcode.Errors. + + // Assumes that v2.ErrorCodeNameUnknown is a continueable error code. + err := errcode.Errors{errcode.Error{Code: v2.ErrorCodeNameUnknown}} + if !continueOnError(err, false) { + t.Fatal("ContinueOnError should unnest, base return value on errcode.Errors") + } + + // Assumes that errcode.ErrorCodeTooManyRequests is not a V1-fallback indication + err = errcode.Errors{errcode.Error{Code: errcode.ErrorCodeTooManyRequests}} + if continueOnError(err, false) { + t.Fatal("ContinueOnError should unnest, base return value on errcode.Errors") + } +} diff --git a/components/engine/distribution/pull_v2.go b/components/engine/distribution/pull_v2.go index c8d784ca63..35ff529b4f 100644 --- a/components/engine/distribution/pull_v2.go +++ b/components/engine/distribution/pull_v2.go @@ -74,7 +74,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform strin if _, ok := err.(fallbackError); ok { return err } - if continueOnError(err) { + if continueOnError(err, p.endpoint.Mirror) { return fallbackError{ err: err, confirmedV2: p.confirmedV2, diff --git a/components/engine/distribution/push_v2.go b/components/engine/distribution/push_v2.go index 7ffce5b2af..2aecc183b0 100644 --- a/components/engine/distribution/push_v2.go +++ b/components/engine/distribution/push_v2.go @@ -67,7 +67,7 @@ func (p *v2Pusher) Push(ctx context.Context) (err error) { } if err = p.pushV2Repository(ctx); err != nil { - if continueOnError(err) { + if continueOnError(err, p.endpoint.Mirror) { return fallbackError{ err: err, confirmedV2: p.pushState.confirmedV2, diff --git a/components/engine/docs/contributing/set-up-dev-env.md b/components/engine/docs/contributing/set-up-dev-env.md index acd6888cd4..b28b8fb677 100644 --- a/components/engine/docs/contributing/set-up-dev-env.md +++ b/components/engine/docs/contributing/set-up-dev-env.md @@ -132,14 +132,14 @@ can take over 15 minutes to complete. ```none Successfully built 3d872560918e docker run --rm -i --privileged -e BUILDFLAGS -e KEEPBUNDLE -e DOCKER_BUILD_GOGC -e DOCKER_BUILD_PKGS -e DOCKER_CLIENTONLY -e DOCKER_DEBUG -e DOCKER_EXPERIMENTAL -e DOCKER_GITCOMMIT -e DOCKER_GRAPHDRIVER=devicemapper -e DOCKER_INCREMENTAL_BINARY -e DOCKER_REMAP_ROOT -e DOCKER_STORAGE_OPTS -e DOCKER_USERLANDPROXY -e TESTDIRS -e TESTFLAGS -e TIMEOUT -v "home/ubuntu/repos/docker/bundles:/go/src/github.com/moby/moby/bundles" -t "docker-dev:dry-run-test" bash - root@f31fa223770f:/go/src/github.com/moby/moby# + root@f31fa223770f:/go/src/github.com/docker/docker# ``` At this point, your prompt reflects the container's BASH shell. 5. List the contents of the current directory (`/go/src/github.com/moby/moby`). - You should see the image's source from the `/go/src/github.com/moby/moby` + You should see the image's source from the `/go/src/github.com/docker/docker` directory. ![List example](images/list_example.png) @@ -147,7 +147,7 @@ can take over 15 minutes to complete. 6. Make a `dockerd` binary. ```none - root@a8b2885ab900:/go/src/github.com/moby/moby# hack/make.sh binary + root@a8b2885ab900:/go/src/github.com/docker/docker# hack/make.sh binary Removing bundles/ ---> Making bundle: binary (in bundles/binary) @@ -161,7 +161,7 @@ can take over 15 minutes to complete. `/usr/local/bin/` directory. ```none - root@a8b2885ab900:/go/src/github.com/moby/moby# make install + root@a8b2885ab900:/go/src/github.com/docker/docker# make install ``` 8. Start the Engine daemon running in the background. @@ -190,7 +190,7 @@ can take over 15 minutes to complete. 9. Inside your container, check your Docker version. ```none - root@5f8630b873fe:/go/src/github.com/moby/moby# docker --version + root@5f8630b873fe:/go/src/github.com/docker/docker# docker --version Docker version 1.12.0-dev, build 6e728fb ``` @@ -201,13 +201,13 @@ can take over 15 minutes to complete. 10. Run the `hello-world` image. ```none - root@5f8630b873fe:/go/src/github.com/moby/moby# docker run hello-world + root@5f8630b873fe:/go/src/github.com/docker/docker# docker run hello-world ``` 11. List the image you just downloaded. ```none - root@5f8630b873fe:/go/src/github.com/moby/moby# docker images + root@5f8630b873fe:/go/src/github.com/docker/docker# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest c54a2cc56cbb 3 months ago 1.85 kB ``` @@ -296,7 +296,7 @@ example, you'll edit the help for the `attach` subcommand. 10. To view your change, run the `dockerd --help` command in the docker development container shell. ```bash - root@b0cb4f22715d:/go/src/github.com/moby/moby# dockerd --help + root@b0cb4f22715d:/go/src/github.com/docker/docker# dockerd --help Usage: dockerd COMMAND diff --git a/components/engine/integration/container/exec_test.go b/components/engine/integration/container/exec_test.go new file mode 100644 index 0000000000..22d7ec01cc --- /dev/null +++ b/components/engine/integration/container/exec_test.go @@ -0,0 +1,60 @@ +package container + +import ( + "context" + "io/ioutil" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/integration/util/request" + "github.com/stretchr/testify/require" +) + +func TestExec(t *testing.T) { + defer setupTest(t)() + ctx := context.Background() + client := request.NewAPIClient(t) + + container, err := client.ContainerCreate(ctx, + &container.Config{ + Image: "busybox", + Tty: true, + WorkingDir: "/root", + Cmd: strslice.StrSlice([]string{"top"}), + }, + &container.HostConfig{}, + &network.NetworkingConfig{}, + "foo", + ) + require.NoError(t, err) + err = client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}) + require.NoError(t, err) + + id, err := client.ContainerExecCreate(ctx, container.ID, + types.ExecConfig{ + WorkingDir: "/tmp", + Env: strslice.StrSlice([]string{"FOO=BAR"}), + AttachStdout: true, + Cmd: strslice.StrSlice([]string{"sh", "-c", "env"}), + }, + ) + require.NoError(t, err) + + resp, err := client.ContainerExecAttach(ctx, id.ID, + types.ExecStartCheck{ + Detach: false, + Tty: false, + }, + ) + require.NoError(t, err) + defer resp.Close() + r, err := ioutil.ReadAll(resp.Reader) + require.NoError(t, err) + out := string(r) + require.NoError(t, err) + require.Contains(t, out, "PWD=/tmp", "exec command not running in expected /tmp working directory") + require.Contains(t, out, "FOO=BAR", "exec command not running with expected environment variable FOO") +}