From a9dda5c3748f22dfc71f1eee2fcc918c567a6b30 Mon Sep 17 00:00:00 2001 From: Marcus Martins Date: Tue, 23 May 2017 21:45:38 -0700 Subject: [PATCH 1/5] Handle a Docker daemon without registry info The current implementation of the ElectAuthServer doesn't handle well when the default Registry server is not included in the response from the daemon Info endpoint. That leads to the storage and usage of the credentials for the default registry (`https://index.docker.io/v1/`) under an empty string on the client config file. Sample config file after a login via a Docker Daemon without Registry information: ```json { "auths": { "": { "auth": "***" } } } ``` That can lead to duplication of the password for the default registry and authentication failures against the default registry if a pull/push is performed without first authenticating via the misbehaving daemon. Also, changes the output of the warning message from stdout to sdterr as per dnephin suggestion. Signed-off-by: Marcus Martins Upstream-commit: 862649707ef2b901fc0535895de7456fc4c9c0c3 Component: cli --- components/cli/cli/command/registry.go | 4 +- components/cli/cli/command/registry_test.go | 79 +++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 components/cli/cli/command/registry_test.go diff --git a/components/cli/cli/command/registry.go b/components/cli/cli/command/registry.go index 884fa6ec40..802b3a4b83 100644 --- a/components/cli/cli/command/registry.go +++ b/components/cli/cli/command/registry.go @@ -28,7 +28,9 @@ func ElectAuthServer(ctx context.Context, cli Cli) string { // the default registry URL might be Windows specific. serverAddress := registry.IndexServer if info, err := cli.Client().Info(ctx); err != nil { - fmt.Fprintf(cli.Out(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress) + fmt.Fprintf(cli.Err(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress) + } else if info.IndexServerAddress == "" { + fmt.Fprintf(cli.Err(), "Warning: Empty registry endpoint from daemon. Using system default: %s\n", serverAddress) } else { serverAddress = info.IndexServerAddress } diff --git a/components/cli/cli/command/registry_test.go b/components/cli/cli/command/registry_test.go new file mode 100644 index 0000000000..f6adeb49f4 --- /dev/null +++ b/components/cli/cli/command/registry_test.go @@ -0,0 +1,79 @@ +package command_test + +import ( + "bytes" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" + + // Prevents a circular import with "github.com/docker/cli/cli/internal/test" + . "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/internal/test" + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" +) + +type fakeClient struct { + client.Client + infoFunc func() (types.Info, error) +} + +func (cli *fakeClient) Info(_ context.Context) (types.Info, error) { + if cli.infoFunc != nil { + return cli.infoFunc() + } + return types.Info{}, nil +} + +func TestElectAuthServer(t *testing.T) { + testCases := []struct { + expectedAuthServer string + expectedWarning string + infoFunc func() (types.Info, error) + }{ + { + expectedAuthServer: "https://index.docker.io/v1/", + expectedWarning: "", + infoFunc: func() (types.Info, error) { + return types.Info{IndexServerAddress: "https://index.docker.io/v1/"}, nil + }, + }, + { + expectedAuthServer: "https://index.docker.io/v1/", + expectedWarning: "Empty registry endpoint from daemon", + infoFunc: func() (types.Info, error) { + return types.Info{IndexServerAddress: ""}, nil + }, + }, + { + expectedAuthServer: "https://foo.bar", + expectedWarning: "", + infoFunc: func() (types.Info, error) { + return types.Info{IndexServerAddress: "https://foo.bar"}, nil + }, + }, + { + expectedAuthServer: "https://index.docker.io/v1/", + expectedWarning: "failed to get default registry endpoint from daemon", + infoFunc: func() (types.Info, error) { + return types.Info{}, errors.Errorf("error getting info") + }, + }, + } + for _, tc := range testCases { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{infoFunc: tc.infoFunc}, buf) + errBuf := new(bytes.Buffer) + cli.SetErr(errBuf) + server := ElectAuthServer(context.Background(), cli) + assert.Equal(t, tc.expectedAuthServer, server) + actual := errBuf.String() + if tc.expectedWarning == "" { + assert.Empty(t, actual) + } else { + assert.Contains(t, actual, tc.expectedWarning) + } + } +} From 8870137e27b02ca64a2979644284e021f2102312 Mon Sep 17 00:00:00 2001 From: John Stephens Date: Fri, 26 May 2017 17:30:33 -0700 Subject: [PATCH 2/5] Remove stack configs on stack removal Signed-off-by: John Stephens Upstream-commit: f05cd11ee234804d85b3aadae0777610809530a4 Component: cli --- .../cli/cli/command/stack/client_test.go | 37 +++++++++++++++++++ components/cli/cli/command/stack/common.go | 10 +++++ components/cli/cli/command/stack/remove.go | 23 +++++++++++- .../cli/cli/command/stack/remove_test.go | 19 ++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/components/cli/cli/command/stack/client_test.go b/components/cli/cli/command/stack/client_test.go index 5b51575580..d4c373ce12 100644 --- a/components/cli/cli/command/stack/client_test.go +++ b/components/cli/cli/command/stack/client_test.go @@ -17,17 +17,21 @@ type fakeClient struct { services []string networks []string secrets []string + configs []string removedServices []string removedNetworks []string removedSecrets []string + removedConfigs []string serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error) networkListFunc func(options types.NetworkListOptions) ([]types.NetworkResource, error) secretListFunc func(options types.SecretListOptions) ([]swarm.Secret, error) + configListFunc func(options types.ConfigListOptions) ([]swarm.Config, error) serviceRemoveFunc func(serviceID string) error networkRemoveFunc func(networkID string) error secretRemoveFunc func(secretID string) error + configRemoveFunc func(configID string) error } func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { @@ -75,6 +79,21 @@ func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListO return secretsList, nil } +func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { + if cli.configListFunc != nil { + return cli.configListFunc(options) + } + + namespace := namespaceFromFilters(options.Filters) + configsList := []swarm.Config{} + for _, name := range cli.configs { + if belongToNamespace(name, namespace) { + configsList = append(configsList, configFromName(name)) + } + } + return configsList, nil +} + func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error { if cli.serviceRemoveFunc != nil { return cli.serviceRemoveFunc(serviceID) @@ -102,6 +121,15 @@ func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error return nil } +func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error { + if cli.configRemoveFunc != nil { + return cli.configRemoveFunc(configID) + } + + cli.removedConfigs = append(cli.removedConfigs, configID) + return nil +} + func serviceFromName(name string) swarm.Service { return swarm.Service{ ID: "ID-" + name, @@ -127,6 +155,15 @@ func secretFromName(name string) swarm.Secret { } } +func configFromName(name string) swarm.Config { + return swarm.Config{ + ID: "ID-" + name, + Spec: swarm.ConfigSpec{ + Annotations: swarm.Annotations{Name: name}, + }, + } +} + func namespaceFromFilters(filters filters.Args) string { label := filters.Get("label")[0] return strings.TrimPrefix(label, convert.LabelNamespace+"=") diff --git a/components/cli/cli/command/stack/common.go b/components/cli/cli/command/stack/common.go index 27d664bdc9..d9f921af56 100644 --- a/components/cli/cli/command/stack/common.go +++ b/components/cli/cli/command/stack/common.go @@ -61,3 +61,13 @@ func getStackSecrets( ctx, types.SecretListOptions{Filters: getStackFilter(namespace)}) } + +func getStackConfigs( + ctx context.Context, + apiclient client.APIClient, + namespace string, +) ([]swarm.Config, error) { + return apiclient.ConfigList( + ctx, + types.ConfigListOptions{Filters: getStackFilter(namespace)}) +} diff --git a/components/cli/cli/command/stack/remove.go b/components/cli/cli/command/stack/remove.go index 27258850d2..09431dad70 100644 --- a/components/cli/cli/command/stack/remove.go +++ b/components/cli/cli/command/stack/remove.go @@ -55,13 +55,19 @@ func runRemove(dockerCli command.Cli, opts removeOptions) error { return err } - if len(services)+len(networks)+len(secrets) == 0 { + configs, err := getStackConfigs(ctx, client, namespace) + if err != nil { + return err + } + + if len(services)+len(networks)+len(secrets)+len(configs) == 0 { fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace) continue } hasError := removeServices(ctx, dockerCli, services) hasError = removeSecrets(ctx, dockerCli, secrets) || hasError + hasError = removeConfigs(ctx, dockerCli, configs) || hasError hasError = removeNetworks(ctx, dockerCli, networks) || hasError if hasError { @@ -119,3 +125,18 @@ func removeSecrets( } return err != nil } + +func removeConfigs( + ctx context.Context, + dockerCli command.Cli, + configs []swarm.Config, +) bool { + var err error + for _, config := range configs { + fmt.Fprintf(dockerCli.Err(), "Removing config %s\n", config.Spec.Name) + if err = dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil { + fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err) + } + } + return err != nil +} diff --git a/components/cli/cli/command/stack/remove_test.go b/components/cli/cli/command/stack/remove_test.go index a2be8c5f4c..d6efb90106 100644 --- a/components/cli/cli/command/stack/remove_test.go +++ b/components/cli/cli/command/stack/remove_test.go @@ -32,10 +32,18 @@ func TestRemoveStack(t *testing.T) { } allSecretIDs := buildObjectIDs(allSecrets) + allConfigs := []string{ + objectName("foo", "config1"), + objectName("foo", "config2"), + objectName("bar", "config1"), + } + allConfigIDs := buildObjectIDs(allConfigs) + cli := &fakeClient{ services: allServices, networks: allNetworks, secrets: allSecrets, + configs: allConfigs, } cmd := newRemoveCommand(test.NewFakeCli(cli, &bytes.Buffer{})) cmd.SetArgs([]string{"foo", "bar"}) @@ -44,6 +52,7 @@ func TestRemoveStack(t *testing.T) { assert.Equal(t, allServiceIDs, cli.removedServices) assert.Equal(t, allNetworkIDs, cli.removedNetworks) assert.Equal(t, allSecretIDs, cli.removedSecrets) + assert.Equal(t, allConfigIDs, cli.removedConfigs) } func TestSkipEmptyStack(t *testing.T) { @@ -57,10 +66,14 @@ func TestSkipEmptyStack(t *testing.T) { allSecrets := []string{objectName("bar", "secret1")} allSecretIDs := buildObjectIDs(allSecrets) + allConfigs := []string{objectName("bar", "config1")} + allConfigIDs := buildObjectIDs(allConfigs) + cli := &fakeClient{ services: allServices, networks: allNetworks, secrets: allSecrets, + configs: allConfigs, } cmd := newRemoveCommand(test.NewFakeCli(cli, buf)) cmd.SetArgs([]string{"foo", "bar"}) @@ -70,6 +83,7 @@ func TestSkipEmptyStack(t *testing.T) { assert.Equal(t, allServiceIDs, cli.removedServices) assert.Equal(t, allNetworkIDs, cli.removedNetworks) assert.Equal(t, allSecretIDs, cli.removedSecrets) + assert.Equal(t, allConfigIDs, cli.removedConfigs) } func TestContinueAfterError(t *testing.T) { @@ -82,11 +96,15 @@ func TestContinueAfterError(t *testing.T) { allSecrets := []string{objectName("foo", "secret1"), objectName("bar", "secret1")} allSecretIDs := buildObjectIDs(allSecrets) + allConfigs := []string{objectName("foo", "config1"), objectName("bar", "config1")} + allConfigIDs := buildObjectIDs(allConfigs) + removedServices := []string{} cli := &fakeClient{ services: allServices, networks: allNetworks, secrets: allSecrets, + configs: allConfigs, serviceRemoveFunc: func(serviceID string) error { removedServices = append(removedServices, serviceID) @@ -104,4 +122,5 @@ func TestContinueAfterError(t *testing.T) { assert.Equal(t, allServiceIDs, removedServices) assert.Equal(t, allNetworkIDs, cli.removedNetworks) assert.Equal(t, allSecretIDs, cli.removedSecrets) + assert.Equal(t, allConfigIDs, cli.removedConfigs) } From 94d3fb628e422a9873455f6d0db88ac5bf1ab9ea Mon Sep 17 00:00:00 2001 From: John Stephens Date: Fri, 26 May 2017 21:01:22 -0700 Subject: [PATCH 3/5] Include stack service configs in service specs Signed-off-by: John Stephens Upstream-commit: b7cac96f69d10436d740365157f72bf08a2a456f Component: cli --- components/cli/cli/compose/convert/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 79c6555276..5a99dc5777 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -141,6 +141,7 @@ func convertService( TTY: service.Tty, OpenStdin: service.StdinOpen, Secrets: secrets, + Configs: configs, ReadOnly: service.ReadOnly, Privileges: &privileges, }, From a89e29d68b7f2d66bc0fbf1f036d6a37ac13ce76 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 22 May 2017 13:41:16 -0400 Subject: [PATCH 4/5] Don't prune volumes on `docker system prune` Volumes tend to carry important data and pruning them on `docker system prune` can easily cause unwanted data loss. Let's play it safe and not prune volumes on `system prune` by default, and instead provide an option. Signed-off-by: Brian Goff Upstream-commit: 37fd6128dc268b885f070b6d89a72e73d2eacec8 Component: cli --- components/cli/cli/command/system/prune.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/components/cli/cli/command/system/prune.go b/components/cli/cli/command/system/prune.go index 11a45146a7..fca9c98f2c 100644 --- a/components/cli/cli/command/system/prune.go +++ b/components/cli/cli/command/system/prune.go @@ -12,9 +12,10 @@ import ( ) type pruneOptions struct { - force bool - all bool - filter opts.FilterOpt + force bool + all bool + pruneVolumes bool + filter opts.FilterOpt } // NewPruneCommand creates a new cobra.Command for `docker prune` @@ -34,6 +35,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images not just dangling ones") + flags.BoolVar(&options.pruneVolumes, "volumes", false, "Prune volumes") flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'label==')") // "filter" flag is available in 1.28 (docker 17.04) and up flags.SetAnnotation("filter", "version", []string{"1.28"}) @@ -67,12 +69,15 @@ func runPrune(dockerCli command.Cli, options pruneOptions) error { } var spaceReclaimed uint64 - - for _, pruneFn := range []func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error){ + pruneFuncs := []func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error){ prune.RunContainerPrune, - prune.RunVolumePrune, prune.RunNetworkPrune, - } { + } + if options.pruneVolumes { + pruneFuncs = append(pruneFuncs, prune.RunVolumePrune) + } + + for _, pruneFn := range pruneFuncs { spc, output, err := pruneFn(dockerCli, options.filter) if err != nil { return err From ee28c840c7d8d1f534d943b9ba2deed3da039c8c Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 25 May 2017 12:41:56 -0400 Subject: [PATCH 5/5] Remove gox, add support for s390x Signed-off-by: Daniel Nephin Upstream-commit: b7b7d784d8007aea0d8cff54c67d19e960cb4639 Component: cli --- components/cli/dockerfiles/Dockerfile.cross | 4 ---- components/cli/dockerfiles/Dockerfile.dev | 4 ---- components/cli/scripts/build/.variables | 5 +++-- components/cli/scripts/build/cross | 10 ++++++++-- components/cli/scripts/build/linux-cross | 19 ------------------- 5 files changed, 11 insertions(+), 31 deletions(-) delete mode 100755 components/cli/scripts/build/linux-cross diff --git a/components/cli/dockerfiles/Dockerfile.cross b/components/cli/dockerfiles/Dockerfile.cross index 7e9aaacc24..f52c3e10dd 100644 --- a/components/cli/dockerfiles/Dockerfile.cross +++ b/components/cli/dockerfiles/Dockerfile.cross @@ -11,10 +11,6 @@ RUN apt-get update -qq && apt-get install -y -q \ parallel \ ; -RUN go get github.com/mitchellh/gox && \ - cp /go/bin/gox /usr/bin && \ - rm -rf /go/src/* /go/pkg/* /go/bin/* - COPY dockerfiles/osx-cross.sh /tmp/ RUN /tmp/osx-cross.sh ENV PATH /osxcross/target/bin:$PATH diff --git a/components/cli/dockerfiles/Dockerfile.dev b/components/cli/dockerfiles/Dockerfile.dev index a14ea25f29..f3e058e285 100644 --- a/components/cli/dockerfiles/Dockerfile.dev +++ b/components/cli/dockerfiles/Dockerfile.dev @@ -7,10 +7,6 @@ RUN go get github.com/LK4D4/vndr && \ cp /go/bin/vndr /usr/bin && \ rm -rf /go/src/* /go/pkg/* /go/bin/* -RUN go get github.com/mitchellh/gox && \ - cp /go/bin/gox /usr/bin && \ - rm -rf /go/src/* /go/pkg/* /go/bin/* - RUN go get github.com/jteeuwen/go-bindata/go-bindata && \ cp /go/bin/go-bindata /usr/bin && \ rm -rf /go/src/* /go/pkg/* /go/bin/* diff --git a/components/cli/scripts/build/.variables b/components/cli/scripts/build/.variables index 12ca95e7d1..eb87c38268 100755 --- a/components/cli/scripts/build/.variables +++ b/components/cli/scripts/build/.variables @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -eu VERSION=${VERSION:-"unknown-version"} @@ -14,5 +13,7 @@ export LDFLAGS="\ ${LDFLAGS:-} \ " -export TARGET="build/docker-$(go env GOHOSTOS)-$(go env GOHOSTARCH)" +GOOS="${GOOS:-$(go env GOHOSTOS)}" +GOARCH="${GOARCH:-$(go env GOHOSTARCH)}" +export TARGET="build/docker-$GOOS-$GOARCH" export SOURCE="github.com/docker/cli/cmd/docker" diff --git a/components/cli/scripts/build/cross b/components/cli/scripts/build/cross index 51b8024ecb..9e7bfb35b8 100755 --- a/components/cli/scripts/build/cross +++ b/components/cli/scripts/build/cross @@ -1,12 +1,18 @@ #!/usr/bin/env bash +# +# Build a binary for all supported platforms +# set -eu -o pipefail BUILDDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -echo "Building all binaries" +echo "Building binaries for all platforms" SHELL=/bin/bash parallel ::: \ - "$BUILDDIR/linux-cross" \ "$BUILDDIR/windows" \ "$BUILDDIR/osx" \ + "GOOS=linux GOARCH=amd64 $BUILDDIR/binary" \ + "GOOS=linux GOARCH=arm $BUILDDIR/binary" \ + "GOOS=linux GOARCH=ppc64le $BUILDDIR/binary" \ + "GOOS=linux GOARCH=s390x $BUILDDIR/binary" \ ; diff --git a/components/cli/scripts/build/linux-cross b/components/cli/scripts/build/linux-cross deleted file mode 100755 index 53aa0e1511..0000000000 --- a/components/cli/scripts/build/linux-cross +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -# -# Build static linux binary for multiple architectures -# - -set -eu -o pipefail - -source ./scripts/build/.variables - -CROSS_OSARCH="linux/amd64 linux/arm linux/ppc64le" - -# Not yet supported by gox -# linux/s390x - -echo "Building all linux binaries" -gox -output build/docker-{{.OS}}-{{.Arch}} \ - -osarch "${CROSS_OSARCH}" \ - --ldflags "${LDFLAGS}" \ - "${SOURCE}"