Merge pull request #35721 from dnephin/fix-swagger-spec-errors

Fix codegen problems in swagger spec
diff --git a/api/swagger.yaml b/api/swagger.yaml
index 406b72a..7a5017f 100644
--- a/api/swagger.yaml
+++ b/api/swagger.yaml
@@ -7256,6 +7256,9 @@
               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/api/types/configs.go b/api/types/configs.go
index 20c19f2..54d3e39 100644
--- a/api/types/configs.go
+++ b/api/types/configs.go
@@ -50,6 +50,7 @@
 	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/daemon/exec.go b/daemon/exec.go
index 01670fa..83b7de2 100644
--- a/daemon/exec.go
+++ b/daemon/exec.go
@@ -122,6 +122,7 @@
 	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 @@
 	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 @@
 		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/daemon/exec/exec.go b/daemon/exec/exec.go
index 193d32f..370b403 100644
--- a/daemon/exec/exec.go
+++ b/daemon/exec/exec.go
@@ -31,6 +31,7 @@
 	Tty          bool
 	Privileged   bool
 	User         string
+	WorkingDir   string
 	Env          []string
 	Pid          int
 }
diff --git a/daemon/graphdriver/lcow/lcow.go b/daemon/graphdriver/lcow/lcow.go
index 5ec8b8b..058c69f 100644
--- a/daemon/graphdriver/lcow/lcow.go
+++ b/daemon/graphdriver/lcow/lcow.go
@@ -824,7 +824,7 @@
 		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/distribution/errors.go b/distribution/errors.go
index dd6ff0a..355e9da 100644
--- a/distribution/errors.go
+++ b/distribution/errors.go
@@ -126,21 +126,25 @@
 
 // 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/distribution/errors_test.go b/distribution/errors_test.go
new file mode 100644
index 0000000..aa9ef4f
--- /dev/null
+++ b/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/distribution/pull_v2.go b/distribution/pull_v2.go
index c8d784c..35ff529 100644
--- a/distribution/pull_v2.go
+++ b/distribution/pull_v2.go
@@ -74,7 +74,7 @@
 		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/distribution/push_v2.go b/distribution/push_v2.go
index 7ffce5b..2aecc18 100644
--- a/distribution/push_v2.go
+++ b/distribution/push_v2.go
@@ -67,7 +67,7 @@
 	}
 
 	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/docs/contributing/set-up-dev-env.md b/docs/contributing/set-up-dev-env.md
index acd6888..28bea5b 100644
--- a/docs/contributing/set-up-dev-env.md
+++ b/docs/contributing/set-up-dev-env.md
@@ -10,8 +10,7 @@
 You use the `moby/moby` repository and its `Dockerfile` to create a Docker image,
 run a Docker container, and develop code in the container.
 
-If you followed the procedures that <a href="/opensource/project/set-up-git/" target="_blank">
-set up Git for contributing</a>, you should have a fork of the `moby/moby`
+If you followed the procedures that [set up Git for contributing](./set-up-git.md), you should have a fork of the `moby/moby`
 repository. You also created a branch called `dry-run-test`. In this section,
 you continue working with your fork on this branch.
 
@@ -106,8 +105,7 @@
    ```
 
    If you are following along with this guide, you created a `dry-run-test`
-   branch when you <a href="/opensource/project/set-up-git/" target="_blank">
-   set up Git for contributing</a>.
+   branch when you [set up Git for contributing](./set-up-git.md).
 
 3. Ensure you are on your `dry-run-test` branch.
 
@@ -132,14 +130,14 @@
    ```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 +145,7 @@
 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 +159,7 @@
    `/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 +188,7 @@
 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 +199,13 @@
 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 +294,7 @@
 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/integration/container/exec_test.go b/integration/container/exec_test.go
new file mode 100644
index 0000000..22d7ec0
--- /dev/null
+++ b/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")
+}