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) + } + } +} 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) } 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 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, }, 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}"