From 38cfa2ee6d38f4a1d822a96dc5818c5f823d8cf4 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Mon, 22 May 2017 01:39:06 +0000 Subject: [PATCH 01/67] Spelling fixes * appropriate * assumption * attach * because * building * customized * mapping * propagated Signed-off-by: Josh Soref Upstream-commit: 70a9905ee5a24cf20521f096f7cf0646e6ffcc70 Component: cli --- components/cli/cli/command/container/hijack.go | 2 +- components/cli/cli/command/container/start.go | 4 ++-- components/cli/cli/command/formatter/container_test.go | 2 +- components/cli/cli/command/service/helpers.go | 2 +- components/cli/cli/command/service/logs.go | 2 +- components/cli/cli/command/service/progress/progress.go | 2 +- components/cli/dockerfiles/osx-cross.sh | 2 +- components/cli/opts/opts_test.go | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/cli/cli/command/container/hijack.go b/components/cli/cli/command/container/hijack.go index c6815a4d7d..603557f3bd 100644 --- a/components/cli/cli/command/container/hijack.go +++ b/components/cli/cli/command/container/hijack.go @@ -163,7 +163,7 @@ func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan if err != nil { // This error will also occur on the receive // side (from stdout) where it will be - // propogated back to the caller. + // propagated back to the caller. logrus.Debugf("Error sendStdin: %s", err) } } diff --git a/components/cli/cli/command/container/start.go b/components/cli/cli/command/container/start.go index af8255de8b..6cd4751117 100644 --- a/components/cli/cli/command/container/start.go +++ b/components/cli/cli/command/container/start.go @@ -146,12 +146,12 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error { fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err) } } - if attchErr := <-cErr; attchErr != nil { + if attachErr := <-cErr; attachErr != nil { if _, ok := err.(term.EscapeError); ok { // The user entered the detach escape sequence. return nil } - return attchErr + return attachErr } if status := <-statusChan; status != 0 { diff --git a/components/cli/cli/command/formatter/container_test.go b/components/cli/cli/command/formatter/container_test.go index 5b1451c29e..cfe42df49f 100644 --- a/components/cli/cli/command/formatter/container_test.go +++ b/components/cli/cli/command/formatter/container_test.go @@ -226,7 +226,7 @@ size: 0B Context{Format: NewContainerFormat("{{.Image}}", false, true)}, "ubuntu\nubuntu\n", }, - // Special headers for customerized table format + // Special headers for customized table format { Context{Format: NewContainerFormat(`table {{truncate .ID 5}}\t{{json .Image}} {{.RunningFor}}/{{title .Status}}/{{pad .Ports 2 2}}.{{upper .Names}} {{lower .Status}}`, false, true)}, `CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS diff --git a/components/cli/cli/command/service/helpers.go b/components/cli/cli/command/service/helpers.go index 2e893f96e1..8bc745dcef 100644 --- a/components/cli/cli/command/service/helpers.go +++ b/components/cli/cli/command/service/helpers.go @@ -11,7 +11,7 @@ import ( ) // waitOnService waits for the service to converge. It outputs a progress bar, -// if appopriate based on the CLI flags. +// if appropriate based on the CLI flags. func waitOnService(ctx context.Context, dockerCli *command.DockerCli, serviceID string, opts *serviceOptions) error { errChan := make(chan error, 1) pipeReader, pipeWriter := io.Pipe() diff --git a/components/cli/cli/command/service/logs.go b/components/cli/cli/command/service/logs.go index 42e00e6cd2..fbefa5f52e 100644 --- a/components/cli/cli/command/service/logs.go +++ b/components/cli/cli/command/service/logs.go @@ -237,7 +237,7 @@ type logWriter struct { func (lw *logWriter) Write(buf []byte) (int, error) { // this works but ONLY because stdcopy calls write a whole line at a time. // if this ends up horribly broken or panics, check to see if stdcopy has - // reneged on that asssumption. (@god forgive me) + // reneged on that assumption. (@god forgive me) // also this only works because the logs format is, like, barely parsable. // if something changes in the logs format, this is gonna break diff --git a/components/cli/cli/command/service/progress/progress.go b/components/cli/cli/command/service/progress/progress.go index 88cc9e5fac..8e37489bdb 100644 --- a/components/cli/cli/command/service/progress/progress.go +++ b/components/cli/cli/command/service/progress/progress.go @@ -233,7 +233,7 @@ func writeOverallProgress(progressOut progress.Output, numerator, denominator in type replicatedProgressUpdater struct { progressOut progress.Output - // used for maping slots to a contiguous space + // used for mapping slots to a contiguous space // this also causes progress bars to appear in order slotMap map[int]int diff --git a/components/cli/dockerfiles/osx-cross.sh b/components/cli/dockerfiles/osx-cross.sh index 07f6542fcc..840334a135 100755 --- a/components/cli/dockerfiles/osx-cross.sh +++ b/components/cli/dockerfiles/osx-cross.sh @@ -25,5 +25,5 @@ echo "Downloading OSX SDK" time curl -sSL https://s3.dockerproject.org/darwin/v2/${OSX_SDK}.tar.xz \ -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" -echo "Buidling osxcross" +echo "Building osxcross" UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh > /dev/null diff --git a/components/cli/opts/opts_test.go b/components/cli/opts/opts_test.go index f7b91db928..723a212ea7 100644 --- a/components/cli/opts/opts_test.go +++ b/components/cli/opts/opts_test.go @@ -158,7 +158,7 @@ func TestValidateDNSSearch(t *testing.T) { `foo.bar-.baz`, `foo.-bar`, `foo.-bar.baz`, - `foo.bar.baz.this.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbe`, + `foo.bar.baz.this.should.fail.on.long.name.because.it.is.longer.thanisshouldbethis.should.fail.on.long.name.because.it.is.longer.thanisshouldbethis.should.fail.on.long.name.because.it.is.longer.thanisshouldbethis.should.fail.on.long.name.because.it.is.longer.thanisshouldbe`, } for _, domain := range valid { From f1ece287754d32e7ac5b4e2ebf711b431cf9973c Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Tue, 23 May 2017 11:11:43 -0700 Subject: [PATCH 02/67] Update go-winio to v0.4.1 Signed-off-by: Darren Stahl Upstream-commit: 5981675d5fa86f1159f707cc92f257edef87de8a Component: cli --- components/cli/vendor.conf | 2 +- .../cli/vendor/github.com/Microsoft/go-winio/file.go | 8 +++++--- .../cli/vendor/github.com/Microsoft/go-winio/pipe.go | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index 0ca92b3228..984a1ca524 100644 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -1,5 +1,5 @@ github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62 -github.com/Microsoft/go-winio v0.4.0 +github.com/Microsoft/go-winio v0.4.1 github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty github.com/Sirupsen/logrus v0.11.0 github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c diff --git a/components/cli/vendor/github.com/Microsoft/go-winio/file.go b/components/cli/vendor/github.com/Microsoft/go-winio/file.go index 078a5687b6..613f31b520 100644 --- a/components/cli/vendor/github.com/Microsoft/go-winio/file.go +++ b/components/cli/vendor/github.com/Microsoft/go-winio/file.go @@ -124,7 +124,8 @@ func (f *win32File) Close() error { return nil } -// prepareIo prepares for a new IO operation +// prepareIo prepares for a new IO operation. +// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. func (f *win32File) prepareIo() (*ioOperation, error) { f.wg.Add(1) if f.closing { @@ -155,7 +156,6 @@ func ioCompletionProcessor(h syscall.Handle) { // the operation has actually completed. func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { if err != syscall.ERROR_IO_PENDING { - f.wg.Done() return int(bytes), err } @@ -192,7 +192,6 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er // code to ioCompletionProcessor, c must remain alive // until the channel read is complete. runtime.KeepAlive(c) - f.wg.Done() return int(r.bytes), err } @@ -202,6 +201,7 @@ func (f *win32File) Read(b []byte) (int, error) { if err != nil { return 0, err } + defer f.wg.Done() if f.readDeadline.timedout.isSet() { return 0, ErrTimeout @@ -228,6 +228,8 @@ func (f *win32File) Write(b []byte) (int, error) { if err != nil { return 0, err } + defer f.wg.Done() + if f.writeDeadline.timedout.isSet() { return 0, ErrTimeout } diff --git a/components/cli/vendor/github.com/Microsoft/go-winio/pipe.go b/components/cli/vendor/github.com/Microsoft/go-winio/pipe.go index 0ee2641ffc..fa270a310f 100644 --- a/components/cli/vendor/github.com/Microsoft/go-winio/pipe.go +++ b/components/cli/vendor/github.com/Microsoft/go-winio/pipe.go @@ -367,6 +367,8 @@ func connectPipe(p *win32File) error { if err != nil { return err } + defer p.wg.Done() + err = connectNamedPipe(p.handle, &c.o) _, err = p.asyncIo(c, nil, 0, err) if err != nil && err != cERROR_PIPE_CONNECTED { From beeb2759325d581b82406cfab29931d453b02f6b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 31 May 2017 21:43:56 +0200 Subject: [PATCH 03/67] Fix prefix-matching for service ps The docker CLI matches objects either by ID _prefix_ or a full name match, but not partial name matches. The correct order of resolution is; - Full ID match (a name should not be able to mask an ID) - Full name - ID-prefix This patch changes the way services are matched. Also change to use the first matching service, if there's a full match (by ID or Name) instead of continue looking for other possible matches. Error handling changed; - Do not error early if multiple services were requested and one or more services were not found. Print the services that were not found after printing those that _were_ found instead - Print an error if ID-prefix matching is ambiguous Signed-off-by: Sebastiaan van Stijn Upstream-commit: 62796124432c7e56e7dda226c3c53c8c2356a30c Component: cli --- components/cli/cli/command/service/ps.go | 51 ++++++++++++++++-------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/components/cli/cli/command/service/ps.go b/components/cli/cli/command/service/ps.go index 87b29d2043..7c744478ec 100644 --- a/components/cli/cli/command/service/ps.go +++ b/components/cli/cli/command/service/ps.go @@ -69,29 +69,43 @@ func runPS(dockerCli command.Cli, options psOptions) error { return err } + var errs []string + serviceCount := 0 +loop: + // Match services by 1. Full ID, 2. Full name, 3. ID prefix. An error is returned if the ID-prefix match is ambiguous for _, service := range options.services { - serviceCount := 0 - // Lookup by ID/Prefix - for _, serviceEntry := range serviceByIDList { - if strings.HasPrefix(serviceEntry.ID, service) { - filter.Add("service", serviceEntry.ID) + for _, s := range serviceByIDList { + if s.ID == service { + filter.Add("service", s.ID) serviceCount++ + continue loop } } - - // Lookup by Name/Prefix - for _, serviceEntry := range serviceByNameList { - if strings.HasPrefix(serviceEntry.Spec.Annotations.Name, service) { - filter.Add("service", serviceEntry.ID) + for _, s := range serviceByNameList { + if s.Spec.Annotations.Name == service { + filter.Add("service", s.ID) serviceCount++ + continue loop } } - // If nothing has been found, return immediately. - if serviceCount == 0 { - return errors.Errorf("no such services: %s", service) + found := false + for _, s := range serviceByIDList { + if strings.HasPrefix(s.ID, service) { + if found { + return errors.New("multiple services found with provided prefix: " + service) + } + filter.Add("service", s.ID) + serviceCount++ + found = true + } + } + if !found { + errs = append(errs, "no such service: "+service) } } - + if serviceCount == 0 { + return errors.New(strings.Join(errs, "\n")) + } if filter.Include("node") { nodeFilters := filter.Get("node") for _, nodeFilter := range nodeFilters { @@ -117,6 +131,11 @@ func runPS(dockerCli command.Cli, options psOptions) error { format = formatter.TableFormatKey } } - - return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format) + if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { + return err + } + if len(errs) != 0 { + return errors.New(strings.Join(errs, "\n")) + } + return nil } From 67be6e9ac80c8a298579f1d4c1e430a2ab5b809b Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 1 Jun 2017 11:47:43 -0400 Subject: [PATCH 04/67] Fix complexity of service/ps. Signed-off-by: Daniel Nephin Upstream-commit: b5baffde44cf16bc2eb030c09da76d39abb62252 Component: cli --- components/cli/cli/command/service/ps.go | 65 ++++++++++++++---------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/components/cli/cli/command/service/ps.go b/components/cli/cli/command/service/ps.go index 7c744478ec..741f6b589f 100644 --- a/components/cli/cli/command/service/ps.go +++ b/components/cli/cli/command/service/ps.go @@ -12,6 +12,7 @@ import ( "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -52,6 +53,34 @@ func runPS(dockerCli command.Cli, options psOptions) error { client := dockerCli.Client() ctx := context.Background() + filter, notfound, err := createFilter(ctx, client, options) + if err != nil { + return err + } + + tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) + if err != nil { + return err + } + + format := options.format + if len(format) == 0 { + if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet { + format = dockerCli.ConfigFile().TasksFormat + } else { + format = formatter.TableFormatKey + } + } + if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { + return err + } + if len(notfound) != 0 { + return errors.New(strings.Join(notfound, "\n")) + } + return nil +} + +func createFilter(ctx context.Context, client client.APIClient, options psOptions) (filters.Args, []string, error) { filter := options.filter.Value() serviceIDFilter := filters.NewArgs() @@ -62,14 +91,14 @@ func runPS(dockerCli command.Cli, options psOptions) error { } serviceByIDList, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceIDFilter}) if err != nil { - return err + return filter, nil, err } serviceByNameList, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceNameFilter}) if err != nil { - return err + return filter, nil, err } - var errs []string + var notfound []string serviceCount := 0 loop: // Match services by 1. Full ID, 2. Full name, 3. ID prefix. An error is returned if the ID-prefix match is ambiguous @@ -92,7 +121,7 @@ loop: for _, s := range serviceByIDList { if strings.HasPrefix(s.ID, service) { if found { - return errors.New("multiple services found with provided prefix: " + service) + return filter, nil, errors.New("multiple services found with provided prefix: " + service) } filter.Add("service", s.ID) serviceCount++ @@ -100,42 +129,22 @@ loop: } } if !found { - errs = append(errs, "no such service: "+service) + notfound = append(notfound, "no such service: "+service) } } if serviceCount == 0 { - return errors.New(strings.Join(errs, "\n")) + return filter, nil, errors.New(strings.Join(notfound, "\n")) } if filter.Include("node") { nodeFilters := filter.Get("node") for _, nodeFilter := range nodeFilters { nodeReference, err := node.Reference(ctx, client, nodeFilter) if err != nil { - return err + return filter, nil, err } filter.Del("node", nodeFilter) filter.Add("node", nodeReference) } } - - tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) - if err != nil { - return err - } - - format := options.format - if len(format) == 0 { - if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet { - format = dockerCli.ConfigFile().TasksFormat - } else { - format = formatter.TableFormatKey - } - } - if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { - return err - } - if len(errs) != 0 { - return errors.New(strings.Join(errs, "\n")) - } - return nil + return filter, notfound, err } From 25b6069bb2554865d554acae59df9d4ff9b2f6b7 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 1 Jun 2017 14:50:07 -0400 Subject: [PATCH 05/67] Compose: Improve error messages when resource creation/updates fail. Signed-off-by: Daniel Nephin Upstream-commit: 729d07a371c0539453f5754303d40b2cf0d2e642 Component: cli --- .../cli/command/stack/deploy_composefile.go | 41 ++++++++----------- components/cli/cli/compose/loader/volume.go | 5 +-- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index 642867cba9..145da67daa 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -196,17 +196,18 @@ func createSecrets( for _, secretSpec := range secrets { secret, _, err := client.SecretInspectWithRaw(ctx, secretSpec.Name) - if err == nil { + switch { + case err == nil: // secret already exists, then we update that if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil { - return err + return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name) } - } else if apiclient.IsErrSecretNotFound(err) { + case apiclient.IsErrSecretNotFound(err): // secret does not exist, then we create a new one. if _, err := client.SecretCreate(ctx, secretSpec); err != nil { - return err + return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name) } - } else { + default: return err } } @@ -222,17 +223,18 @@ func createConfigs( for _, configSpec := range configs { config, _, err := client.ConfigInspectWithRaw(ctx, configSpec.Name) - if err == nil { + switch { + case err == nil: // config already exists, then we update that if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil { - return err + errors.Wrapf(err, "failed to update config %s", configSpec.Name) } - } else if apiclient.IsErrConfigNotFound(err) { + case apiclient.IsErrConfigNotFound(err): // config does not exist, then we create a new one. if _, err := client.ConfigCreate(ctx, configSpec); err != nil { - return err + errors.Wrapf(err, "failed to create config %s", configSpec.Name) } - } else { + default: return err } } @@ -269,10 +271,9 @@ func createNetworks( fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name) if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil { - return err + return errors.Wrapf(err, "failed to create network %s", internalName) } } - return nil } @@ -312,19 +313,15 @@ func deployServices( if service, exists := existingServiceMap[name]; exists { fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID) - updateOpts := types.ServiceUpdateOptions{} - if sendAuth { - updateOpts.EncodedRegistryAuth = encodedAuth - } response, err := apiClient.ServiceUpdate( ctx, service.ID, service.Version, serviceSpec, - updateOpts, + types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}, ) if err != nil { - return err + return errors.Wrapf(err, "failed to update service %s", name) } for _, warning := range response.Warnings { @@ -333,15 +330,11 @@ func deployServices( } else { fmt.Fprintf(out, "Creating service %s\n", name) - createOpts := types.ServiceCreateOptions{} - if sendAuth { - createOpts.EncodedRegistryAuth = encodedAuth - } + createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth} if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil { - return err + return errors.Wrapf(err, "failed to create service %s", name) } } } - return nil } diff --git a/components/cli/cli/compose/loader/volume.go b/components/cli/cli/compose/loader/volume.go index 0b8bc1b063..865d78ff7f 100644 --- a/components/cli/cli/compose/loader/volume.go +++ b/components/cli/cli/compose/loader/volume.go @@ -114,8 +114,5 @@ func isFilePath(source string) bool { // Windows absolute path first, next := utf8.DecodeRuneInString(source) - if unicode.IsLetter(first) && source[next] == ':' { - return true - } - return false + return unicode.IsLetter(first) && source[next] == ':' } From cd8431c1221dae70a32d8b1adf98dd1eb48576fd Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 1 Jun 2017 15:22:09 -0400 Subject: [PATCH 06/67] Use a map instad of a switch/case for Compose transform. Signed-off-by: Daniel Nephin Upstream-commit: 97ebc194380a7cc7744df820a08244b1457a93cf Component: cli --- components/cli/cli/compose/loader/loader.go | 75 ++++++++++----------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/components/cli/cli/compose/loader/loader.go b/components/cli/cli/compose/loader/loader.go index f12a7f0eea..4cf06f66df 100644 --- a/components/cli/cli/compose/loader/loader.go +++ b/components/cli/cli/compose/loader/loader.go @@ -196,7 +196,7 @@ func transform(source map[string]interface{}, target interface{}) error { data := mapstructure.Metadata{} config := &mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc( - transformHook, + createTransformHook(), mapstructure.StringToTimeDurationHookFunc()), Result: target, Metadata: &data, @@ -208,46 +208,33 @@ func transform(source map[string]interface{}, target interface{}) error { return decoder.Decode(source) } -func transformHook( - source reflect.Type, - target reflect.Type, - data interface{}, -) (interface{}, error) { - switch target { - case reflect.TypeOf(types.External{}): - return transformExternal(data) - case reflect.TypeOf(types.HealthCheckTest{}): - return transformHealthCheckTest(data) - case reflect.TypeOf(types.ShellCommand{}): - return transformShellCommand(data) - case reflect.TypeOf(types.StringList{}): - return transformStringList(data) - case reflect.TypeOf(map[string]string{}): - return transformMapStringString(data) - case reflect.TypeOf(types.UlimitsConfig{}): - return transformUlimits(data) - case reflect.TypeOf(types.UnitBytes(0)): - return transformSize(data) - case reflect.TypeOf([]types.ServicePortConfig{}): - return transformServicePort(data) - case reflect.TypeOf(types.ServiceSecretConfig{}): - return transformStringSourceMap(data) - case reflect.TypeOf(types.ServiceConfigObjConfig{}): - return transformStringSourceMap(data) - case reflect.TypeOf(types.StringOrNumberList{}): - return transformStringOrNumberList(data) - case reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): - return transformServiceNetworkMap(data) - case reflect.TypeOf(types.MappingWithEquals{}): - return transformMappingOrList(data, "=", true), nil - case reflect.TypeOf(types.Labels{}): - return transformMappingOrList(data, "=", false), nil - case reflect.TypeOf(types.MappingWithColon{}): - return transformMappingOrList(data, ":", false), nil - case reflect.TypeOf(types.ServiceVolumeConfig{}): - return transformServiceVolumeConfig(data) +func createTransformHook() mapstructure.DecodeHookFuncType { + transforms := map[reflect.Type]func(interface{}) (interface{}, error){ + reflect.TypeOf(types.External{}): transformExternal, + reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest, + reflect.TypeOf(types.ShellCommand{}): transformShellCommand, + reflect.TypeOf(types.StringList{}): transformStringList, + reflect.TypeOf(map[string]string{}): transformMapStringString, + reflect.TypeOf(types.UlimitsConfig{}): transformUlimits, + reflect.TypeOf(types.UnitBytes(0)): transformSize, + reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort, + reflect.TypeOf(types.ServiceSecretConfig{}): transformStringSourceMap, + reflect.TypeOf(types.ServiceConfigObjConfig{}): transformStringSourceMap, + reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList, + reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap, + reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true), + reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false), + reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false), + reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig, + } + + return func(_ reflect.Type, target reflect.Type, data interface{}) (interface{}, error) { + transform, ok := transforms[target] + if !ok { + return data, nil + } + return transform(data) } - return data, nil } // keys needs to be converted to strings for jsonschema @@ -618,6 +605,12 @@ func transformStringList(data interface{}) (interface{}, error) { } } +func transformMappingOrListFunc(sep string, allowNil bool) func(interface{}) (interface{}, error) { + return func(data interface{}) (interface{}, error) { + return transformMappingOrList(data, sep, allowNil), nil + } +} + func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) interface{} { switch value := mappingOrList.(type) { case map[string]interface{}: @@ -659,7 +652,7 @@ func transformHealthCheckTest(data interface{}) (interface{}, error) { } } -func transformSize(value interface{}) (int64, error) { +func transformSize(value interface{}) (interface{}, error) { switch value := value.(type) { case int: return int64(value), nil From 7e488b89ed3208b3d700a3cf9bf6ef105a50d41b Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 1 Jun 2017 12:59:11 -0400 Subject: [PATCH 07/67] Add unit tests for service/ps Signed-off-by: Daniel Nephin Upstream-commit: 3718833f2cecde35ae1ed007a9b2d5bf507c7c66 Component: cli --- components/cli/cli/command/service/ps_test.go | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 components/cli/cli/command/service/ps_test.go diff --git a/components/cli/cli/command/service/ps_test.go b/components/cli/cli/command/service/ps_test.go new file mode 100644 index 0000000000..a53748d6d3 --- /dev/null +++ b/components/cli/cli/command/service/ps_test.go @@ -0,0 +1,118 @@ +package service + +import ( + "testing" + + "bytes" + + "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/opts" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/client" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +type fakeClient struct { + client.Client + serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) +} + +func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + if f.serviceListFunc != nil { + return f.serviceListFunc(ctx, options) + } + return nil, nil +} + +func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { + return nil, nil +} + +func newService(id string, name string) swarm.Service { + return swarm.Service{ + ID: id, + Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: name}}, + } +} + +func TestCreateFilter(t *testing.T) { + client := &fakeClient{ + serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + {ID: "idmatch"}, + {ID: "idprefixmatch"}, + newService("cccccccc", "namematch"), + newService("01010101", "notfoundprefix"), + }, nil + }, + } + + filter := opts.NewFilterOpt() + require.NoError(t, filter.Set("node=somenode")) + options := psOptions{ + services: []string{"idmatch", "idprefix", "namematch", "notfound"}, + filter: filter, + } + + actual, notfound, err := createFilter(context.Background(), client, options) + require.NoError(t, err) + assert.Equal(t, notfound, []string{"no such service: notfound"}) + + expected := filters.NewArgs() + expected.Add("service", "idmatch") + expected.Add("service", "idprefixmatch") + expected.Add("service", "cccccccc") + expected.Add("node", "somenode") + assert.Equal(t, expected, actual) +} + +func TestCreateFilterWithAmbiguousIDPrefixError(t *testing.T) { + client := &fakeClient{ + serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + {ID: "aaaone"}, + {ID: "aaatwo"}, + }, nil + }, + } + options := psOptions{ + services: []string{"aaa"}, + filter: opts.NewFilterOpt(), + } + _, _, err := createFilter(context.Background(), client, options) + assert.EqualError(t, err, "multiple services found with provided prefix: aaa") +} + +func TestCreateFilterNoneFound(t *testing.T) { + client := &fakeClient{} + options := psOptions{ + services: []string{"foo", "notfound"}, + filter: opts.NewFilterOpt(), + } + _, _, err := createFilter(context.Background(), client, options) + assert.EqualError(t, err, "no such service: foo\nno such service: notfound") +} + +func TestRunPSWarnsOnNotFound(t *testing.T) { + client := &fakeClient{ + serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + {ID: "foo"}, + }, nil + }, + } + + out := new(bytes.Buffer) + cli := test.NewFakeCli(client, out) + options := psOptions{ + services: []string{"foo", "bar"}, + filter: opts.NewFilterOpt(), + format: "{{.ID}}", + } + err := runPS(cli, options) + assert.EqualError(t, err, "no such service: bar") +} From 4bf796daf660735d991df14bbbb6192aed82450e Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 10 May 2017 18:24:32 -0700 Subject: [PATCH 08/67] Add scripts and targets for manpages and yamldocs Signed-off-by: Tibor Vass Upstream-commit: ff615dbc4d01b0115e4658ee1a0ed6809b5f88ae Component: cli --- components/cli/.gitignore | 4 ++++ components/cli/Makefile | 16 +++++++++++++--- components/cli/docker.Makefile | 12 +++++++++++- .../generate.sh => scripts/docs/generate-man.sh} | 10 ++++++++++ components/cli/scripts/docs/generate-yaml.sh | 5 +++++ components/cli/scripts/test/unit-with-coverage | 2 +- 6 files changed, 44 insertions(+), 5 deletions(-) rename components/cli/{man/generate.sh => scripts/docs/generate-man.sh} (53%) create mode 100755 components/cli/scripts/docs/generate-yaml.sh diff --git a/components/cli/.gitignore b/components/cli/.gitignore index 09d363d1ce..fa99439ac8 100644 --- a/components/cli/.gitignore +++ b/components/cli/.gitignore @@ -2,5 +2,9 @@ /build/ cli/winresources/rsrc_386.syso cli/winresources/rsrc_amd64.syso +/man/man1/ +/man/man5/ +/man/man8/ +/docs/yaml/gen/ coverage.txt profile.out diff --git a/components/cli/Makefile b/components/cli/Makefile index b9180d4e02..dda6df868f 100644 --- a/components/cli/Makefile +++ b/components/cli/Makefile @@ -6,17 +6,17 @@ all: binary # remove build artifacts .PHONY: clean clean: - rm -rf ./build/* cli/winresources/rsrc_* + rm -rf ./build/* cli/winresources/rsrc_* ./man/man[1-9] docs/yaml/gen # run go test # the "-tags daemon" part is temporary .PHONY: test test: - ./scripts/test/unit $(shell go list ./... | grep -v /vendor/) + ./scripts/test/unit $(shell go list ./... | grep -v '/vendor/') .PHONY: test-coverage test-coverage: - ./scripts/test/unit-with-coverage + ./scripts/test/unit-with-coverage $(shell go list ./... | grep -v '/vendor/') .PHONY: lint lint: @@ -44,6 +44,16 @@ vendor: vendor.conf vndr 2> /dev/null scripts/validate/check-git-diff vendor +## generate man pages from go source and markdown +.PHONY: manpages +manpages: + scripts/dos/generate-man.sh + +## generate documentation YAML files consumed by docs repo +.PHONY: yamldocs +yamldocs: + scripts/docs/generate-yaml.sh + cli/compose/schema/bindata.go: cli/compose/schema/data/*.json go generate github.com/docker/cli/cli/compose/schema diff --git a/components/cli/docker.Makefile b/components/cli/docker.Makefile index 765b3a6f96..1e5319bb23 100644 --- a/components/cli/docker.Makefile +++ b/components/cli/docker.Makefile @@ -69,4 +69,14 @@ vendor: build_docker_image vendor.conf docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make vendor dynbinary: build_cross_image - docker run --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make dynbinary + docker run -ti --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make dynbinary + +## generate man pages from go source and markdown +.PHONY: manpages +manpages: build_docker_image + docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make manpages + +## Generate documentation YAML files consumed by docs repo +.PHONY: yamldocs +yamldocs: build_docker_image + docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make yamldocs diff --git a/components/cli/man/generate.sh b/components/cli/scripts/docs/generate-man.sh similarity index 53% rename from components/cli/man/generate.sh rename to components/cli/scripts/docs/generate-man.sh index 905b2d7baf..e955130559 100755 --- a/components/cli/man/generate.sh +++ b/components/cli/scripts/docs/generate-man.sh @@ -7,6 +7,16 @@ set -eu mkdir -p ./man/man1 +MD2MAN_REPO=github.com/cpuguy83/go-md2man +MD2MAN_COMMIT=$(grep -F "$MD2MAN_REPO" vendor.conf | cut -d' ' -f2) + +( + go get -d "$MD2MAN_REPO" + cd "$GOPATH"/src/"$MD2MAN_REPO" + git checkout "$MD2MAN_COMMIT" &> /dev/null + go install "$MD2MAN_REPO" +) + # Generate man pages from cobra commands go build -o /tmp/gen-manpages ./man /tmp/gen-manpages --root . --target ./man/man1 diff --git a/components/cli/scripts/docs/generate-yaml.sh b/components/cli/scripts/docs/generate-yaml.sh new file mode 100755 index 0000000000..c322071bcf --- /dev/null +++ b/components/cli/scripts/docs/generate-yaml.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +go build -o build/yaml-docs-generator github.com/docker/cli/docs/yaml +mkdir docs/yaml/gen +build/yaml-docs-generator --root $(pwd) --target $(pwd)/docs/yaml/gen diff --git a/components/cli/scripts/test/unit-with-coverage b/components/cli/scripts/test/unit-with-coverage index 9073e2daf6..8c599b43ff 100755 --- a/components/cli/scripts/test/unit-with-coverage +++ b/components/cli/scripts/test/unit-with-coverage @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eu -o pipefail -for pkg in $(go list ./... | grep -v /vendor/); do +for pkg in $@; do ./scripts/test/unit \ -cover \ -coverprofile=profile.out \ From a920b458df3d0f3527137820d9e41343f2cfec7d Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Tue, 6 Jun 2017 00:23:21 +0200 Subject: [PATCH 09/67] Update vendoring of docker/docker Signed-off-by: Vincent Demeester Upstream-commit: 44ac80881f1cd9375b21a5ad8a2b8e496d0afa7a Component: cli --- components/cli/cli/command/config/create.go | 3 +- components/cli/cli/command/container/opts.go | 11 +- components/cli/cli/command/container/run.go | 16 +- .../cli/cli/command/container/update.go | 3 +- components/cli/cli/command/image/build.go | 5 +- .../cli/cli/command/image/build/context.go | 31 ++- components/cli/cli/command/network/create.go | 3 +- components/cli/cli/command/node/update.go | 3 +- components/cli/cli/command/secret/create.go | 3 +- components/cli/cli/command/service/opts.go | 85 ++++--- components/cli/cli/command/service/update.go | 7 +- components/cli/cli/command/volume/create.go | 3 +- components/cli/cli/compose/convert/service.go | 3 +- components/cli/cli/compose/loader/loader.go | 5 +- .../docker/runconfig => }/opts/envfile.go | 0 components/cli/opts/envfile_test.go | 141 +++++++++++ .../docker/runconfig => }/opts/parse.go | 0 components/cli/vendor.conf | 9 +- .../client/client.go | 38 ++- .../client/command.go | 9 +- .../credentials/credentials.go | 27 +++ .../credentials/error.go | 74 +++++- .../docker/docker/api/types/swarm/task.go | 3 - .../docker/docker/api/types/types.go | 2 +- .../docker/docker/api/types/volume.go | 3 + .../remotecontext/git}/gitutils.go | 2 +- .../github.com/docker/docker/client/client.go | 6 + .../docker/docker/client/container_wait.go | 4 +- .../docker/docker/client/interface.go | 1 + .../docker/docker/client/service_create.go | 34 ++- .../docker/docker/client/service_update.go | 8 +- .../github.com/docker/docker/opts/config.go | 98 -------- .../github.com/docker/docker/opts/opts.go | 161 ------------- .../docker/docker/opts/throttledevice.go | 111 --------- .../docker/docker/pkg/httputils/httputils.go | 56 ----- .../docker/docker/pkg/httputils/mimetype.go | 29 --- .../docker/docker/pkg/ioutils/multireader.go | 224 ------------------ .../docker/docker/pkg/term/tc_solaris_cgo.go | 12 +- .../docker/docker/pkg/term/termios_bsd.go | 2 +- .../resumable}/resumablerequestreader.go | 24 +- .../docker/docker/registry/session.go | 40 ++-- .../docker/docker/runconfig/hostconfig.go | 2 +- .../docker/runconfig/hostconfig_unix.go | 12 +- .../docker/runconfig/hostconfig_windows.go | 12 +- .../github.com/docker/docker/vendor.conf | 11 +- .../github.com/docker/docker/volume/volume.go | 3 + .../github.com/docker/libnetwork/LICENSE | 202 ---------------- .../github.com/docker/libnetwork/README.md | 89 ------- .../docker/libnetwork/resolvconf/README.md | 1 - .../libnetwork/resolvconf/dns/resolvconf.go | 26 -- .../github.com/docker/libnetwork/vendor.conf | 42 ---- 51 files changed, 492 insertions(+), 1207 deletions(-) rename components/cli/{vendor/github.com/docker/docker/runconfig => }/opts/envfile.go (100%) create mode 100644 components/cli/opts/envfile_test.go rename components/cli/{vendor/github.com/docker/docker/runconfig => }/opts/parse.go (100%) rename components/cli/vendor/github.com/docker/docker/{pkg/gitutils => builder/remotecontext/git}/gitutils.go (99%) delete mode 100644 components/cli/vendor/github.com/docker/docker/opts/config.go delete mode 100644 components/cli/vendor/github.com/docker/docker/opts/throttledevice.go delete mode 100644 components/cli/vendor/github.com/docker/docker/pkg/httputils/httputils.go delete mode 100644 components/cli/vendor/github.com/docker/docker/pkg/httputils/mimetype.go delete mode 100644 components/cli/vendor/github.com/docker/docker/pkg/ioutils/multireader.go rename components/cli/vendor/github.com/docker/docker/{pkg/httputils => registry/resumable}/resumablerequestreader.go (66%) delete mode 100644 components/cli/vendor/github.com/docker/libnetwork/LICENSE delete mode 100644 components/cli/vendor/github.com/docker/libnetwork/README.md delete mode 100644 components/cli/vendor/github.com/docker/libnetwork/resolvconf/README.md delete mode 100644 components/cli/vendor/github.com/docker/libnetwork/resolvconf/dns/resolvconf.go delete mode 100644 components/cli/vendor/github.com/docker/libnetwork/vendor.conf diff --git a/components/cli/cli/command/config/create.go b/components/cli/cli/command/config/create.go index 3f0a7db05e..437137c160 100644 --- a/components/cli/cli/command/config/create.go +++ b/components/cli/cli/command/config/create.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/system" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -65,7 +64,7 @@ func runConfigCreate(dockerCli command.Cli, options createOptions) error { spec := swarm.ConfigSpec{ Annotations: swarm.Annotations{ Name: options.name, - Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), + Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), }, Data: configData, } diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go index b636733815..2b7471b352 100644 --- a/components/cli/cli/command/container/opts.go +++ b/components/cli/cli/command/container/opts.go @@ -17,7 +17,6 @@ import ( networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/pkg/signal" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/spf13/pflag" @@ -409,13 +408,13 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err } // collect all the environment variables for the container - envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll()) + envVariables, err := opts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll()) if err != nil { return nil, err } // collect all the labels for the container - labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) + labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) if err != nil { return nil, err } @@ -440,7 +439,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err return nil, errors.Errorf("--userns: invalid USER mode") } - restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy) + restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy) if err != nil { return nil, err } @@ -553,7 +552,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err MacAddress: copts.macAddress, Entrypoint: entrypoint, WorkingDir: copts.workingDir, - Labels: runconfigopts.ConvertKVStringsToMap(labels), + Labels: opts.ConvertKVStringsToMap(labels), Healthcheck: healthConfig, } if flags.Changed("stop-signal") { @@ -666,7 +665,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err } func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { - loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts) + loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts) if loggingDriver == "none" && len(loggingOpts) > 0 { return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver) } diff --git a/components/cli/cli/command/container/run.go b/components/cli/cli/command/container/run.go index 722ad22e69..0164219b7f 100644 --- a/components/cli/cli/command/container/run.go +++ b/components/cli/cli/command/container/run.go @@ -5,6 +5,7 @@ import ( "io" "net/http/httputil" "os" + "regexp" "runtime" "strings" "syscall" @@ -17,7 +18,6 @@ import ( "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" - "github.com/docker/libnetwork/resolvconf/dns" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -77,13 +77,25 @@ func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) { // they are trying to set a DNS to a localhost address func warnOnLocalhostDNS(hostConfig container.HostConfig, stderr io.Writer) { for _, dnsIP := range hostConfig.DNS { - if dns.IsLocalhost(dnsIP) { + if isLocalhost(dnsIP) { fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) return } } } +// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range. +const ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` + +var localhostIPRegexp = regexp.MustCompile(ipLocalhost) + +// IsLocalhost returns true if ip matches the localhost IP regular expression. +// Used for determining if nameserver settings are being passed which are +// localhost addresses +func isLocalhost(ip string) bool { + return localhostIPRegexp.MatchString(ip) +} + func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error { containerConfig, err := parse(flags, copts) // just in case the parse does not exit diff --git a/components/cli/cli/command/container/update.go b/components/cli/cli/command/container/update.go index c13c7e6ab1..9cb74aeb76 100644 --- a/components/cli/cli/command/container/update.go +++ b/components/cli/cli/command/container/update.go @@ -8,7 +8,6 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/opts" containertypes "github.com/docker/docker/api/types/container" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -82,7 +81,7 @@ func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error { var restartPolicy containertypes.RestartPolicy if options.restartPolicy != "" { - restartPolicy, err = runconfigopts.ParseRestartPolicy(options.restartPolicy) + restartPolicy, err = opts.ParseRestartPolicy(options.restartPolicy) if err != nil { return err } diff --git a/components/cli/cli/command/image/build.go b/components/cli/cli/command/image/build.go index da28e898e9..1a6cb951bc 100644 --- a/components/cli/cli/command/image/build.go +++ b/components/cli/cli/command/image/build.go @@ -25,7 +25,6 @@ import ( "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" - runconfigopts "github.com/docker/docker/runconfig/opts" units "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -291,9 +290,9 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { Dockerfile: relDockerfile, ShmSize: options.shmSize.Value(), Ulimits: options.ulimits.GetList(), - BuildArgs: runconfigopts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll()), + BuildArgs: opts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll()), AuthConfigs: authConfigs, - Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), + Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), CacheFrom: options.cacheFrom, SecurityOpt: options.securityOpt, NetworkMode: options.networkMode, diff --git a/components/cli/cli/command/image/build/context.go b/components/cli/cli/command/image/build/context.go index e6165aa975..8b47dac026 100644 --- a/components/cli/cli/command/image/build/context.go +++ b/components/cli/cli/command/image/build/context.go @@ -1,24 +1,23 @@ package build import ( + "archive/tar" "bufio" + "bytes" "fmt" "io" "io/ioutil" + "net/http" "os" "os/exec" "path/filepath" "runtime" "strings" - - "archive/tar" - "bytes" "time" + "github.com/docker/docker/builder/remotecontext/git" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" - "github.com/docker/docker/pkg/gitutils" - "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" @@ -143,7 +142,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error) if _, err := exec.LookPath("git"); err != nil { return "", "", errors.Wrapf(err, "unable to find 'git'") } - absContextDir, err := gitutils.Clone(gitURL) + absContextDir, err := git.Clone(gitURL) if err != nil { return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory") } @@ -161,7 +160,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error) // Returns the tar archive used for the context and a path of the // dockerfile inside the tar. func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) { - response, err := httputils.Download(remoteURL) + response, err := getWithStatusError(remoteURL) if err != nil { return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err) } @@ -173,6 +172,24 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName) } +// getWithStatusError does an http.Get() and returns an error if the +// status code is 4xx or 5xx. +func getWithStatusError(url string) (resp *http.Response, err error) { + if resp, err = http.Get(url); err != nil { + return nil, err + } + if resp.StatusCode < 400 { + return resp, nil + } + msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status) + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, errors.Wrapf(err, msg+": error reading body") + } + return nil, errors.Errorf(msg+": %s", bytes.TrimSpace(body)) +} + // GetContextFromLocalDir uses the given local directory as context for a // `docker build`. Returns the absolute path to the local context directory, // the relative path of the dockerfile in that context directory, and a non-nil diff --git a/components/cli/cli/command/network/create.go b/components/cli/cli/command/network/create.go index 88f7a62611..ed6d2de7bd 100644 --- a/components/cli/cli/command/network/create.go +++ b/components/cli/cli/command/network/create.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -107,7 +106,7 @@ func runCreate(dockerCli *command.DockerCli, options createOptions) error { Ingress: options.ingress, Scope: options.scope, ConfigOnly: options.configOnly, - Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), + Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), } if from := options.configFrom; from != "" { diff --git a/components/cli/cli/command/node/update.go b/components/cli/cli/command/node/update.go index 4ba5f6d38b..017cf7dcb5 100644 --- a/components/cli/cli/command/node/update.go +++ b/components/cli/cli/command/node/update.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -95,7 +94,7 @@ func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) error { } if flags.Changed(flagLabelAdd) { labels := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll() - for k, v := range runconfigopts.ConvertKVStringsToMap(labels) { + for k, v := range opts.ConvertKVStringsToMap(labels) { spec.Annotations.Labels[k] = v } } diff --git a/components/cli/cli/command/secret/create.go b/components/cli/cli/command/secret/create.go index e708f7c591..20efaec752 100644 --- a/components/cli/cli/command/secret/create.go +++ b/components/cli/cli/command/secret/create.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/system" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -65,7 +64,7 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error { spec := swarm.SecretSpec{ Annotations: swarm.Annotations{ Name: options.name, - Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), + Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), }, Data: secretData, } diff --git a/components/cli/cli/command/service/opts.go b/components/cli/cli/command/service/opts.go index c173f4f70b..35cdedc70e 100644 --- a/components/cli/cli/command/service/opts.go +++ b/components/cli/cli/command/service/opts.go @@ -11,7 +11,6 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/api/defaults" shlex "github.com/flynn-archive/go-shlex" @@ -389,7 +388,7 @@ func (ldo *logDriverOptions) toLogDriver() *swarm.Driver { // set the log driver only if specified. return &swarm.Driver{ Name: ldo.name, - Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()), + Options: opts.ConvertKVStringsToMap(ldo.opts.GetAll()), } } @@ -519,36 +518,36 @@ func newServiceOptions() *serviceOptions { } } -func (opts *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) { +func (options *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) { serviceMode := swarm.ServiceMode{} - switch opts.mode { + switch options.mode { case "global": - if opts.replicas.Value() != nil { + if options.replicas.Value() != nil { return serviceMode, errors.Errorf("replicas can only be used with replicated mode") } serviceMode.Global = &swarm.GlobalService{} case "replicated": serviceMode.Replicated = &swarm.ReplicatedService{ - Replicas: opts.replicas.Value(), + Replicas: options.replicas.Value(), } default: - return serviceMode, errors.Errorf("Unknown mode: %s, only replicated and global supported", opts.mode) + return serviceMode, errors.Errorf("Unknown mode: %s, only replicated and global supported", options.mode) } return serviceMode, nil } -func (opts *serviceOptions) ToStopGracePeriod(flags *pflag.FlagSet) *time.Duration { +func (options *serviceOptions) ToStopGracePeriod(flags *pflag.FlagSet) *time.Duration { if flags.Changed(flagStopGracePeriod) { - return opts.stopGrace.Value() + return options.stopGrace.Value() } return nil } -func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet) (swarm.ServiceSpec, error) { +func (options *serviceOptions) ToService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet) (swarm.ServiceSpec, error) { var service swarm.ServiceSpec - envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll()) + envVariables, err := opts.ReadKVStrings(options.envFile.GetAll(), options.env.GetAll()) if err != nil { return service, err } @@ -567,68 +566,68 @@ func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.Netw currentEnv = append(currentEnv, env) } - healthConfig, err := opts.healthcheck.toHealthConfig() + healthConfig, err := options.healthcheck.toHealthConfig() if err != nil { return service, err } - serviceMode, err := opts.ToServiceMode() + serviceMode, err := options.ToServiceMode() if err != nil { return service, err } - networks, err := convertNetworks(ctx, apiClient, opts.networks) + networks, err := convertNetworks(ctx, apiClient, options.networks) if err != nil { return service, err } service = swarm.ServiceSpec{ Annotations: swarm.Annotations{ - Name: opts.name, - Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()), + Name: options.name, + Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: swarm.ContainerSpec{ - Image: opts.image, - Args: opts.args, - Command: opts.entrypoint.Value(), + Image: options.image, + Args: options.args, + Command: options.entrypoint.Value(), Env: currentEnv, - Hostname: opts.hostname, - Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()), - Dir: opts.workdir, - User: opts.user, - Groups: opts.groups.GetAll(), - StopSignal: opts.stopSignal, - TTY: opts.tty, - ReadOnly: opts.readOnly, - Mounts: opts.mounts.Value(), + Hostname: options.hostname, + Labels: opts.ConvertKVStringsToMap(options.containerLabels.GetAll()), + Dir: options.workdir, + User: options.user, + Groups: options.groups.GetAll(), + StopSignal: options.stopSignal, + TTY: options.tty, + ReadOnly: options.readOnly, + Mounts: options.mounts.Value(), DNSConfig: &swarm.DNSConfig{ - Nameservers: opts.dns.GetAll(), - Search: opts.dnsSearch.GetAll(), - Options: opts.dnsOption.GetAll(), + Nameservers: options.dns.GetAll(), + Search: options.dnsSearch.GetAll(), + Options: options.dnsOption.GetAll(), }, - Hosts: convertExtraHostsToSwarmHosts(opts.hosts.GetAll()), - StopGracePeriod: opts.ToStopGracePeriod(flags), + Hosts: convertExtraHostsToSwarmHosts(options.hosts.GetAll()), + StopGracePeriod: options.ToStopGracePeriod(flags), Healthcheck: healthConfig, }, Networks: networks, - Resources: opts.resources.ToResourceRequirements(), - RestartPolicy: opts.restartPolicy.ToRestartPolicy(flags), + Resources: options.resources.ToResourceRequirements(), + RestartPolicy: options.restartPolicy.ToRestartPolicy(flags), Placement: &swarm.Placement{ - Constraints: opts.constraints.GetAll(), - Preferences: opts.placementPrefs.prefs, + Constraints: options.constraints.GetAll(), + Preferences: options.placementPrefs.prefs, }, - LogDriver: opts.logDriver.toLogDriver(), + LogDriver: options.logDriver.toLogDriver(), }, Mode: serviceMode, - UpdateConfig: opts.update.updateConfig(flags), - RollbackConfig: opts.update.rollbackConfig(flags), - EndpointSpec: opts.endpoint.ToEndpointSpec(), + UpdateConfig: options.update.updateConfig(flags), + RollbackConfig: options.update.rollbackConfig(flags), + EndpointSpec: options.endpoint.ToEndpointSpec(), } - if opts.credentialSpec.Value() != nil { + if options.credentialSpec.Value() != nil { service.TaskTemplate.ContainerSpec.Privileges = &swarm.Privileges{ - CredentialSpec: opts.credentialSpec.Value(), + CredentialSpec: options.credentialSpec.Value(), } } diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index 5e3bc7c458..48552e2453 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/swarmkit/api/defaults" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -519,7 +518,7 @@ func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) { } values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll() - for key, value := range runconfigopts.ConvertKVStringsToMap(values) { + for key, value := range opts.ConvertKVStringsToMap(values) { (*field)[key] = value } } @@ -539,7 +538,7 @@ func updateLabels(flags *pflag.FlagSet, field *map[string]string) { } values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll() - for key, value := range runconfigopts.ConvertKVStringsToMap(values) { + for key, value := range opts.ConvertKVStringsToMap(values) { (*field)[key] = value } } @@ -933,7 +932,7 @@ func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error { taskTemplate.LogDriver = &swarm.Driver{ Name: name, - Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()), + Options: opts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()), } return nil diff --git a/components/cli/cli/command/volume/create.go b/components/cli/cli/command/volume/create.go index bf7f1a72b6..5a1092266f 100644 --- a/components/cli/cli/command/volume/create.go +++ b/components/cli/cli/command/volume/create.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/opts" volumetypes "github.com/docker/docker/api/types/volume" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -57,7 +56,7 @@ func runCreate(dockerCli command.Cli, options createOptions) error { Driver: options.driver, DriverOpts: options.driverOpts.GetAll(), Name: options.name, - Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), + Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), } vol, err := client.VolumeCreate(context.Background(), volReq) diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 233a376293..0fef936a22 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -14,7 +14,6 @@ import ( "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" ) @@ -404,7 +403,7 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) { // TODO: log if restart is being ignored if source == nil { - policy, err := runconfigopts.ParseRestartPolicy(restart) + policy, err := opts.ParseRestartPolicy(restart) if err != nil { return nil, err } diff --git a/components/cli/cli/compose/loader/loader.go b/components/cli/cli/compose/loader/loader.go index f12a7f0eea..8e009e375a 100644 --- a/components/cli/cli/compose/loader/loader.go +++ b/components/cli/cli/compose/loader/loader.go @@ -14,7 +14,6 @@ import ( "github.com/docker/cli/cli/compose/template" "github.com/docker/cli/cli/compose/types" "github.com/docker/cli/opts" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/go-connections/nat" units "github.com/docker/go-units" shellwords "github.com/mattn/go-shellwords" @@ -351,14 +350,14 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l for _, file := range serviceConfig.EnvFile { filePath := absPath(workingDir, file) - fileVars, err := runconfigopts.ParseEnvFile(filePath) + fileVars, err := opts.ParseEnvFile(filePath) if err != nil { return err } envVars = append(envVars, fileVars...) } updateEnvironment(environment, - runconfigopts.ConvertKVStringsToMapWithNil(envVars), lookupEnv) + opts.ConvertKVStringsToMapWithNil(envVars), lookupEnv) } updateEnvironment(environment, serviceConfig.Environment, lookupEnv) diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/opts/envfile.go b/components/cli/opts/envfile.go similarity index 100% rename from components/cli/vendor/github.com/docker/docker/runconfig/opts/envfile.go rename to components/cli/opts/envfile.go diff --git a/components/cli/opts/envfile_test.go b/components/cli/opts/envfile_test.go new file mode 100644 index 0000000000..f3faabe3c0 --- /dev/null +++ b/components/cli/opts/envfile_test.go @@ -0,0 +1,141 @@ +package opts + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "reflect" + "strings" + "testing" +) + +func tmpFileWithContent(content string, t *testing.T) string { + tmpFile, err := ioutil.TempFile("", "envfile-test") + if err != nil { + t.Fatal(err) + } + defer tmpFile.Close() + + tmpFile.WriteString(content) + return tmpFile.Name() +} + +// Test ParseEnvFile for a file with a few well formatted lines +func TestParseEnvFileGoodFile(t *testing.T) { + content := `foo=bar + baz=quux +# comment + +_foobar=foobaz +with.dots=working +and_underscore=working too +` + // Adding a newline + a line with pure whitespace. + // This is being done like this instead of the block above + // because it's common for editors to trim trailing whitespace + // from lines, which becomes annoying since that's the + // exact thing we need to test. + content += "\n \t " + tmpFile := tmpFileWithContent(content, t) + defer os.Remove(tmpFile) + + lines, err := ParseEnvFile(tmpFile) + if err != nil { + t.Fatal(err) + } + + expectedLines := []string{ + "foo=bar", + "baz=quux", + "_foobar=foobaz", + "with.dots=working", + "and_underscore=working too", + } + + if !reflect.DeepEqual(lines, expectedLines) { + t.Fatal("lines not equal to expectedLines") + } +} + +// Test ParseEnvFile for an empty file +func TestParseEnvFileEmptyFile(t *testing.T) { + tmpFile := tmpFileWithContent("", t) + defer os.Remove(tmpFile) + + lines, err := ParseEnvFile(tmpFile) + if err != nil { + t.Fatal(err) + } + + if len(lines) != 0 { + t.Fatal("lines not empty; expected empty") + } +} + +// Test ParseEnvFile for a non existent file +func TestParseEnvFileNonExistentFile(t *testing.T) { + _, err := ParseEnvFile("foo_bar_baz") + if err == nil { + t.Fatal("ParseEnvFile succeeded; expected failure") + } + if _, ok := err.(*os.PathError); !ok { + t.Fatalf("Expected a PathError, got [%v]", err) + } +} + +// Test ParseEnvFile for a badly formatted file +func TestParseEnvFileBadlyFormattedFile(t *testing.T) { + content := `foo=bar + f =quux +` + + tmpFile := tmpFileWithContent(content, t) + defer os.Remove(tmpFile) + + _, err := ParseEnvFile(tmpFile) + if err == nil { + t.Fatalf("Expected an ErrBadEnvVariable, got nothing") + } + if _, ok := err.(ErrBadEnvVariable); !ok { + t.Fatalf("Expected an ErrBadEnvVariable, got [%v]", err) + } + expectedMessage := "poorly formatted environment: variable 'f ' has white spaces" + if err.Error() != expectedMessage { + t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) + } +} + +// Test ParseEnvFile for a file with a line exceeding bufio.MaxScanTokenSize +func TestParseEnvFileLineTooLongFile(t *testing.T) { + content := strings.Repeat("a", bufio.MaxScanTokenSize+42) + content = fmt.Sprint("foo=", content) + + tmpFile := tmpFileWithContent(content, t) + defer os.Remove(tmpFile) + + _, err := ParseEnvFile(tmpFile) + if err == nil { + t.Fatal("ParseEnvFile succeeded; expected failure") + } +} + +// ParseEnvFile with a random file, pass through +func TestParseEnvFileRandomFile(t *testing.T) { + content := `first line +another invalid line` + tmpFile := tmpFileWithContent(content, t) + defer os.Remove(tmpFile) + + _, err := ParseEnvFile(tmpFile) + if err == nil { + t.Fatalf("Expected an ErrBadEnvVariable, got nothing") + } + if _, ok := err.(ErrBadEnvVariable); !ok { + t.Fatalf("Expected an ErrBadEnvVariable, got [%v]", err) + } + expectedMessage := "poorly formatted environment: variable 'first line' has white spaces" + if err.Error() != expectedMessage { + t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) + } +} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/opts/parse.go b/components/cli/opts/parse.go similarity index 100% rename from components/cli/vendor/github.com/docker/docker/runconfig/opts/parse.go rename to components/cli/opts/parse.go diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index 83249b101e..0c506e6618 100644 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -7,13 +7,12 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20 github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 -github.com/docker/docker 45c6f4262a865a03adaac291a9ce33c0f2190d77 -github.com/docker/docker-credential-helpers v0.5.0 -github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 +github.com/docker/docker c8141a1fb1ff33b2bfab85a40e5da9a282f36cdc +github.com/docker/docker-credential-helpers v0.5.1 +github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 #? github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894 github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 -github.com/docker/libnetwork b13e0604016a4944025aaff521d9c125850b0d04 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/docker/notary v0.4.2 github.com/docker/swarmkit 1a3e510517be82d18ac04380b5f71eddf06c2fc0 @@ -29,7 +28,7 @@ github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715 github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git -github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d +github.com/opencontainers/selinux v1.0.0-rc1 github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438 diff --git a/components/cli/vendor/github.com/docker/docker-credential-helpers/client/client.go b/components/cli/vendor/github.com/docker/docker-credential-helpers/client/client.go index d0813965b5..d1d0434cb5 100644 --- a/components/cli/vendor/github.com/docker/docker-credential-helpers/client/client.go +++ b/components/cli/vendor/github.com/docker/docker-credential-helpers/client/client.go @@ -9,12 +9,27 @@ import ( "github.com/docker/docker-credential-helpers/credentials" ) +// isValidCredsMessage checks if 'msg' contains invalid credentials error message. +// It returns whether the logs are free of invalid credentials errors and the error if it isn't. +// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername. +func isValidCredsMessage(msg string) error { + if credentials.IsCredentialsMissingServerURLMessage(msg) { + return credentials.NewErrCredentialsMissingServerURL() + } + + if credentials.IsCredentialsMissingUsernameMessage(msg) { + return credentials.NewErrCredentialsMissingUsername() + } + + return nil +} + // Store uses an external program to save credentials. -func Store(program ProgramFunc, credentials *credentials.Credentials) error { +func Store(program ProgramFunc, creds *credentials.Credentials) error { cmd := program("store") buffer := new(bytes.Buffer) - if err := json.NewEncoder(buffer).Encode(credentials); err != nil { + if err := json.NewEncoder(buffer).Encode(creds); err != nil { return err } cmd.Input(buffer) @@ -22,6 +37,11 @@ func Store(program ProgramFunc, credentials *credentials.Credentials) error { out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) + + if isValidErr := isValidCredsMessage(t); isValidErr != nil { + err = isValidErr + } + return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t) } @@ -41,6 +61,10 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error return nil, credentials.NewErrCredentialsNotFound() } + if isValidErr := isValidCredsMessage(t); isValidErr != nil { + err = isValidErr + } + return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t) } @@ -62,6 +86,11 @@ func Erase(program ProgramFunc, serverURL string) error { out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) + + if isValidErr := isValidCredsMessage(t); isValidErr != nil { + err = isValidErr + } + return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t) } @@ -75,6 +104,11 @@ func List(program ProgramFunc) (map[string]string, error) { out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) + + if isValidErr := isValidCredsMessage(t); isValidErr != nil { + err = isValidErr + } + return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t) } diff --git a/components/cli/vendor/github.com/docker/docker-credential-helpers/client/command.go b/components/cli/vendor/github.com/docker/docker-credential-helpers/client/command.go index 8983da6947..a144d5ac18 100644 --- a/components/cli/vendor/github.com/docker/docker-credential-helpers/client/command.go +++ b/components/cli/vendor/github.com/docker/docker-credential-helpers/client/command.go @@ -2,6 +2,7 @@ package client import ( "io" + "os" "os/exec" ) @@ -17,10 +18,16 @@ type ProgramFunc func(args ...string) Program // NewShellProgramFunc creates programs that are executed in a Shell. func NewShellProgramFunc(name string) ProgramFunc { return func(args ...string) Program { - return &Shell{cmd: exec.Command(name, args...)} + return &Shell{cmd: newCmdRedirectErr(name, args)} } } +func newCmdRedirectErr(name string, args []string) *exec.Cmd { + newCmd := exec.Command(name, args...) + newCmd.Stderr = os.Stderr + return newCmd +} + // Shell invokes shell commands to talk with a remote credentials helper. type Shell struct { cmd *exec.Cmd diff --git a/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go b/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go index cfe0cb134e..544ab3c4e1 100644 --- a/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go +++ b/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go @@ -17,6 +17,22 @@ type Credentials struct { Secret string } +// isValid checks the integrity of Credentials object such that no credentials lack +// a server URL or a username. +// It returns whether the credentials are valid and the error if it isn't. +// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername +func (c *Credentials) isValid() (bool, error) { + if len(c.ServerURL) == 0 { + return false, NewErrCredentialsMissingServerURL() + } + + if len(c.Username) == 0 { + return false, NewErrCredentialsMissingUsername() + } + + return true, nil +} + // Docker credentials should be labeled as such in credentials stores that allow labelling. // That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain, // Windows credentials manager and Linux libsecret. Default value is "Docker Credentials" @@ -81,6 +97,10 @@ func Store(helper Helper, reader io.Reader) error { return err } + if ok, err := creds.isValid(); !ok { + return err + } + return helper.Add(&creds) } @@ -100,6 +120,9 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error { } serverURL := strings.TrimSpace(buffer.String()) + if len(serverURL) == 0 { + return NewErrCredentialsMissingServerURL() + } username, secret, err := helper.Get(serverURL) if err != nil { @@ -107,6 +130,7 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error { } resp := Credentials{ + ServerURL: serverURL, Username: username, Secret: secret, } @@ -135,6 +159,9 @@ func Erase(helper Helper, reader io.Reader) error { } serverURL := strings.TrimSpace(buffer.String()) + if len(serverURL) == 0 { + return NewErrCredentialsMissingServerURL() + } return helper.Delete(serverURL) } diff --git a/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/error.go b/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/error.go index d24bf16f09..588d4a83d7 100644 --- a/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/error.go +++ b/components/cli/vendor/github.com/docker/docker-credential-helpers/credentials/error.go @@ -1,8 +1,15 @@ package credentials -// ErrCredentialsNotFound standarizes the not found error, so every helper returns -// the same message and docker can handle it properly. -const errCredentialsNotFoundMessage = "credentials not found in native keychain" +const ( + // ErrCredentialsNotFound standardizes the not found error, so every helper returns + // the same message and docker can handle it properly. + errCredentialsNotFoundMessage = "credentials not found in native keychain" + + // ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize + // invalid credentials or credentials management operations + errCredentialsMissingServerURLMessage = "no credentials server URL" + errCredentialsMissingUsernameMessage = "no credentials username" +) // errCredentialsNotFound represents an error // raised when credentials are not in the store. @@ -35,3 +42,64 @@ func IsErrCredentialsNotFound(err error) bool { func IsErrCredentialsNotFoundMessage(err string) bool { return err == errCredentialsNotFoundMessage } + + +// errCredentialsMissingServerURL represents an error raised +// when the credentials object has no server URL or when no +// server URL is provided to a credentials operation requiring +// one. +type errCredentialsMissingServerURL struct{} + +func (errCredentialsMissingServerURL) Error() string { + return errCredentialsMissingServerURLMessage +} + +// errCredentialsMissingUsername represents an error raised +// when the credentials object has no username or when no +// username is provided to a credentials operation requiring +// one. +type errCredentialsMissingUsername struct{} + +func (errCredentialsMissingUsername) Error() string { + return errCredentialsMissingUsernameMessage +} + + +// NewErrCredentialsMissingServerURL creates a new error for +// errCredentialsMissingServerURL. +func NewErrCredentialsMissingServerURL() error { + return errCredentialsMissingServerURL{} +} + +// NewErrCredentialsMissingUsername creates a new error for +// errCredentialsMissingUsername. +func NewErrCredentialsMissingUsername() error { + return errCredentialsMissingUsername{} +} + + +// IsCredentialsMissingServerURL returns true if the error +// was an errCredentialsMissingServerURL. +func IsCredentialsMissingServerURL(err error) bool { + _, ok := err.(errCredentialsMissingServerURL) + return ok +} + +// IsCredentialsMissingServerURLMessage checks for an +// errCredentialsMissingServerURL in the error message. +func IsCredentialsMissingServerURLMessage(err string) bool { + return err == errCredentialsMissingServerURLMessage +} + +// IsCredentialsMissingUsername returns true if the error +// was an errCredentialsMissingUsername. +func IsCredentialsMissingUsername(err error) bool { + _, ok := err.(errCredentialsMissingUsername) + return ok +} + +// IsCredentialsMissingUsernameMessage checks for an +// errCredentialsMissingUsername in the error message. +func IsCredentialsMissingUsernameMessage(err string) bool { + return err == errCredentialsMissingUsernameMessage +} diff --git a/components/cli/vendor/github.com/docker/docker/api/types/swarm/task.go b/components/cli/vendor/github.com/docker/docker/api/types/swarm/task.go index 9fd52e47d0..a598a79d59 100644 --- a/components/cli/vendor/github.com/docker/docker/api/types/swarm/task.go +++ b/components/cli/vendor/github.com/docker/docker/api/types/swarm/task.go @@ -67,9 +67,6 @@ type TaskSpec struct { ForceUpdate uint64 Runtime RuntimeType `json:",omitempty"` - // TODO (ehazlett): this should be removed and instead - // use struct tags (proto) for the runtimes - RuntimeData []byte `json:",omitempty"` } // Resources represents resources (CPU/Memory). diff --git a/components/cli/vendor/github.com/docker/docker/api/types/types.go b/components/cli/vendor/github.com/docker/docker/api/types/types.go index fa9f1b0de1..c905466e29 100644 --- a/components/cli/vendor/github.com/docker/docker/api/types/types.go +++ b/components/cli/vendor/github.com/docker/docker/api/types/types.go @@ -277,7 +277,7 @@ type Health struct { // ContainerState stores container's running state // it's part of ContainerJSONBase and will return by "inspect" command type ContainerState struct { - Status string + Status string // String representation of the container state. Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead" Running bool Paused bool Restarting bool diff --git a/components/cli/vendor/github.com/docker/docker/api/types/volume.go b/components/cli/vendor/github.com/docker/docker/api/types/volume.go index da4f8ebd9c..a69b0cfb17 100644 --- a/components/cli/vendor/github.com/docker/docker/api/types/volume.go +++ b/components/cli/vendor/github.com/docker/docker/api/types/volume.go @@ -7,6 +7,9 @@ package types // swagger:model Volume type Volume struct { + // Time volume was created. + CreatedAt string `json:"CreatedAt,omitempty"` + // Name of the volume driver used by the volume. // Required: true Driver string `json:"Driver"` diff --git a/components/cli/vendor/github.com/docker/docker/pkg/gitutils/gitutils.go b/components/cli/vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go similarity index 99% rename from components/cli/vendor/github.com/docker/docker/pkg/gitutils/gitutils.go rename to components/cli/vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go index 4e09e05239..fd8250dbc3 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/gitutils/gitutils.go +++ b/components/cli/vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go @@ -1,4 +1,4 @@ -package gitutils +package git import ( "fmt" diff --git a/components/cli/vendor/github.com/docker/docker/client/client.go b/components/cli/vendor/github.com/docker/docker/client/client.go index f8f2fc6ad5..34897b9549 100644 --- a/components/cli/vendor/github.com/docker/docker/client/client.go +++ b/components/cli/vendor/github.com/docker/docker/client/client.go @@ -247,6 +247,12 @@ func (cli *Client) UpdateClientVersion(v string) { } +// DaemonHost returns the host associated with this instance of the Client. +// This operation doesn't acquire a mutex. +func (cli *Client) DaemonHost() string { + return cli.host +} + // ParseHost verifies that the given host strings is valid. func ParseHost(host string) (string, string, string, error) { protoAddrParts := strings.SplitN(host, "://", 2) diff --git a/components/cli/vendor/github.com/docker/docker/client/container_wait.go b/components/cli/vendor/github.com/docker/docker/client/container_wait.go index edfa5d3c85..13b5eea6a9 100644 --- a/components/cli/vendor/github.com/docker/docker/client/container_wait.go +++ b/components/cli/vendor/github.com/docker/docker/client/container_wait.go @@ -10,7 +10,7 @@ import ( "github.com/docker/docker/api/types/versions" ) -// ContainerWait waits until the specified continer is in a certain state +// ContainerWait waits until the specified container is in a certain state // indicated by the given condition, either "not-running" (default), // "next-exit", or "removed". // @@ -31,7 +31,7 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit } resultC := make(chan container.ContainerWaitOKBody) - errC := make(chan error) + errC := make(chan error, 1) query := url.Values{} query.Set("condition", string(condition)) diff --git a/components/cli/vendor/github.com/docker/docker/client/interface.go b/components/cli/vendor/github.com/docker/docker/client/interface.go index 678a69ddf7..d5f3a14ec3 100644 --- a/components/cli/vendor/github.com/docker/docker/client/interface.go +++ b/components/cli/vendor/github.com/docker/docker/client/interface.go @@ -31,6 +31,7 @@ type CommonAPIClient interface { SystemAPIClient VolumeAPIClient ClientVersion() string + DaemonHost() string ServerVersion(ctx context.Context) (types.Version, error) UpdateClientVersion(v string) } diff --git a/components/cli/vendor/github.com/docker/docker/client/service_create.go b/components/cli/vendor/github.com/docker/docker/client/service_create.go index 73fc68fb33..71a7583e86 100644 --- a/components/cli/vendor/github.com/docker/docker/client/service_create.go +++ b/components/cli/vendor/github.com/docker/docker/client/service_create.go @@ -24,14 +24,18 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth} } + // ensure that the image is tagged + if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" { + service.TaskTemplate.ContainerSpec.Image = taggedImg + } + // Contact the registry to retrieve digest and platform information if options.QueryRegistry { distributionInspect, err := cli.DistributionInspect(ctx, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth) distErr = err if err == nil { // now pin by digest if the image doesn't already contain a digest - img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest) - if img != "" { + if img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest); img != "" { service.TaskTemplate.ContainerSpec.Image = img } // add platforms that are compatible with the service @@ -55,25 +59,33 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, } // imageWithDigestString takes an image string and a digest, and updates -// the image string if it didn't originally contain a digest. It assumes -// that the image string is not an image ID +// the image string if it didn't originally contain a digest. It returns +// an empty string if there are no updates. func imageWithDigestString(image string, dgst digest.Digest) string { - isCanonical := false - ref, err := reference.ParseAnyReference(image) + namedRef, err := reference.ParseNormalizedNamed(image) if err == nil { - _, isCanonical = ref.(reference.Canonical) - - if !isCanonical { - namedRef, _ := ref.(reference.Named) + if _, isCanonical := namedRef.(reference.Canonical); !isCanonical { + // ensure that image gets a default tag if none is provided img, err := reference.WithDigest(namedRef, dgst) if err == nil { - return img.String() + return reference.FamiliarString(img) } } } return "" } +// imageWithTagString takes an image string, and returns a tagged image +// string, adding a 'latest' tag if one was not provided. It returns an +// emptry string if a canonical reference was provided +func imageWithTagString(image string) string { + namedRef, err := reference.ParseNormalizedNamed(image) + if err == nil { + return reference.FamiliarString(reference.TagNameOnly(namedRef)) + } + return "" +} + // updateServicePlatforms updates the Platforms in swarm.Placement to list // all compatible platforms for the service, as found in distributionInspect // and returns a pointer to the new or updated swarm.Placement struct diff --git a/components/cli/vendor/github.com/docker/docker/client/service_update.go b/components/cli/vendor/github.com/docker/docker/client/service_update.go index 0cb35a1991..412790b5dd 100644 --- a/components/cli/vendor/github.com/docker/docker/client/service_update.go +++ b/components/cli/vendor/github.com/docker/docker/client/service_update.go @@ -35,6 +35,11 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version query.Set("version", strconv.FormatUint(version.Index, 10)) + // ensure that the image is tagged + if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" { + service.TaskTemplate.ContainerSpec.Image = taggedImg + } + // Contact the registry to retrieve digest and platform information // This happens only when the image has changed if options.QueryRegistry { @@ -42,8 +47,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version distErr = err if err == nil { // now pin by digest if the image doesn't already contain a digest - img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest) - if img != "" { + if img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest); img != "" { service.TaskTemplate.ContainerSpec.Image = img } // add platforms that are compatible with the service diff --git a/components/cli/vendor/github.com/docker/docker/opts/config.go b/components/cli/vendor/github.com/docker/docker/opts/config.go deleted file mode 100644 index 82fd2bce4e..0000000000 --- a/components/cli/vendor/github.com/docker/docker/opts/config.go +++ /dev/null @@ -1,98 +0,0 @@ -package opts - -import ( - "encoding/csv" - "fmt" - "os" - "strconv" - "strings" - - swarmtypes "github.com/docker/docker/api/types/swarm" -) - -// ConfigOpt is a Value type for parsing configs -type ConfigOpt struct { - values []*swarmtypes.ConfigReference -} - -// Set a new config value -func (o *ConfigOpt) Set(value string) error { - csvReader := csv.NewReader(strings.NewReader(value)) - fields, err := csvReader.Read() - if err != nil { - return err - } - - options := &swarmtypes.ConfigReference{ - File: &swarmtypes.ConfigReferenceFileTarget{ - UID: "0", - GID: "0", - Mode: 0444, - }, - } - - // support a simple syntax of --config foo - if len(fields) == 1 { - options.File.Name = fields[0] - options.ConfigName = fields[0] - o.values = append(o.values, options) - return nil - } - - for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - key := strings.ToLower(parts[0]) - - if len(parts) != 2 { - return fmt.Errorf("invalid field '%s' must be a key=value pair", field) - } - - value := parts[1] - switch key { - case "source", "src": - options.ConfigName = value - case "target": - options.File.Name = value - case "uid": - options.File.UID = value - case "gid": - options.File.GID = value - case "mode": - m, err := strconv.ParseUint(value, 0, 32) - if err != nil { - return fmt.Errorf("invalid mode specified: %v", err) - } - - options.File.Mode = os.FileMode(m) - default: - return fmt.Errorf("invalid field in config request: %s", key) - } - } - - if options.ConfigName == "" { - return fmt.Errorf("source is required") - } - - o.values = append(o.values, options) - return nil -} - -// Type returns the type of this option -func (o *ConfigOpt) Type() string { - return "config" -} - -// String returns a string repr of this option -func (o *ConfigOpt) String() string { - configs := []string{} - for _, config := range o.values { - repr := fmt.Sprintf("%s -> %s", config.ConfigName, config.File.Name) - configs = append(configs, repr) - } - return strings.Join(configs, ", ") -} - -// Value returns the config requests -func (o *ConfigOpt) Value() []*swarmtypes.ConfigReference { - return o.values -} diff --git a/components/cli/vendor/github.com/docker/docker/opts/opts.go b/components/cli/vendor/github.com/docker/docker/opts/opts.go index f76f308051..8d82f76792 100644 --- a/components/cli/vendor/github.com/docker/docker/opts/opts.go +++ b/components/cli/vendor/github.com/docker/docker/opts/opts.go @@ -2,13 +2,11 @@ package opts import ( "fmt" - "math/big" "net" "path" "regexp" "strings" - "github.com/docker/docker/api/types/filters" units "github.com/docker/go-units" ) @@ -236,15 +234,6 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } -// ValidateMACAddress validates a MAC address. -func ValidateMACAddress(val string) (string, error) { - _, err := net.ParseMAC(strings.TrimSpace(val)) - if err != nil { - return "", err - } - return val, nil -} - // ValidateDNSSearch validates domain for resolvconf search configuration. // A zero length domain is represented by a dot (.). func ValidateDNSSearch(val string) (string, error) { @@ -274,114 +263,6 @@ func ValidateLabel(val string) (string, error) { return val, nil } -// ValidateSysctl validates a sysctl and returns it. -func ValidateSysctl(val string) (string, error) { - validSysctlMap := map[string]bool{ - "kernel.msgmax": true, - "kernel.msgmnb": true, - "kernel.msgmni": true, - "kernel.sem": true, - "kernel.shmall": true, - "kernel.shmmax": true, - "kernel.shmmni": true, - "kernel.shm_rmid_forced": true, - } - validSysctlPrefixes := []string{ - "net.", - "fs.mqueue.", - } - arr := strings.Split(val, "=") - if len(arr) < 2 { - return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) - } - if validSysctlMap[arr[0]] { - return val, nil - } - - for _, vp := range validSysctlPrefixes { - if strings.HasPrefix(arr[0], vp) { - return val, nil - } - } - return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) -} - -// FilterOpt is a flag type for validating filters -type FilterOpt struct { - filter filters.Args -} - -// NewFilterOpt returns a new FilterOpt -func NewFilterOpt() FilterOpt { - return FilterOpt{filter: filters.NewArgs()} -} - -func (o *FilterOpt) String() string { - repr, err := filters.ToParam(o.filter) - if err != nil { - return "invalid filters" - } - return repr -} - -// Set sets the value of the opt by parsing the command line value -func (o *FilterOpt) Set(value string) error { - var err error - o.filter, err = filters.ParseFlag(value, o.filter) - return err -} - -// Type returns the option type -func (o *FilterOpt) Type() string { - return "filter" -} - -// Value returns the value of this option -func (o *FilterOpt) Value() filters.Args { - return o.filter -} - -// NanoCPUs is a type for fixed point fractional number. -type NanoCPUs int64 - -// String returns the string format of the number -func (c *NanoCPUs) String() string { - if *c == 0 { - return "" - } - return big.NewRat(c.Value(), 1e9).FloatString(3) -} - -// Set sets the value of the NanoCPU by passing a string -func (c *NanoCPUs) Set(value string) error { - cpus, err := ParseCPUs(value) - *c = NanoCPUs(cpus) - return err -} - -// Type returns the type -func (c *NanoCPUs) Type() string { - return "decimal" -} - -// Value returns the value in int64 -func (c *NanoCPUs) Value() int64 { - return int64(*c) -} - -// ParseCPUs takes a string ratio and returns an integer value of nano cpus -func ParseCPUs(value string) (int64, error) { - cpu, ok := new(big.Rat).SetString(value) - if !ok { - return 0, fmt.Errorf("failed to parse %v as a rational number", value) - } - nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) - if !nano.IsInt() { - return 0, fmt.Errorf("value is too precise") - } - return nano.Num().Int64(), nil -} - // ParseLink parses and validates the specified string as a link format (name:alias) func ParseLink(val string) (string, string, error) { if val == "" { @@ -404,12 +285,6 @@ func ParseLink(val string) (string, string, error) { return arr[0], arr[1], nil } -// ValidateLink validates that the specified string has a valid link format (containerName:alias). -func ValidateLink(val string) (string, error) { - _, _, err := ParseLink(val) - return val, err -} - // MemBytes is a type for human readable memory bytes (like 128M, 2g, etc) type MemBytes int64 @@ -450,39 +325,3 @@ func (m *MemBytes) UnmarshalJSON(s []byte) error { *m = MemBytes(val) return err } - -// MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc). -// It differs from MemBytes in that -1 is valid and the default. -type MemSwapBytes int64 - -// Set sets the value of the MemSwapBytes by passing a string -func (m *MemSwapBytes) Set(value string) error { - if value == "-1" { - *m = MemSwapBytes(-1) - return nil - } - val, err := units.RAMInBytes(value) - *m = MemSwapBytes(val) - return err -} - -// Type returns the type -func (m *MemSwapBytes) Type() string { - return "bytes" -} - -// Value returns the value in int64 -func (m *MemSwapBytes) Value() int64 { - return int64(*m) -} - -func (m *MemSwapBytes) String() string { - b := MemBytes(*m) - return b.String() -} - -// UnmarshalJSON is the customized unmarshaler for MemSwapBytes -func (m *MemSwapBytes) UnmarshalJSON(s []byte) error { - b := MemBytes(*m) - return b.UnmarshalJSON(s) -} diff --git a/components/cli/vendor/github.com/docker/docker/opts/throttledevice.go b/components/cli/vendor/github.com/docker/docker/opts/throttledevice.go deleted file mode 100644 index 65dd3ebf68..0000000000 --- a/components/cli/vendor/github.com/docker/docker/opts/throttledevice.go +++ /dev/null @@ -1,111 +0,0 @@ -package opts - -import ( - "fmt" - "strconv" - "strings" - - "github.com/docker/docker/api/types/blkiodev" - "github.com/docker/go-units" -) - -// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. -type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) - -// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. -func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := units.RAMInBytes(split[1]) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - - return &blkiodev.ThrottleDevice{ - Path: split[0], - Rate: uint64(rate), - }, nil -} - -// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. -func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := strconv.ParseUint(split[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) - } - - return &blkiodev.ThrottleDevice{ - Path: split[0], - Rate: uint64(rate), - }, nil -} - -// ThrottledeviceOpt defines a map of ThrottleDevices -type ThrottledeviceOpt struct { - values []*blkiodev.ThrottleDevice - validator ValidatorThrottleFctType -} - -// NewThrottledeviceOpt creates a new ThrottledeviceOpt -func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { - values := []*blkiodev.ThrottleDevice{} - return ThrottledeviceOpt{ - values: values, - validator: validator, - } -} - -// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt -func (opt *ThrottledeviceOpt) Set(val string) error { - var value *blkiodev.ThrottleDevice - if opt.validator != nil { - v, err := opt.validator(val) - if err != nil { - return err - } - value = v - } - (opt.values) = append((opt.values), value) - return nil -} - -// String returns ThrottledeviceOpt values as a string. -func (opt *ThrottledeviceOpt) String() string { - var out []string - for _, v := range opt.values { - out = append(out, v.String()) - } - - return fmt.Sprintf("%v", out) -} - -// GetList returns a slice of pointers to ThrottleDevices. -func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { - var throttledevice []*blkiodev.ThrottleDevice - throttledevice = append(throttledevice, opt.values...) - - return throttledevice -} - -// Type returns the option type -func (opt *ThrottledeviceOpt) Type() string { - return "list" -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/httputils/httputils.go b/components/cli/vendor/github.com/docker/docker/pkg/httputils/httputils.go deleted file mode 100644 index af86835bd9..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/httputils/httputils.go +++ /dev/null @@ -1,56 +0,0 @@ -package httputils - -import ( - "errors" - "fmt" - "net/http" - "regexp" - "strings" - - "github.com/docker/docker/pkg/jsonmessage" -) - -var ( - headerRegexp = regexp.MustCompile(`^(?:(.+)/(.+?))\((.+)\).*$`) - errInvalidHeader = errors.New("Bad header, should be in format `docker/version (platform)`") -) - -// Download requests a given URL and returns an io.Reader. -func Download(url string) (resp *http.Response, err error) { - if resp, err = http.Get(url); err != nil { - return nil, err - } - if resp.StatusCode >= 400 { - return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status) - } - return resp, nil -} - -// NewHTTPRequestError returns a JSON response error. -func NewHTTPRequestError(msg string, res *http.Response) error { - return &jsonmessage.JSONError{ - Message: msg, - Code: res.StatusCode, - } -} - -// ServerHeader contains the server information. -type ServerHeader struct { - App string // docker - Ver string // 1.8.0-dev - OS string // windows or linux -} - -// ParseServerHeader extracts pieces from an HTTP server header -// which is in the format "docker/version (os)" e.g. docker/1.8.0-dev (windows). -func ParseServerHeader(hdr string) (*ServerHeader, error) { - matches := headerRegexp.FindStringSubmatch(hdr) - if len(matches) != 4 { - return nil, errInvalidHeader - } - return &ServerHeader{ - App: strings.TrimSpace(matches[1]), - Ver: strings.TrimSpace(matches[2]), - OS: strings.TrimSpace(matches[3]), - }, nil -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/httputils/mimetype.go b/components/cli/vendor/github.com/docker/docker/pkg/httputils/mimetype.go deleted file mode 100644 index abef9e9e83..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/httputils/mimetype.go +++ /dev/null @@ -1,29 +0,0 @@ -package httputils - -import ( - "mime" - "net/http" -) - -// MimeTypes stores the MIME content type. -var MimeTypes = struct { - TextPlain string - OctetStream string -}{"text/plain", "application/octet-stream"} - -// DetectContentType returns a best guess representation of the MIME -// content type for the bytes at c. The value detected by -// http.DetectContentType is guaranteed not be nil, defaulting to -// application/octet-stream when a better guess cannot be made. The -// result of this detection is then run through mime.ParseMediaType() -// which separates the actual MIME string from any parameters. -func DetectContentType(c []byte) (string, map[string]string, error) { - - ct := http.DetectContentType(c) - contentType, args, err := mime.ParseMediaType(ct) - if err != nil { - return "", nil, err - } - - return contentType, args, nil -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/ioutils/multireader.go b/components/cli/vendor/github.com/docker/docker/pkg/ioutils/multireader.go deleted file mode 100644 index edb043ddc3..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/ioutils/multireader.go +++ /dev/null @@ -1,224 +0,0 @@ -package ioutils - -import ( - "bytes" - "fmt" - "io" - "os" -) - -type pos struct { - idx int - offset int64 -} - -type multiReadSeeker struct { - readers []io.ReadSeeker - pos *pos - posIdx map[io.ReadSeeker]int -} - -func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) { - var tmpOffset int64 - switch whence { - case os.SEEK_SET: - for i, rdr := range r.readers { - // get size of the current reader - s, err := rdr.Seek(0, os.SEEK_END) - if err != nil { - return -1, err - } - - if offset > tmpOffset+s { - if i == len(r.readers)-1 { - rdrOffset := s + (offset - tmpOffset) - if _, err := rdr.Seek(rdrOffset, os.SEEK_SET); err != nil { - return -1, err - } - r.pos = &pos{i, rdrOffset} - return offset, nil - } - - tmpOffset += s - continue - } - - rdrOffset := offset - tmpOffset - idx := i - - rdr.Seek(rdrOffset, os.SEEK_SET) - // make sure all following readers are at 0 - for _, rdr := range r.readers[i+1:] { - rdr.Seek(0, os.SEEK_SET) - } - - if rdrOffset == s && i != len(r.readers)-1 { - idx++ - rdrOffset = 0 - } - r.pos = &pos{idx, rdrOffset} - return offset, nil - } - case os.SEEK_END: - for _, rdr := range r.readers { - s, err := rdr.Seek(0, os.SEEK_END) - if err != nil { - return -1, err - } - tmpOffset += s - } - r.Seek(tmpOffset+offset, os.SEEK_SET) - return tmpOffset + offset, nil - case os.SEEK_CUR: - if r.pos == nil { - return r.Seek(offset, os.SEEK_SET) - } - // Just return the current offset - if offset == 0 { - return r.getCurOffset() - } - - curOffset, err := r.getCurOffset() - if err != nil { - return -1, err - } - rdr, rdrOffset, err := r.getReaderForOffset(curOffset + offset) - if err != nil { - return -1, err - } - - r.pos = &pos{r.posIdx[rdr], rdrOffset} - return curOffset + offset, nil - default: - return -1, fmt.Errorf("Invalid whence: %d", whence) - } - - return -1, fmt.Errorf("Error seeking for whence: %d, offset: %d", whence, offset) -} - -func (r *multiReadSeeker) getReaderForOffset(offset int64) (io.ReadSeeker, int64, error) { - - var offsetTo int64 - - for _, rdr := range r.readers { - size, err := getReadSeekerSize(rdr) - if err != nil { - return nil, -1, err - } - if offsetTo+size > offset { - return rdr, offset - offsetTo, nil - } - if rdr == r.readers[len(r.readers)-1] { - return rdr, offsetTo + offset, nil - } - offsetTo += size - } - - return nil, 0, nil -} - -func (r *multiReadSeeker) getCurOffset() (int64, error) { - var totalSize int64 - for _, rdr := range r.readers[:r.pos.idx+1] { - if r.posIdx[rdr] == r.pos.idx { - totalSize += r.pos.offset - break - } - - size, err := getReadSeekerSize(rdr) - if err != nil { - return -1, fmt.Errorf("error getting seeker size: %v", err) - } - totalSize += size - } - return totalSize, nil -} - -func (r *multiReadSeeker) getOffsetToReader(rdr io.ReadSeeker) (int64, error) { - var offset int64 - for _, r := range r.readers { - if r == rdr { - break - } - - size, err := getReadSeekerSize(rdr) - if err != nil { - return -1, err - } - offset += size - } - return offset, nil -} - -func (r *multiReadSeeker) Read(b []byte) (int, error) { - if r.pos == nil { - // make sure all readers are at 0 - r.Seek(0, os.SEEK_SET) - } - - bLen := int64(len(b)) - buf := bytes.NewBuffer(nil) - var rdr io.ReadSeeker - - for _, rdr = range r.readers[r.pos.idx:] { - readBytes, err := io.CopyN(buf, rdr, bLen) - if err != nil && err != io.EOF { - return -1, err - } - bLen -= readBytes - - if bLen == 0 { - break - } - } - - rdrPos, err := rdr.Seek(0, os.SEEK_CUR) - if err != nil { - return -1, err - } - r.pos = &pos{r.posIdx[rdr], rdrPos} - return buf.Read(b) -} - -func getReadSeekerSize(rdr io.ReadSeeker) (int64, error) { - // save the current position - pos, err := rdr.Seek(0, os.SEEK_CUR) - if err != nil { - return -1, err - } - - // get the size - size, err := rdr.Seek(0, os.SEEK_END) - if err != nil { - return -1, err - } - - // reset the position - if _, err := rdr.Seek(pos, os.SEEK_SET); err != nil { - return -1, err - } - return size, nil -} - -// MultiReadSeeker returns a ReadSeeker that's the logical concatenation of the provided -// input readseekers. After calling this method the initial position is set to the -// beginning of the first ReadSeeker. At the end of a ReadSeeker, Read always advances -// to the beginning of the next ReadSeeker and returns EOF at the end of the last ReadSeeker. -// Seek can be used over the sum of lengths of all readseekers. -// -// When a MultiReadSeeker is used, no Read and Seek operations should be made on -// its ReadSeeker components. Also, users should make no assumption on the state -// of individual readseekers while the MultiReadSeeker is used. -func MultiReadSeeker(readers ...io.ReadSeeker) io.ReadSeeker { - if len(readers) == 1 { - return readers[0] - } - idx := make(map[io.ReadSeeker]int) - for i, rdr := range readers { - idx[rdr] = i - } - return &multiReadSeeker{ - readers: readers, - posIdx: idx, - } -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go b/components/cli/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go index ebbded77c8..50234affc0 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go @@ -13,7 +13,7 @@ import ( import "C" // Termios is the Unix API for terminal I/O. -// It is passthrough for syscall.Termios in order to make it portable with +// It is passthrough for unix.Termios in order to make it portable with // other platforms where it is not available or handled differently. type Termios unix.Termios @@ -28,11 +28,11 @@ func MakeRaw(fd uintptr) (*State, error) { newState := oldState.termios - newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON | syscall.IXANY) - newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) - newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) - newState.Cflag |= syscall.CS8 + newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON | unix.IXANY) + newState.Oflag &^= unix.OPOST + newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) + newState.Cflag &^= (unix.CSIZE | unix.PARENB) + newState.Cflag |= unix.CS8 /* VMIN is the minimum number of characters that needs to be read in non-canonical mode for it to be returned diff --git a/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go b/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go index c47341e873..9fdc720f25 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go @@ -27,7 +27,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState := oldState.termios newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) - newState.Oflag &^= unix.OPOST + newState.Oflag |= unix.OPOST newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) newState.Cflag &^= (unix.CSIZE | unix.PARENB) newState.Cflag |= unix.CS8 diff --git a/components/cli/vendor/github.com/docker/docker/pkg/httputils/resumablerequestreader.go b/components/cli/vendor/github.com/docker/docker/registry/resumable/resumablerequestreader.go similarity index 66% rename from components/cli/vendor/github.com/docker/docker/pkg/httputils/resumablerequestreader.go rename to components/cli/vendor/github.com/docker/docker/registry/resumable/resumablerequestreader.go index de488fb531..5403c76847 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/httputils/resumablerequestreader.go +++ b/components/cli/vendor/github.com/docker/docker/registry/resumable/resumablerequestreader.go @@ -1,4 +1,4 @@ -package httputils +package resumable import ( "fmt" @@ -9,7 +9,7 @@ import ( "github.com/Sirupsen/logrus" ) -type resumableRequestReader struct { +type requestReader struct { client *http.Client request *http.Request lastRange int64 @@ -20,22 +20,22 @@ type resumableRequestReader struct { waitDuration time.Duration } -// ResumableRequestReader makes it possible to resume reading a request's body transparently +// NewRequestReader makes it possible to resume reading a request's body transparently // maxfail is the number of times we retry to make requests again (not resumes) // totalsize is the total length of the body; auto detect if not provided -func ResumableRequestReader(c *http.Client, r *http.Request, maxfail uint32, totalsize int64) io.ReadCloser { - return &resumableRequestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, waitDuration: 5 * time.Second} +func NewRequestReader(c *http.Client, r *http.Request, maxfail uint32, totalsize int64) io.ReadCloser { + return &requestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, waitDuration: 5 * time.Second} } -// ResumableRequestReaderWithInitialResponse makes it possible to resume +// NewRequestReaderWithInitialResponse makes it possible to resume // reading the body of an already initiated request. -func ResumableRequestReaderWithInitialResponse(c *http.Client, r *http.Request, maxfail uint32, totalsize int64, initialResponse *http.Response) io.ReadCloser { - return &resumableRequestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, currentResponse: initialResponse, waitDuration: 5 * time.Second} +func NewRequestReaderWithInitialResponse(c *http.Client, r *http.Request, maxfail uint32, totalsize int64, initialResponse *http.Response) io.ReadCloser { + return &requestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, currentResponse: initialResponse, waitDuration: 5 * time.Second} } -func (r *resumableRequestReader) Read(p []byte) (n int, err error) { +func (r *requestReader) Read(p []byte) (n int, err error) { if r.client == nil || r.request == nil { - return 0, fmt.Errorf("client and request can't be nil\n") + return 0, fmt.Errorf("client and request can't be nil") } isFreshRequest := false if r.lastRange != 0 && r.currentResponse == nil { @@ -81,14 +81,14 @@ func (r *resumableRequestReader) Read(p []byte) (n int, err error) { return n, err } -func (r *resumableRequestReader) Close() error { +func (r *requestReader) Close() error { r.cleanUpResponse() r.client = nil r.request = nil return nil } -func (r *resumableRequestReader) cleanUpResponse() { +func (r *requestReader) cleanUpResponse() { if r.currentResponse != nil { r.currentResponse.Body.Close() r.currentResponse = nil diff --git a/components/cli/vendor/github.com/docker/docker/registry/session.go b/components/cli/vendor/github.com/docker/docker/registry/session.go index c71e778037..746b8fb5b5 100644 --- a/components/cli/vendor/github.com/docker/docker/registry/session.go +++ b/components/cli/vendor/github.com/docker/docker/registry/session.go @@ -23,10 +23,11 @@ import ( "github.com/docker/distribution/registry/api/errcode" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/tarsum" + "github.com/docker/docker/registry/resumable" ) var ( @@ -226,7 +227,7 @@ func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) { if res.StatusCode == 401 { return nil, errcode.ErrorCodeUnauthorized.WithArgs() } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) + return nil, newJSONError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } var history []string @@ -246,7 +247,7 @@ func (r *Session) LookupRemoteImage(imgID, registry string) error { } res.Body.Close() if res.StatusCode != 200 { - return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) + return newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } return nil } @@ -259,7 +260,7 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int64, err } defer res.Body.Close() if res.StatusCode != 200 { - return nil, -1, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) + return nil, -1, newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } // if the size header is not present, then set it to '-1' imageSize := int64(-1) @@ -313,7 +314,7 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { logrus.Debug("server supports resume") - return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil + return resumable.NewRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil } logrus.Debug("server doesn't support resume") return res.Body, nil @@ -444,13 +445,13 @@ func (r *Session) GetRepositoryData(name reference.Named) (*RepositoryData, erro // TODO: Right now we're ignoring checksums in the response body. // In the future, we need to use them to check image validity. if res.StatusCode == 404 { - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) + return nil, newJSONError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) } else if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, reference.Path(name), errBody), res) + return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, reference.Path(name), errBody), res) } var endpoints []string @@ -537,12 +538,12 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist } defer res.Body.Close() if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") { - return httputils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) + return newJSONError("HTTP code 401, Docker will not send auth headers over HTTP.", res) } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { @@ -550,7 +551,7 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist } else if jsonBody["error"] == "Image already exists" { return ErrAlreadyExists } - return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) + return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) } return nil } @@ -591,9 +592,9 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return "", "", newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } - return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) + return "", "", newJSONError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) } checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) @@ -619,7 +620,7 @@ func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registr } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { - return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, reference.Path(remote)), res) + return newJSONError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, reference.Path(remote)), res) } return nil } @@ -683,7 +684,7 @@ func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, reference.Path(remote), errBody), res) + return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, reference.Path(remote), errBody), res) } tokens = res.Header["X-Docker-Token"] logrus.Debugf("Auth token: %v", tokens) @@ -701,7 +702,7 @@ func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, reference.Path(remote), errBody), res) + return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, reference.Path(remote), errBody), res) } } @@ -750,7 +751,7 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea } defer res.Body.Close() if res.StatusCode != 200 { - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) + return nil, newJSONError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) } result := new(registrytypes.SearchResults) return result, json.NewDecoder(res.Body).Decode(result) @@ -781,3 +782,10 @@ func isTimeout(err error) bool { t, ok := e.(timeout) return ok && t.Timeout() } + +func newJSONError(msg string, res *http.Response) error { + return &jsonmessage.JSONError{ + Message: msg, + Code: res.StatusCode, + } +} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go index e8eede1505..24aed1935e 100644 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go +++ b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go @@ -45,7 +45,7 @@ func validateNetContainerMode(c *container.Config, hc *container.HostConfig) err parts := strings.Split(string(hc.NetworkMode), ":") if parts[0] == "container" { if len(parts) < 2 || parts[1] == "" { - return fmt.Errorf("--net: invalid net mode: invalid container format container:") + return fmt.Errorf("Invalid network mode: invalid container format container:") } } diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go index a60daa8787..55df5da3ff 100644 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go +++ b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go @@ -55,7 +55,7 @@ func validateIsolation(hc *container.HostConfig) error { return nil } if !hc.Isolation.IsValid() { - return fmt.Errorf("invalid --isolation: %q - %s only supports 'default'", hc.Isolation, runtime.GOOS) + return fmt.Errorf("Invalid isolation: %q - %s only supports 'default'", hc.Isolation, runtime.GOOS) } return nil } @@ -68,11 +68,11 @@ func validateQoS(hc *container.HostConfig) error { } if hc.IOMaximumBandwidth != 0 { - return fmt.Errorf("invalid QoS settings: %s does not support --io-maxbandwidth", runtime.GOOS) + return fmt.Errorf("Invalid QoS settings: %s does not support configuration of maximum bandwidth", runtime.GOOS) } if hc.IOMaximumIOps != 0 { - return fmt.Errorf("invalid QoS settings: %s does not support --io-maxiops", runtime.GOOS) + return fmt.Errorf("Invalid QoS settings: %s does not support configuration of maximum IOPs", runtime.GOOS) } return nil } @@ -86,15 +86,15 @@ func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error { } if hc.Resources.CPURealtimePeriod > 0 && !si.CPURealtimePeriod { - return fmt.Errorf("invalid --cpu-rt-period: Your kernel does not support cgroup rt period") + return fmt.Errorf("Your kernel does not support cgroup cpu real-time period") } if hc.Resources.CPURealtimeRuntime > 0 && !si.CPURealtimeRuntime { - return fmt.Errorf("invalid --cpu-rt-runtime: Your kernel does not support cgroup rt runtime") + return fmt.Errorf("Your kernel does not support cgroup cpu real-time runtime") } if hc.Resources.CPURealtimePeriod != 0 && hc.Resources.CPURealtimeRuntime != 0 && hc.Resources.CPURealtimeRuntime > hc.Resources.CPURealtimePeriod { - return fmt.Errorf("invalid --cpu-rt-runtime: rt runtime cannot be higher than rt period") + return fmt.Errorf("cpu real-time runtime cannot be higher than cpu real-time period") } return nil } diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go index 9ca93ae508..5eb956d1b4 100644 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go +++ b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go @@ -31,7 +31,7 @@ func validateNetMode(c *container.Config, hc *container.HostConfig) error { } if hc.NetworkMode.IsContainer() && hc.Isolation.IsHyperV() { - return fmt.Errorf("net mode --net=container: unsupported for hyperv isolation") + return fmt.Errorf("Using the network stack of another container is not supported while using Hyper-V Containers") } return nil @@ -46,7 +46,7 @@ func validateIsolation(hc *container.HostConfig) error { return nil } if !hc.Isolation.IsValid() { - return fmt.Errorf("invalid --isolation: %q. Windows supports 'default', 'process', or 'hyperv'", hc.Isolation) + return fmt.Errorf("Invalid isolation: %q. Windows supports 'default', 'process', or 'hyperv'", hc.Isolation) } return nil } @@ -63,10 +63,10 @@ func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error { return nil } if hc.Resources.CPURealtimePeriod != 0 { - return fmt.Errorf("invalid --cpu-rt-period: Windows does not support this feature") + return fmt.Errorf("Windows does not support CPU real-time period") } if hc.Resources.CPURealtimeRuntime != 0 { - return fmt.Errorf("invalid --cpu-rt-runtime: Windows does not support this feature") + return fmt.Errorf("Windows does not support CPU real-time runtime") } return nil } @@ -78,7 +78,7 @@ func validatePrivileged(hc *container.HostConfig) error { return nil } if hc.Privileged { - return fmt.Errorf("invalid --privileged: Windows does not support this feature") + return fmt.Errorf("Windows does not support privileged mode") } return nil } @@ -90,7 +90,7 @@ func validateReadonlyRootfs(hc *container.HostConfig) error { return nil } if hc.ReadonlyRootfs { - return fmt.Errorf("invalid --read-only: Windows does not support this feature") + return fmt.Errorf("Windows does not support root filesystem in read-only mode") } return nil } diff --git a/components/cli/vendor/github.com/docker/docker/vendor.conf b/components/cli/vendor/github.com/docker/docker/vendor.conf index df4aa7e1e8..35708e168c 100644 --- a/components/cli/vendor/github.com/docker/docker/vendor.conf +++ b/components/cli/vendor/github.com/docker/docker/vendor.conf @@ -1,7 +1,7 @@ # the following lines are in sorted order, FYI github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62 github.com/Microsoft/hcsshim v0.5.17 -github.com/Microsoft/go-winio v0.4.0 +github.com/Microsoft/go-winio v0.4.2 github.com/Sirupsen/logrus v0.11.0 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a @@ -26,7 +26,7 @@ github.com/imdario/mergo 0.2.1 golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 #get libnetwork packages -github.com/docker/libnetwork b2bc1a68486ccf8ada503162d9f0df7d31bdd8fb +github.com/docker/libnetwork 83e1e49475b88a9f1f8ba89a690a7d5de42e24b9 github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec @@ -62,8 +62,8 @@ github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f # When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly github.com/opencontainers/runc 992a5be178a62e026f4069f443c6164912adbf09 -github.com/opencontainers/runtime-spec v1.0.0-rc5 # specs github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe +github.com/opencontainers/runtime-spec d42f1eb741e6361e858d83fc75aa6893b66292c4 # specs github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 @@ -99,15 +99,12 @@ cloud.google.com/go 9d965e63e8cceb1b5d7977a202f0fcb8866d6525 github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 google.golang.org/genproto b3e7c2fb04031add52c4817f53f43757ccbf9c18 -# native credentials -github.com/docker/docker-credential-helpers v0.5.0 - # containerd github.com/containerd/containerd 3addd840653146c90a254301d6c3a663c7fd6429 github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 # cluster -github.com/docker/swarmkit 1a3e510517be82d18ac04380b5f71eddf06c2fc0 +github.com/docker/swarmkit eb07af52aa2216100cff1ad0b13df48daa8914bf github.com/gogo/protobuf v0.4 github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume.go b/components/cli/vendor/github.com/docker/docker/volume/volume.go index 5135605281..ef362370e5 100644 --- a/components/cli/vendor/github.com/docker/docker/volume/volume.go +++ b/components/cli/vendor/github.com/docker/docker/volume/volume.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "syscall" + "time" mounttypes "github.com/docker/docker/api/types/mount" "github.com/docker/docker/pkg/idtools" @@ -64,6 +65,8 @@ type Volume interface { Mount(id string) (string, error) // Unmount unmounts the volume when it is no longer in use. Unmount(id string) error + // CreatedAt returns Volume Creation time + CreatedAt() (time.Time, error) // Status returns low-level status information about a volume Status() map[string]interface{} } diff --git a/components/cli/vendor/github.com/docker/libnetwork/LICENSE b/components/cli/vendor/github.com/docker/libnetwork/LICENSE deleted file mode 100644 index e06d208186..0000000000 --- a/components/cli/vendor/github.com/docker/libnetwork/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/components/cli/vendor/github.com/docker/libnetwork/README.md b/components/cli/vendor/github.com/docker/libnetwork/README.md deleted file mode 100644 index 536f8aa2b3..0000000000 --- a/components/cli/vendor/github.com/docker/libnetwork/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# libnetwork - networking for containers - -[![Circle CI](https://circleci.com/gh/docker/libnetwork/tree/master.svg?style=svg)](https://circleci.com/gh/docker/libnetwork/tree/master) [![Coverage Status](https://coveralls.io/repos/docker/libnetwork/badge.svg)](https://coveralls.io/r/docker/libnetwork) [![GoDoc](https://godoc.org/github.com/docker/libnetwork?status.svg)](https://godoc.org/github.com/docker/libnetwork) - -Libnetwork provides a native Go implementation for connecting containers - -The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications. - -#### Design -Please refer to the [design](docs/design.md) for more information. - -#### Using libnetwork - -There are many networking solutions available to suit a broad range of use-cases. libnetwork uses a driver / plugin model to support all of these solutions while abstracting the complexity of the driver implementations by exposing a simple and consistent Network Model to users. - - -```go -func main() { - if reexec.Init() { - return - } - - // Select and configure the network driver - networkType := "bridge" - - // Create a new controller instance - driverOptions := options.Generic{} - genericOption := make(map[string]interface{}) - genericOption[netlabel.GenericData] = driverOptions - controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption)) - if err != nil { - log.Fatalf("libnetwork.New: %s", err) - } - - // Create a network for containers to join. - // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use. - network, err := controller.NewNetwork(networkType, "network1", "") - if err != nil { - log.Fatalf("controller.NewNetwork: %s", err) - } - - // For each new container: allocate IP and interfaces. The returned network - // settings will be used for container infos (inspect and such), as well as - // iptables rules for port publishing. This info is contained or accessible - // from the returned endpoint. - ep, err := network.CreateEndpoint("Endpoint1") - if err != nil { - log.Fatalf("network.CreateEndpoint: %s", err) - } - - // Create the sandbox for the container. - // NewSandbox accepts Variadic optional arguments which libnetwork can use. - sbx, err := controller.NewSandbox("container1", - libnetwork.OptionHostname("test"), - libnetwork.OptionDomainname("docker.io")) - if err != nil { - log.Fatalf("controller.NewSandbox: %s", err) - } - - // A sandbox can join the endpoint via the join api. - err = ep.Join(sbx) - if err != nil { - log.Fatalf("ep.Join: %s", err) - } - - // libnetwork client can check the endpoint's operational data via the Info() API - epInfo, err := ep.DriverInfo() - if err != nil { - log.Fatalf("ep.DriverInfo: %s", err) - } - - macAddress, ok := epInfo[netlabel.MacAddress] - if !ok { - log.Fatalf("failed to get mac address from endpoint info") - } - - fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key()) -} -``` - -## Future -Please refer to [roadmap](ROADMAP.md) for more information. - -## Contributing - -Want to hack on libnetwork? [Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) apply. - -## Copyright and license -Code and documentation copyright 2015 Docker, inc. Code released under the Apache 2.0 license. Docs released under Creative commons. diff --git a/components/cli/vendor/github.com/docker/libnetwork/resolvconf/README.md b/components/cli/vendor/github.com/docker/libnetwork/resolvconf/README.md deleted file mode 100644 index cdda554ba5..0000000000 --- a/components/cli/vendor/github.com/docker/libnetwork/resolvconf/README.md +++ /dev/null @@ -1 +0,0 @@ -Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf diff --git a/components/cli/vendor/github.com/docker/libnetwork/resolvconf/dns/resolvconf.go b/components/cli/vendor/github.com/docker/libnetwork/resolvconf/dns/resolvconf.go deleted file mode 100644 index e348bc57f5..0000000000 --- a/components/cli/vendor/github.com/docker/libnetwork/resolvconf/dns/resolvconf.go +++ /dev/null @@ -1,26 +0,0 @@ -package dns - -import ( - "regexp" -) - -// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range. -const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` - -// IPv4Localhost is a regex pattern for IPv4 localhost address range. -const IPv4Localhost = `(127\.([0-9]{1,3}\.){2}[0-9]{1,3})` - -var localhostIPRegexp = regexp.MustCompile(IPLocalhost) -var localhostIPv4Regexp = regexp.MustCompile(IPv4Localhost) - -// IsLocalhost returns true if ip matches the localhost IP regular expression. -// Used for determining if nameserver settings are being passed which are -// localhost addresses -func IsLocalhost(ip string) bool { - return localhostIPRegexp.MatchString(ip) -} - -// IsIPv4Localhost returns true if ip matches the IPv4 localhost regular expression. -func IsIPv4Localhost(ip string) bool { - return localhostIPv4Regexp.MatchString(ip) -} diff --git a/components/cli/vendor/github.com/docker/libnetwork/vendor.conf b/components/cli/vendor/github.com/docker/libnetwork/vendor.conf deleted file mode 100644 index 45f2784192..0000000000 --- a/components/cli/vendor/github.com/docker/libnetwork/vendor.conf +++ /dev/null @@ -1,42 +0,0 @@ -github.com/Azure/go-ansiterm 04b7f292a41fcb5da32dda536c0807fc13e8351c -github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 -github.com/Microsoft/go-winio ce2922f643c8fd76b46cadc7f404a06282678b34 -github.com/Microsoft/hcsshim e439b7d2b63f036d3a50c93a9e0b154a0d50e788 -github.com/Sirupsen/logrus 4b6ea7319e214d98c938f12692336f7ca9348d6b -github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec -github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 -github.com/boltdb/bolt c6ba97b89e0454fec9aa92e1d33a4e2c5fc1f631 -github.com/codegangsta/cli a65b733b303f0055f8d324d805f393cd3e7a7904 -github.com/coreos/etcd 925d1d74cec8c3b169c52fd4b2dc234a35934fce -github.com/coreos/go-systemd b4a58d95188dd092ae20072bac14cece0e67c388 -github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d - -github.com/docker/docker 9c96768eae4b3a65147b47a55c850c103ab8972d -github.com/docker/go-connections 34b5052da6b11e27f5f2e357b38b571ddddd3928 -github.com/docker/go-events 2e7d352816128aa84f4d29b2a21d400133701a0d -github.com/docker/go-units 8e2d4523730c73120e10d4652f36ad6010998f4e -github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef - -github.com/godbus/dbus 5f6efc7ef2759c81b7ba876593971bfce311eab3 -github.com/gogo/protobuf 8d70fb3182befc465c4a1eac8ad4d38ff49778e2 -github.com/golang/protobuf/proto f7137ae6b19afbfd61a94b746fda3b3fe0491874 -github.com/gorilla/context 215affda49addc4c8ef7e2534915df2c8c35c6cd -github.com/gorilla/mux 8096f47503459bcc74d1f4c487b7e6e42e5746b5 -github.com/hashicorp/consul/api 954aec66231b79c161a4122b023fbcad13047f79 -github.com/hashicorp/go-msgpack/codec 71c2886f5a673a35f909803f38ece5810165097b -github.com/hashicorp/go-multierror 2167c8ec40776024589f483a6b836489e47e1049 -github.com/hashicorp/memberlist 88ac4de0d1a0ca6def284b571342db3b777a4c37 -github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 -github.com/mattn/go-shellwords 525bedee691b5a8df547cb5cf9f86b7fb1883e24 -github.com/miekg/dns d27455715200c7d3e321a1e5cadb27c9ee0b0f02 -github.com/opencontainers/runc/libcontainer ba1568de399395774ad84c2ace65937814c542ed -github.com/samuel/go-zookeeper/zk d0e0d8e11f318e000a8cc434616d69e329edc374 -github.com/seccomp/libseccomp-golang 1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1 -github.com/stretchr/testify dab07ac62d4905d3e48d17dc549c684ac3b7c15a -github.com/syndtr/gocapability/capability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 -github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 -github.com/vishvananda/netlink 1e86b2bee5b6a7d377e4c02bb7f98209d6a7297c -github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 -golang.org/x/net c427ad74c6d7a814201695e9ffde0c5d400a7674 -golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 -github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 From 9b7bef68dd4380ee9da22f6018323a1aee2b7eba Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Tue, 6 Jun 2017 04:59:50 +0000 Subject: [PATCH 10/67] fix manpages script Signed-off-by: Tibor Vass Upstream-commit: dc81def89cc46d664bbd427b54992f8650bd63c2 Component: cli --- components/cli/Makefile | 2 +- components/cli/scripts/docs/generate-man.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/cli/Makefile b/components/cli/Makefile index dda6df868f..dbb3d3bfef 100644 --- a/components/cli/Makefile +++ b/components/cli/Makefile @@ -47,7 +47,7 @@ vendor: vendor.conf ## generate man pages from go source and markdown .PHONY: manpages manpages: - scripts/dos/generate-man.sh + scripts/docs/generate-man.sh ## generate documentation YAML files consumed by docs repo .PHONY: yamldocs diff --git a/components/cli/scripts/docs/generate-man.sh b/components/cli/scripts/docs/generate-man.sh index e955130559..28fea4e1ce 100755 --- a/components/cli/scripts/docs/generate-man.sh +++ b/components/cli/scripts/docs/generate-man.sh @@ -18,7 +18,7 @@ MD2MAN_COMMIT=$(grep -F "$MD2MAN_REPO" vendor.conf | cut -d' ' -f2) ) # Generate man pages from cobra commands -go build -o /tmp/gen-manpages ./man +go build -o /tmp/gen-manpages github.com/docker/cli/man /tmp/gen-manpages --root . --target ./man/man1 # Generate legacy pages from markdown From 3b7dc655ab11fa4cbe61d9b94492054d24ed974e Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 6 Jun 2017 15:02:28 -0400 Subject: [PATCH 11/67] Fix check-git-diff so that it fails on CI Signed-off-by: Daniel Nephin Upstream-commit: f75a44ffd8170e727067875b443bdb3e88b6438e Component: cli --- components/cli/circle.yml | 1 + components/cli/scripts/validate/check-git-diff | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/cli/circle.yml b/components/cli/circle.yml index dabc4c5aad..9985274b43 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -45,6 +45,7 @@ jobs: if [ "$CIRCLE_NODE_INDEX" != "3" ]; then exit; fi dockerfile=dockerfiles/Dockerfile.dev echo "COPY . ." >> $dockerfile + rm -f .dockerignore # include .git docker build -f $dockerfile --tag cli-builder . docker run cli-builder make -B vendor compose-jsonschema diff --git a/components/cli/scripts/validate/check-git-diff b/components/cli/scripts/validate/check-git-diff index e41970a35a..4cdc0d2db6 100755 --- a/components/cli/scripts/validate/check-git-diff +++ b/components/cli/scripts/validate/check-git-diff @@ -3,12 +3,13 @@ set -eu DIFF_PATH=$1 +DIFF=$(git status --porcelain -- $DIFF_PATH) -if [ "`git status --porcelain -- $DIFF_PATH 2>/dev/null`" ]; then +if [ "$DIFF" ]; then echo echo "These files were changed:" echo - git status --porcelain -- $DIFF_PATH 2>/dev/null + echo $DIFF echo exit 1 else From d8fa98915a63db4c9602dbcc9868f13355613636 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 7 Jun 2017 13:26:24 +0200 Subject: [PATCH 12/67] Update codecov project threshold to 15% That way, the build will fail less. Signed-off-by: Vincent Demeester Upstream-commit: a1327339394a994556423218936b93d1de5d3b0c Component: cli --- components/cli/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/codecov.yml b/components/cli/codecov.yml index 82e1ed8018..ec7d4e2127 100644 --- a/components/cli/codecov.yml +++ b/components/cli/codecov.yml @@ -11,7 +11,7 @@ coverage: project: default: target: auto - threshold: "0.05%" + threshold: "15%" changes: false ignore: - "**/internal/test" From 85d6b4b742dd061ae98786ca12fa622f701a04d6 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 7 Jun 2017 17:02:46 +0200 Subject: [PATCH 13/67] Handle case of configs on old daemon If configs are declared for a service and pointing on an old daemon, error out properly (instead of "page not found"). If there is no configs declared, don't call convertServiceConfigObjs to avoid having an error. Signed-off-by: Vincent Demeester Upstream-commit: cf5550c4260bcea0f733c7abfc6bdf0188357eea Component: cli --- components/cli/cli/command/service/parse.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/cli/cli/command/service/parse.go b/components/cli/cli/command/service/parse.go index f3c9306881..6f69cbb47f 100644 --- a/components/cli/cli/command/service/parse.go +++ b/components/cli/cli/command/service/parse.go @@ -12,6 +12,10 @@ import ( // ParseSecrets retrieves the secrets with the requested names and fills // secret IDs into the secret references. func ParseSecrets(client client.SecretAPIClient, requestedSecrets []*swarmtypes.SecretReference) ([]*swarmtypes.SecretReference, error) { + if len(requestedSecrets) == 0 { + return []*swarmtypes.SecretReference{}, nil + } + secretRefs := make(map[string]*swarmtypes.SecretReference) ctx := context.Background() @@ -61,6 +65,10 @@ func ParseSecrets(client client.SecretAPIClient, requestedSecrets []*swarmtypes. // ParseConfigs retrieves the configs from the requested names and converts // them to config references to use with the spec func ParseConfigs(client client.ConfigAPIClient, requestedConfigs []*swarmtypes.ConfigReference) ([]*swarmtypes.ConfigReference, error) { + if len(requestedConfigs) == 0 { + return []*swarmtypes.ConfigReference{}, nil + } + configRefs := make(map[string]*swarmtypes.ConfigReference) ctx := context.Background() From e0f9d48624c28a5a8420e2971e63fb0c4e244f1b Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Wed, 7 Jun 2017 09:40:43 -0700 Subject: [PATCH 14/67] Clarify ability to attach multiple times Signed-off-by: Misty Stanley-Jones Upstream-commit: b5d33c6e76e381dce664be6c40209fb467b43765 Component: cli --- components/cli/docs/reference/commandline/attach.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docs/reference/commandline/attach.md b/components/cli/docs/reference/commandline/attach.md index 4153331bad..7e654d7350 100644 --- a/components/cli/docs/reference/commandline/attach.md +++ b/components/cli/docs/reference/commandline/attach.md @@ -40,7 +40,7 @@ interactively, as though the commands were running directly in your terminal. > not be interacting with the terminal at that time. You can attach to the same contained process multiple times simultaneously, -even as a different user with the appropriate permissions. +from different sessions on the Docker host. To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to From e0b0f9479ef2863a2b91940333d5f8077e88b805 Mon Sep 17 00:00:00 2001 From: Nishant Totla Date: Mon, 22 May 2017 14:06:36 -0700 Subject: [PATCH 15/67] Enable client side digest pinning for stack deploy Signed-off-by: Nishant Totla Upstream-commit: 9f1bea2657e1830313ebe4d82e0037bc660a7f73 Component: cli --- components/cli/cli/command/stack/deploy.go | 9 ++++++++ .../cli/command/stack/deploy_bundlefile.go | 2 +- .../cli/command/stack/deploy_composefile.go | 21 ++++++++++++++++--- components/cli/cli/compose/convert/service.go | 6 ++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/components/cli/cli/command/stack/deploy.go b/components/cli/cli/command/stack/deploy.go index d18a43484d..a5edd2bd67 100644 --- a/components/cli/cli/command/stack/deploy.go +++ b/components/cli/cli/command/stack/deploy.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/compose/convert" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/versions" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -21,6 +22,7 @@ type deployOptions struct { composefile string namespace string sendRegistryAuth bool + noResolveImage bool prune bool } @@ -44,12 +46,19 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { addRegistryAuthFlag(&opts.sendRegistryAuth, flags) flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced") flags.SetAnnotation("prune", "version", []string{"1.27"}) + flags.BoolVar(&opts.noResolveImage, "no-resolve-image", false, "Do not query the registry to resolve image digest and supported platforms") + flags.SetAnnotation("no-resolve-image", "version", []string{"1.30"}) return cmd } func runDeploy(dockerCli command.Cli, opts deployOptions) error { ctx := context.Background() + // image resolution should not happen for clients older than v1.30 + if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") { + opts.noResolveImage = true + } + switch { case opts.bundlefile == "" && opts.composefile == "": return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).") diff --git a/components/cli/cli/command/stack/deploy_bundlefile.go b/components/cli/cli/command/stack/deploy_bundlefile.go index 2f2a9aa042..9c3ba25954 100644 --- a/components/cli/cli/command/stack/deploy_bundlefile.go +++ b/components/cli/cli/command/stack/deploy_bundlefile.go @@ -87,5 +87,5 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { return err } - return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth) + return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.noResolveImage) } diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index 145da67daa..297594dfee 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -92,7 +92,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption if err != nil { return err } - return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth) + return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.noResolveImage) } func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} { @@ -283,6 +283,7 @@ func deployServices( services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, + noResolveImage bool, ) error { apiClient := dockerCli.Client() out := dockerCli.Out() @@ -301,9 +302,9 @@ func deployServices( name := namespace.Scope(internalName) encodedAuth := "" + image := serviceSpec.TaskTemplate.ContainerSpec.Image if sendAuth { // Retrieve encoded auth token from the image reference - image := serviceSpec.TaskTemplate.ContainerSpec.Image encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image) if err != nil { return err @@ -313,12 +314,20 @@ func deployServices( if service, exists := existingServiceMap[name]; exists { fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID) + updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} + + if image != service.Spec.Labels["com.docker.stack.image"] { + if !noResolveImage { + updateOpts.QueryRegistry = true + } + } + response, err := apiClient.ServiceUpdate( ctx, service.ID, service.Version, serviceSpec, - types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}, + updateOpts, ) if err != nil { return errors.Wrapf(err, "failed to update service %s", name) @@ -331,6 +340,12 @@ func deployServices( fmt.Fprintf(out, "Creating service %s\n", name) createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth} + + // query registry if flag disabling it was not set + if !noResolveImage { + createOpts.QueryRegistry = true + } + if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil { return errors.Wrapf(err, "failed to create service %s", name) } diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 0fef936a22..cf919488f9 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -45,6 +45,12 @@ func Services( if err != nil { return nil, errors.Wrapf(err, "service %s", service.Name) } + // add an image label to serviceSpec + if serviceSpec.Labels == nil { + serviceSpec.Labels = make(map[string]string) + } + serviceSpec.Labels["com.docker.stack.image"] = service.Image + result[service.Name] = serviceSpec } From 31ea0d23c1a6e24b08f615c1c4ae60949418ddc8 Mon Sep 17 00:00:00 2001 From: Nishant Totla Date: Fri, 2 Jun 2017 16:21:41 -0700 Subject: [PATCH 16/67] Change --no-resolve-image flag to --resolve-image string flag Signed-off-by: Nishant Totla Upstream-commit: f790e839fc7d669acafa6365ca7a83cbedfe9e2d Component: cli --- components/cli/cli/command/stack/deploy.go | 29 +++++++++++++++---- .../cli/command/stack/deploy_bundlefile.go | 2 +- .../cli/command/stack/deploy_composefile.go | 12 ++++---- components/cli/cli/compose/convert/service.go | 15 +++++----- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/components/cli/cli/command/stack/deploy.go b/components/cli/cli/command/stack/deploy.go index a5edd2bd67..8e14b70d2f 100644 --- a/components/cli/cli/command/stack/deploy.go +++ b/components/cli/cli/command/stack/deploy.go @@ -15,14 +15,17 @@ import ( const ( defaultNetworkDriver = "overlay" + resolveImageAlways = "always" + resolveImageChanged = "changed" + resolveImageNever = "never" ) type deployOptions struct { bundlefile string composefile string namespace string + resolveImage string sendRegistryAuth bool - noResolveImage bool prune bool } @@ -46,17 +49,17 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { addRegistryAuthFlag(&opts.sendRegistryAuth, flags) flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced") flags.SetAnnotation("prune", "version", []string{"1.27"}) - flags.BoolVar(&opts.noResolveImage, "no-resolve-image", false, "Do not query the registry to resolve image digest and supported platforms") - flags.SetAnnotation("no-resolve-image", "version", []string{"1.30"}) + flags.StringVar(&opts.resolveImage, "resolve-image", resolveImageAlways, + `Query the registry to resolve image digest and supported platforms ("`+resolveImageAlways+`"|"`+resolveImageChanged+`"|"`+resolveImageNever+`")`) + flags.SetAnnotation("resolve-image", "version", []string{"1.30"}) return cmd } func runDeploy(dockerCli command.Cli, opts deployOptions) error { ctx := context.Background() - // image resolution should not happen for clients older than v1.30 - if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") { - opts.noResolveImage = true + if err := validateResolveImageFlag(dockerCli, &opts); err != nil { + return err } switch { @@ -71,6 +74,20 @@ func runDeploy(dockerCli command.Cli, opts deployOptions) error { } } +// validateResolveImageFlag validates the opts.resolveImage command line option +// and also turns image resolution off if the version is older than 1.30 +func validateResolveImageFlag(dockerCli command.Cli, opts *deployOptions) error { + if opts.resolveImage != resolveImageAlways && opts.resolveImage != resolveImageChanged && opts.resolveImage != resolveImageNever { + return errors.Errorf("Invalid option %s for flag --resolve-image", opts.resolveImage) + } + // client side image resolution should not be done when the supported + // server version is older than 1.30 + if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") { + opts.resolveImage = resolveImageNever + } + return nil +} + // checkDaemonIsSwarmManager does an Info API call to verify that the daemon is // a swarm manager. This is necessary because we must create networks before we // create services, but the API call for creating a network does not return a diff --git a/components/cli/cli/command/stack/deploy_bundlefile.go b/components/cli/cli/command/stack/deploy_bundlefile.go index 9c3ba25954..1074210e97 100644 --- a/components/cli/cli/command/stack/deploy_bundlefile.go +++ b/components/cli/cli/command/stack/deploy_bundlefile.go @@ -87,5 +87,5 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { return err } - return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.noResolveImage) + return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.resolveImage) } diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index 297594dfee..1a2266af80 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -92,7 +92,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption if err != nil { return err } - return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.noResolveImage) + return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.resolveImage) } func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} { @@ -283,7 +283,7 @@ func deployServices( services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, - noResolveImage bool, + resolveImage string, ) error { apiClient := dockerCli.Client() out := dockerCli.Out() @@ -316,10 +316,8 @@ func deployServices( updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} - if image != service.Spec.Labels["com.docker.stack.image"] { - if !noResolveImage { - updateOpts.QueryRegistry = true - } + if resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]) { + updateOpts.QueryRegistry = true } response, err := apiClient.ServiceUpdate( @@ -342,7 +340,7 @@ func deployServices( createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth} // query registry if flag disabling it was not set - if !noResolveImage { + if resolveImage == resolveImageAlways || resolveImage == resolveImageChanged { createOpts.QueryRegistry = true } diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index cf919488f9..6d78be9c8b 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -17,7 +17,11 @@ import ( "github.com/pkg/errors" ) -const defaultNetwork = "default" +const ( + defaultNetwork = "default" + // LabelImage is the label used to store image name provided in the compose file + LabelImage = "com.docker.stack.image" +) // Services from compose-file types to engine API types func Services( @@ -45,12 +49,6 @@ func Services( if err != nil { return nil, errors.Wrapf(err, "service %s", service.Name) } - // add an image label to serviceSpec - if serviceSpec.Labels == nil { - serviceSpec.Labels = make(map[string]string) - } - serviceSpec.Labels["com.docker.stack.image"] = service.Image - result[service.Name] = serviceSpec } @@ -163,6 +161,9 @@ func convertService( UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig), } + // add an image label to serviceSpec + serviceSpec.Labels[LabelImage] = service.Image + // ServiceSpec.Networks is deprecated and should not have been used by // this package. It is possible to update TaskTemplate.Networks, but it // is not possible to update ServiceSpec.Networks. Unfortunately, we From 8d3a4135a1c1e0226a823efa699bf4554c3523c2 Mon Sep 17 00:00:00 2001 From: Shukui Yang Date: Thu, 8 Jun 2017 07:03:52 +0800 Subject: [PATCH 17/67] Replace command.DockerCli to command.Cli in docker attach/exec command Signed-off-by: Shukui Yang Upstream-commit: e8cc2cf7604aaa8c0f318292bc3be7eb5f60c16d Component: cli --- .../cli/cli/command/container/attach.go | 5 +- .../cli/cli/command/container/attach_test.go | 62 +++++++++++++++++++ .../cli/cli/command/container/client_test.go | 19 ++++++ components/cli/cli/command/container/exec.go | 4 +- .../cli/cli/command/container/exec_test.go | 34 ++++++++++ components/cli/cli/command/container/tty.go | 4 +- components/cli/cli/command/container/utils.go | 2 +- 7 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 components/cli/cli/command/container/attach_test.go create mode 100644 components/cli/cli/command/container/client_test.go diff --git a/components/cli/cli/command/container/attach.go b/components/cli/cli/command/container/attach.go index e8abb1c748..d9aa3db8c0 100644 --- a/components/cli/cli/command/container/attach.go +++ b/components/cli/cli/command/container/attach.go @@ -34,11 +34,12 @@ func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, ar if c.State.Paused { return nil, errors.New("You cannot attach to a paused container, unpause it first") } + return &c, nil } // NewAttachCommand creates a new cobra.Command for `docker attach` -func NewAttachCommand(dockerCli *command.DockerCli) *cobra.Command { +func NewAttachCommand(dockerCli command.Cli) *cobra.Command { var opts attachOptions cmd := &cobra.Command{ @@ -58,7 +59,7 @@ func NewAttachCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runAttach(dockerCli *command.DockerCli, opts *attachOptions) error { +func runAttach(dockerCli command.Cli, opts *attachOptions) error { ctx := context.Background() client := dockerCli.Client() diff --git a/components/cli/cli/command/container/attach_test.go b/components/cli/cli/command/container/attach_test.go new file mode 100644 index 0000000000..8e29f3b076 --- /dev/null +++ b/components/cli/cli/command/container/attach_test.go @@ -0,0 +1,62 @@ +package container + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/docker/cli/cli/internal/test" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/testutil" + "github.com/pkg/errors" +) + +func TestNewAttachCommandErrors(t *testing.T) { + testCases := []struct { + name string + args []string + expectedError string + containerInspectFunc func(img string) (types.ContainerJSON, error) + }{ + { + name: "client-error", + args: []string{"5cb5bb5e4a3b"}, + expectedError: "something went wrong", + containerInspectFunc: func(containerID string) (types.ContainerJSON, error) { + return types.ContainerJSON{}, errors.Errorf("something went wrong") + }, + }, + { + name: "client-stopped", + args: []string{"5cb5bb5e4a3b"}, + expectedError: "You cannot attach to a stopped container", + containerInspectFunc: func(containerID string) (types.ContainerJSON, error) { + c := types.ContainerJSON{} + c.ContainerJSONBase = &types.ContainerJSONBase{} + c.ContainerJSONBase.State = &types.ContainerState{Running: false} + return c, nil + }, + }, + { + name: "client-paused", + args: []string{"5cb5bb5e4a3b"}, + expectedError: "You cannot attach to a paused container", + containerInspectFunc: func(containerID string) (types.ContainerJSON, error) { + c := types.ContainerJSON{} + c.ContainerJSONBase = &types.ContainerJSONBase{} + c.ContainerJSONBase.State = &types.ContainerState{ + Running: true, + Paused: true, + } + return c, nil + }, + }, + } + for _, tc := range testCases { + buf := new(bytes.Buffer) + cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}, buf)) + cmd.SetOutput(ioutil.Discard) + cmd.SetArgs(tc.args) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} diff --git a/components/cli/cli/command/container/client_test.go b/components/cli/cli/command/container/client_test.go new file mode 100644 index 0000000000..1e2d5d9cf8 --- /dev/null +++ b/components/cli/cli/command/container/client_test.go @@ -0,0 +1,19 @@ +package container + +import ( + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "golang.org/x/net/context" +) + +type fakeClient struct { + client.Client + containerInspectFunc func(string) (types.ContainerJSON, error) +} + +func (cli *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) { + if cli.containerInspectFunc != nil { + return cli.containerInspectFunc(containerID) + } + return types.ContainerJSON{}, nil +} diff --git a/components/cli/cli/command/container/exec.go b/components/cli/cli/command/container/exec.go index 587cc00f2f..843b609a0e 100644 --- a/components/cli/cli/command/container/exec.go +++ b/components/cli/cli/command/container/exec.go @@ -33,7 +33,7 @@ func newExecOptions() *execOptions { } // NewExecCommand creates a new cobra.Command for `docker exec` -func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command { +func NewExecCommand(dockerCli command.Cli) *cobra.Command { options := newExecOptions() cmd := &cobra.Command{ @@ -63,7 +63,7 @@ func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command { } // nolint: gocyclo -func runExec(dockerCli *command.DockerCli, options *execOptions, container string, execCmd []string) error { +func runExec(dockerCli command.Cli, options *execOptions, container string, execCmd []string) error { execConfig, err := parseExec(options, execCmd) // just in case the ParseExec does not exit if container == "" || err != nil { diff --git a/components/cli/cli/command/container/exec_test.go b/components/cli/cli/command/container/exec_test.go index e94beb4545..b2df1c2b0c 100644 --- a/components/cli/cli/command/container/exec_test.go +++ b/components/cli/cli/command/container/exec_test.go @@ -1,9 +1,15 @@ package container import ( + "bytes" + "io/ioutil" "testing" + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/internal/test" "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/testutil" + "github.com/pkg/errors" ) type arguments struct { @@ -114,3 +120,31 @@ func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) boo } return true } + +func TestNewExecCommandErrors(t *testing.T) { + testCases := []struct { + name string + args []string + expectedError string + containerInspectFunc func(img string) (types.ContainerJSON, error) + }{ + { + name: "client-error", + args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"}, + expectedError: "something went wrong", + containerInspectFunc: func(containerID string) (types.ContainerJSON, error) { + return types.ContainerJSON{}, errors.Errorf("something went wrong") + }, + }, + } + for _, tc := range testCases { + buf := new(bytes.Buffer) + conf := configfile.ConfigFile{} + cli := test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}, buf) + cli.SetConfigfile(&conf) + cmd := NewExecCommand(cli) + cmd.SetOutput(ioutil.Discard) + cmd.SetArgs(tc.args) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} diff --git a/components/cli/cli/command/container/tty.go b/components/cli/cli/command/container/tty.go index 9b09d4a0f9..fe123eb108 100644 --- a/components/cli/cli/command/container/tty.go +++ b/components/cli/cli/command/container/tty.go @@ -39,7 +39,7 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin } // MonitorTtySize updates the container tty size when the terminal tty changes size -func MonitorTtySize(ctx context.Context, cli *command.DockerCli, id string, isExec bool) error { +func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error { resizeTty := func() { height, width := cli.Out().GetTtySize() resizeTtyTo(ctx, cli.Client(), id, height, width, isExec) @@ -74,7 +74,7 @@ func MonitorTtySize(ctx context.Context, cli *command.DockerCli, id string, isEx } // ForwardAllSignals forwards signals to the container -func ForwardAllSignals(ctx context.Context, cli *command.DockerCli, cid string) chan os.Signal { +func ForwardAllSignals(ctx context.Context, cli command.Cli, cid string) chan os.Signal { sigc := make(chan os.Signal, 128) signal.CatchAll(sigc) go func() { diff --git a/components/cli/cli/command/container/utils.go b/components/cli/cli/command/container/utils.go index fbb38e8557..d9afe24163 100644 --- a/components/cli/cli/command/container/utils.go +++ b/components/cli/cli/command/container/utils.go @@ -127,7 +127,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, // getExitCode performs an inspect on the container. It returns // the running state and the exit code. -func getExitCode(ctx context.Context, dockerCli *command.DockerCli, containerID string) (bool, int, error) { +func getExitCode(ctx context.Context, dockerCli command.Cli, containerID string) (bool, int, error) { c, err := dockerCli.Client().ContainerInspect(ctx, containerID) if err != nil { // If we can't connect, then the daemon probably died. From b190ae1082cd3d0775e3beb7118e574e03849030 Mon Sep 17 00:00:00 2001 From: Shukui Yang Date: Thu, 8 Jun 2017 07:12:39 +0800 Subject: [PATCH 18/67] Add a restarting check to runAttach Signed-off-by: Shukui Yang Upstream-commit: 90f497302fa3b12aa1db19b3f538262714a5d2ac Component: cli --- components/cli/cli/command/container/attach.go | 3 +++ .../cli/cli/command/container/attach_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/components/cli/cli/command/container/attach.go b/components/cli/cli/command/container/attach.go index d9aa3db8c0..dce6432846 100644 --- a/components/cli/cli/command/container/attach.go +++ b/components/cli/cli/command/container/attach.go @@ -34,6 +34,9 @@ func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, ar if c.State.Paused { return nil, errors.New("You cannot attach to a paused container, unpause it first") } + if c.State.Restarting { + return nil, errors.New("You cannot attach to a restarting container, wait until it is running") + } return &c, nil } diff --git a/components/cli/cli/command/container/attach_test.go b/components/cli/cli/command/container/attach_test.go index 8e29f3b076..14b8137dae 100644 --- a/components/cli/cli/command/container/attach_test.go +++ b/components/cli/cli/command/container/attach_test.go @@ -51,6 +51,21 @@ func TestNewAttachCommandErrors(t *testing.T) { return c, nil }, }, + { + name: "client-restarting", + args: []string{"5cb5bb5e4a3b"}, + expectedError: "You cannot attach to a restarting container", + containerInspectFunc: func(containerID string) (types.ContainerJSON, error) { + c := types.ContainerJSON{} + c.ContainerJSONBase = &types.ContainerJSONBase{} + c.ContainerJSONBase.State = &types.ContainerState{ + Running: true, + Paused: false, + Restarting: true, + } + return c, nil + }, + }, } for _, tc := range testCases { buf := new(bytes.Buffer) From ba21fcb357cee3dd6c908bb4095e500e3010088d Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 8 Jun 2017 10:44:05 +0200 Subject: [PATCH 19/67] =?UTF-8?q?Update=20ConvertService=20for=20external?= =?UTF-8?q?=20usage=20=F0=9F=91=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vincent Demeester Upstream-commit: 96dc07a8cf845c56d2756dabfded2637e1c6e70d Component: cli --- components/cli/cli/compose/convert/service.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 0fef936a22..8f03a372ca 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -41,7 +41,7 @@ func Services( return nil, errors.Wrapf(err, "service %s", service.Name) } - serviceSpec, err := convertService(client.ClientVersion(), namespace, service, networks, volumes, secrets, configs) + serviceSpec, err := Service(client.ClientVersion(), namespace, service, networks, volumes, secrets, configs) if err != nil { return nil, errors.Wrapf(err, "service %s", service.Name) } @@ -51,7 +51,8 @@ func Services( return result, nil } -func convertService( +// Service converts a ServiceConfig into a swarm ServiceSpec +func Service( apiVersion string, namespace Namespace, service composetypes.ServiceConfig, From 696660014bf369dd79f4a35db8da25f7ac414936 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 8 Jun 2017 16:27:49 +0200 Subject: [PATCH 20/67] Bump docker/docker to cd35e4beee13a7c193e2a89008cd87d38fcd0161 Reverts changes in pkg/term related to `OPOST` that pulled in through; 3574e6a674086a6d7fae9adbb17d5f80b2c0a714 And reverted upstream in; https://github.com/moby/moby/commit/cd35e4beee13a7c193e2a89008cd87d38fcd0161 Full diff; https://github.com/moby/moby/compare/c8141a1fb1ff33b2bfab85a40e5da9a282f36cdc...cd35e4beee13a7c193e2a89008cd87d38fcd0161 Signed-off-by: Sebastiaan van Stijn Upstream-commit: efd11bf69db0386d34e3503157842bf6ee723b32 Component: cli --- components/cli/vendor.conf | 2 +- .../github.com/docker/docker/client/client.go | 4 +- .../docker/docker/client/container_copy.go | 6 +- .../github.com/docker/docker/client/ping.go | 4 +- .../docker/docker/client/plugin_upgrade.go | 3 +- .../docker/docker/pkg/archive/archive.go | 213 +++++------------- .../docker/docker/pkg/archive/archive_unix.go | 7 +- .../docker/pkg/archive/archive_windows.go | 5 +- .../docker/docker/pkg/archive/changes.go | 9 +- .../docker/docker/pkg/archive/diff.go | 28 +-- .../docker/docker/pkg/idtools/idtools.go | 146 +++++++++--- .../docker/docker/pkg/idtools/idtools_unix.go | 6 +- .../docker/pkg/idtools/idtools_windows.go | 2 +- .../docker/docker/pkg/term/termios_bsd.go | 2 +- .../docker/docker/pkg/term/termios_linux.go | 2 +- .../docker/docker/registry/session.go | 13 -- .../github.com/docker/docker/vendor.conf | 6 +- .../github.com/docker/docker/volume/volume.go | 14 +- 18 files changed, 213 insertions(+), 259 deletions(-) diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index 0c506e6618..7c618e5b5c 100644 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -7,7 +7,7 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20 github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 -github.com/docker/docker c8141a1fb1ff33b2bfab85a40e5da9a282f36cdc +github.com/docker/docker cd35e4beee13a7c193e2a89008cd87d38fcd0161 github.com/docker/docker-credential-helpers v0.5.1 github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 #? github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 diff --git a/components/cli/vendor/github.com/docker/docker/client/client.go b/components/cli/vendor/github.com/docker/docker/client/client.go index 34897b9549..10fd73759b 100644 --- a/components/cli/vendor/github.com/docker/docker/client/client.go +++ b/components/cli/vendor/github.com/docker/docker/client/client.go @@ -216,9 +216,9 @@ func (cli *Client) getAPIPath(p string, query url.Values) string { var apiPath string if cli.version != "" { v := strings.TrimPrefix(cli.version, "v") - apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p) + apiPath = cli.basePath + "/v" + v + p } else { - apiPath = fmt.Sprintf("%s%s", cli.basePath, p) + apiPath = cli.basePath + p } u := &url.URL{ diff --git a/components/cli/vendor/github.com/docker/docker/client/container_copy.go b/components/cli/vendor/github.com/docker/docker/client/container_copy.go index 545aa54383..66ee5c1b3b 100644 --- a/components/cli/vendor/github.com/docker/docker/client/container_copy.go +++ b/components/cli/vendor/github.com/docker/docker/client/container_copy.go @@ -20,7 +20,7 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri query := url.Values{} query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. - urlStr := fmt.Sprintf("/containers/%s/archive", containerID) + urlStr := "/containers/" + containerID + "/archive" response, err := cli.head(ctx, urlStr, query, nil) if err != nil { return types.ContainerPathStat{}, err @@ -42,7 +42,7 @@ func (cli *Client) CopyToContainer(ctx context.Context, container, path string, query.Set("copyUIDGID", "true") } - apiPath := fmt.Sprintf("/containers/%s/archive", container) + apiPath := "/containers/" + container + "/archive" response, err := cli.putRaw(ctx, apiPath, query, content, nil) if err != nil { @@ -63,7 +63,7 @@ func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath str query := make(url.Values, 1) query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. - apiPath := fmt.Sprintf("/containers/%s/archive", container) + apiPath := "/containers/" + container + "/archive" response, err := cli.get(ctx, apiPath, query, nil) if err != nil { return nil, types.ContainerPathStat{}, err diff --git a/components/cli/vendor/github.com/docker/docker/client/ping.go b/components/cli/vendor/github.com/docker/docker/client/ping.go index d6212ef8bb..631ed02005 100644 --- a/components/cli/vendor/github.com/docker/docker/client/ping.go +++ b/components/cli/vendor/github.com/docker/docker/client/ping.go @@ -1,8 +1,6 @@ package client import ( - "fmt" - "github.com/docker/docker/api/types" "golang.org/x/net/context" ) @@ -10,7 +8,7 @@ import ( // Ping pings the server and returns the value of the "Docker-Experimental", "OS-Type" & "API-Version" headers func (cli *Client) Ping(ctx context.Context) (types.Ping, error) { var ping types.Ping - req, err := cli.buildRequest("GET", fmt.Sprintf("%s/_ping", cli.basePath), nil, nil) + req, err := cli.buildRequest("GET", cli.basePath+"/_ping", nil, nil) if err != nil { return ping, err } diff --git a/components/cli/vendor/github.com/docker/docker/client/plugin_upgrade.go b/components/cli/vendor/github.com/docker/docker/client/plugin_upgrade.go index 24293c5073..28415156cd 100644 --- a/components/cli/vendor/github.com/docker/docker/client/plugin_upgrade.go +++ b/components/cli/vendor/github.com/docker/docker/client/plugin_upgrade.go @@ -1,7 +1,6 @@ package client import ( - "fmt" "io" "net/url" @@ -33,5 +32,5 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) { headers := map[string][]string{"X-Registry-Auth": {registryAuth}} - return cli.post(ctx, fmt.Sprintf("/plugins/%s/upgrade", name), query, privileges, headers) + return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, headers) } diff --git a/components/cli/vendor/github.com/docker/docker/pkg/archive/archive.go b/components/cli/vendor/github.com/docker/docker/pkg/archive/archive.go index 30b3c5b36f..5fb774d602 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/archive/archive.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/archive/archive.go @@ -6,7 +6,6 @@ import ( "bytes" "compress/bzip2" "compress/gzip" - "errors" "fmt" "io" "io/ioutil" @@ -31,10 +30,6 @@ type ( Compression int // WhiteoutFormat is the format of whiteouts unpacked WhiteoutFormat int - // TarChownOptions wraps the chown options UID and GID. - TarChownOptions struct { - UID, GID int - } // TarOptions wraps the tar options. TarOptions struct { @@ -44,7 +39,7 @@ type ( NoLchown bool UIDMaps []idtools.IDMap GIDMaps []idtools.IDMap - ChownOpts *TarChownOptions + ChownOpts *idtools.IDPair IncludeSourceDir bool // WhiteoutFormat is the expected on disk format for whiteout files. // This format will be converted to the standard format on pack @@ -58,33 +53,26 @@ type ( RebaseNames map[string]string InUserNS bool } - - // Archiver allows the reuse of most utility functions of this package - // with a pluggable Untar function. Also, to facilitate the passing of - // specific id mappings for untar, an archiver can be created with maps - // which will then be passed to Untar operations - Archiver struct { - Untar func(io.Reader, string, *TarOptions) error - UIDMaps []idtools.IDMap - GIDMaps []idtools.IDMap - } - - // breakoutError is used to differentiate errors related to breaking out - // When testing archive breakout in the unit tests, this error is expected - // in order for the test to pass. - breakoutError error ) -var ( - // ErrNotImplemented is the error message of function not implemented. - ErrNotImplemented = errors.New("Function not implemented") - defaultArchiver = &Archiver{Untar: Untar, UIDMaps: nil, GIDMaps: nil} -) +// Archiver allows the reuse of most utility functions of this package +// with a pluggable Untar function. Also, to facilitate the passing of +// specific id mappings for untar, an archiver can be created with maps +// which will then be passed to Untar operations +type Archiver struct { + Untar func(io.Reader, string, *TarOptions) error + IDMappings *idtools.IDMappings +} -const ( - // HeaderSize is the size in bytes of a tar header - HeaderSize = 512 -) +// NewDefaultArchiver returns a new Archiver without any IDMappings +func NewDefaultArchiver() *Archiver { + return &Archiver{Untar: Untar, IDMappings: &idtools.IDMappings{}} +} + +// breakoutError is used to differentiate errors related to breaking out +// When testing archive breakout in the unit tests, this error is expected +// in order for the test to pass. +type breakoutError error const ( // Uncompressed represents the uncompressed. @@ -105,18 +93,6 @@ const ( OverlayWhiteoutFormat ) -// IsArchive checks for the magic bytes of a tar or any supported compression -// algorithm. -func IsArchive(header []byte) bool { - compression := DetectCompression(header) - if compression != Uncompressed { - return true - } - r := tar.NewReader(bytes.NewBuffer(header)) - _, err := r.Next() - return err == nil -} - // IsArchivePath checks if the (possibly compressed) file at the given path // starts with a tar file header. func IsArchivePath(path string) bool { @@ -369,9 +345,8 @@ type tarAppender struct { Buffer *bufio.Writer // for hardlink mapping - SeenFiles map[uint64]string - UIDMaps []idtools.IDMap - GIDMaps []idtools.IDMap + SeenFiles map[uint64]string + IDMappings *idtools.IDMappings // For packing and unpacking whiteout files in the // non standard format. The whiteout files defined @@ -380,6 +355,15 @@ type tarAppender struct { WhiteoutConverter tarWhiteoutConverter } +func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer) *tarAppender { + return &tarAppender{ + SeenFiles: make(map[uint64]string), + TarWriter: tar.NewWriter(writer), + Buffer: pools.BufioWriter32KPool.Get(nil), + IDMappings: idMapping, + } +} + // canonicalTarName provides a platform-independent and consistent posix-style //path for files and directories to be archived regardless of the platform. func canonicalTarName(name string, isDir bool) (string, error) { @@ -428,21 +412,15 @@ func (ta *tarAppender) addTarFile(path, name string) error { //handle re-mapping container ID mappings back to host ID mappings before //writing tar headers/files. We skip whiteout files because they were written //by the kernel and already have proper ownership relative to the host - if !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && (ta.UIDMaps != nil || ta.GIDMaps != nil) { - uid, gid, err := getFileUIDGID(fi.Sys()) + if !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IDMappings.Empty() { + fileIDPair, err := getFileUIDGID(fi.Sys()) if err != nil { return err } - xUID, err := idtools.ToContainer(uid, ta.UIDMaps) + hdr.Uid, hdr.Gid, err = ta.IDMappings.ToContainer(fileIDPair) if err != nil { return err } - xGID, err := idtools.ToContainer(gid, ta.GIDMaps) - if err != nil { - return err - } - hdr.Uid = xUID - hdr.Gid = xGID } if ta.WhiteoutConverter != nil { @@ -496,7 +474,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { return nil } -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool, chownOpts *TarChownOptions, inUserns bool) error { +func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool, chownOpts *idtools.IDPair, inUserns bool) error { // hdr.Mode is in linux format, which we can use for sycalls, // but for os.Foo() calls we need the mode converted to os.FileMode, // so use hdrInfo.Mode() (they differ for e.g. setuid bits) @@ -576,7 +554,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L // Lchown is not supported on Windows. if Lchown && runtime.GOOS != "windows" { if chownOpts == nil { - chownOpts = &TarChownOptions{UID: hdr.Uid, GID: hdr.Gid} + chownOpts = &idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid} } if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { return err @@ -664,14 +642,11 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) } go func() { - ta := &tarAppender{ - TarWriter: tar.NewWriter(compressWriter), - Buffer: pools.BufioWriter32KPool.Get(nil), - SeenFiles: make(map[uint64]string), - UIDMaps: options.UIDMaps, - GIDMaps: options.GIDMaps, - WhiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat), - } + ta := newTarAppender( + idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps), + compressWriter, + ) + ta.WhiteoutConverter = getWhiteoutConverter(options.WhiteoutFormat) defer func() { // Make sure to check the error on Close. @@ -827,10 +802,8 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header - remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps) - if err != nil { - return err - } + idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) + rootIDs := idMappings.RootPair() whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) // Iterate through the files in the archive. @@ -864,7 +837,7 @@ loop: parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - err = idtools.MkdirAllNewAs(parentPath, 0777, remappedRootUID, remappedRootGID) + err = idtools.MkdirAllAndChownNew(parentPath, 0777, rootIDs) if err != nil { return err } @@ -909,26 +882,8 @@ loop: } trBuf.Reset(tr) - // if the options contain a uid & gid maps, convert header uid/gid - // entries using the maps such that lchown sets the proper mapped - // uid/gid after writing the file. We only perform this mapping if - // the file isn't already owned by the remapped root UID or GID, as - // that specific uid/gid has no mapping from container -> host, and - // those files already have the proper ownership for inside the - // container. - if hdr.Uid != remappedRootUID { - xUID, err := idtools.ToHost(hdr.Uid, options.UIDMaps) - if err != nil { - return err - } - hdr.Uid = xUID - } - if hdr.Gid != remappedRootGID { - xGID, err := idtools.ToHost(hdr.Gid, options.GIDMaps) - if err != nil { - return err - } - hdr.Gid = xGID + if err := remapIDs(idMappings, hdr); err != nil { + return err } if whiteoutConverter != nil { @@ -1013,23 +968,13 @@ func (archiver *Archiver) TarUntar(src, dst string) error { return err } defer archive.Close() - - var options *TarOptions - if archiver.UIDMaps != nil || archiver.GIDMaps != nil { - options = &TarOptions{ - UIDMaps: archiver.UIDMaps, - GIDMaps: archiver.GIDMaps, - } + options := &TarOptions{ + UIDMaps: archiver.IDMappings.UIDs(), + GIDMaps: archiver.IDMappings.GIDs(), } return archiver.Untar(archive, dst, options) } -// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. -// If either Tar or Untar fails, TarUntar aborts and returns the error. -func TarUntar(src, dst string) error { - return defaultArchiver.TarUntar(src, dst) -} - // UntarPath untar a file from path to a destination, src is the source tar file path. func (archiver *Archiver) UntarPath(src, dst string) error { archive, err := os.Open(src) @@ -1037,22 +982,13 @@ func (archiver *Archiver) UntarPath(src, dst string) error { return err } defer archive.Close() - var options *TarOptions - if archiver.UIDMaps != nil || archiver.GIDMaps != nil { - options = &TarOptions{ - UIDMaps: archiver.UIDMaps, - GIDMaps: archiver.GIDMaps, - } + options := &TarOptions{ + UIDMaps: archiver.IDMappings.UIDs(), + GIDMaps: archiver.IDMappings.GIDs(), } return archiver.Untar(archive, dst, options) } -// UntarPath is a convenience function which looks for an archive -// at filesystem path `src`, and unpacks it at `dst`. -func UntarPath(src, dst string) error { - return defaultArchiver.UntarPath(src, dst) -} - // CopyWithTar creates a tar archive of filesystem path `src`, and // unpacks it at filesystem path `dst`. // The archive is streamed directly with fixed buffering and no @@ -1069,27 +1005,16 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error { // if this archiver is set up with ID mapping we need to create // the new destination directory with the remapped root UID/GID pair // as owner - rootUID, rootGID, err := idtools.GetRootUIDGID(archiver.UIDMaps, archiver.GIDMaps) - if err != nil { - return err - } + rootIDs := archiver.IDMappings.RootPair() // Create dst, copy src's content into it logrus.Debugf("Creating dest directory: %s", dst) - if err := idtools.MkdirAllNewAs(dst, 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(dst, 0755, rootIDs); err != nil { return err } logrus.Debugf("Calling TarUntar(%s, %s)", src, dst) return archiver.TarUntar(src, dst) } -// CopyWithTar creates a tar archive of filesystem path `src`, and -// unpacks it at filesystem path `dst`. -// The archive is streamed directly with fixed buffering and no -// intermediary disk IO. -func CopyWithTar(src, dst string) error { - return defaultArchiver.CopyWithTar(src, dst) -} - // CopyFileWithTar emulates the behavior of the 'cp' command-line // for a single file. It copies a regular file from path `src` to // path `dst`, and preserves all its metadata. @@ -1131,28 +1056,10 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) - remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(archiver.UIDMaps, archiver.GIDMaps) - if err != nil { + if err := remapIDs(archiver.IDMappings, hdr); err != nil { return err } - // only perform mapping if the file being copied isn't already owned by the - // uid or gid of the remapped root in the container - if remappedRootUID != hdr.Uid { - xUID, err := idtools.ToHost(hdr.Uid, archiver.UIDMaps) - if err != nil { - return err - } - hdr.Uid = xUID - } - if remappedRootGID != hdr.Gid { - xGID, err := idtools.ToHost(hdr.Gid, archiver.GIDMaps) - if err != nil { - return err - } - hdr.Gid = xGID - } - tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { @@ -1176,16 +1083,10 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { return err } -// CopyFileWithTar emulates the behavior of the 'cp' command-line -// for a single file. It copies a regular file from path `src` to -// path `dst`, and preserves all its metadata. -// -// Destination handling is in an operating specific manner depending -// where the daemon is running. If `dst` ends with a trailing slash -// the final destination path will be `dst/base(src)` (Linux) or -// `dst\base(src)` (Windows). -func CopyFileWithTar(src, dst string) (err error) { - return defaultArchiver.CopyFileWithTar(src, dst) +func remapIDs(idMappings *idtools.IDMappings, hdr *tar.Header) error { + ids, err := idMappings.ToHost(idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}) + hdr.Uid, hdr.Gid = ids.UID, ids.GID + return err } // cmdStream executes a command, and returns its stdout as a stream. diff --git a/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_unix.go b/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_unix.go index 68d3c97d27..33ee88766f 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_unix.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_unix.go @@ -9,6 +9,7 @@ import ( "path/filepath" "syscall" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/system" rsystem "github.com/opencontainers/runc/libcontainer/system" ) @@ -72,13 +73,13 @@ func getInodeFromStat(stat interface{}) (inode uint64, err error) { return } -func getFileUIDGID(stat interface{}) (int, int, error) { +func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { s, ok := stat.(*syscall.Stat_t) if !ok { - return -1, -1, errors.New("cannot convert stat value to syscall.Stat_t") + return idtools.IDPair{}, errors.New("cannot convert stat value to syscall.Stat_t") } - return int(s.Uid), int(s.Gid), nil + return idtools.IDPair{UID: int(s.Uid), GID: int(s.Gid)}, nil } func major(device uint64) uint64 { diff --git a/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_windows.go b/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_windows.go index 9c7094738d..a22410c039 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_windows.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/archive/archive_windows.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/longpath" ) @@ -72,7 +73,7 @@ func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { return nil } -func getFileUIDGID(stat interface{}) (int, int, error) { +func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { // no notion of file ownership mapping yet on Windows - return 0, 0, nil + return idtools.IDPair{0, 0}, nil } diff --git a/components/cli/vendor/github.com/docker/docker/pkg/archive/changes.go b/components/cli/vendor/github.com/docker/docker/pkg/archive/changes.go index ca2c0ca1bf..5ca39b7215 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/archive/changes.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/archive/changes.go @@ -394,13 +394,8 @@ func ChangesSize(newDir string, changes []Change) int64 { func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (io.ReadCloser, error) { reader, writer := io.Pipe() go func() { - ta := &tarAppender{ - TarWriter: tar.NewWriter(writer), - Buffer: pools.BufioWriter32KPool.Get(nil), - SeenFiles: make(map[uint64]string), - UIDMaps: uidMaps, - GIDMaps: gidMaps, - } + ta := newTarAppender(idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), writer) + // this buffer is needed for the duration of this piped stream defer pools.BufioWriter32KPool.Put(ta.Buffer) diff --git a/components/cli/vendor/github.com/docker/docker/pkg/archive/diff.go b/components/cli/vendor/github.com/docker/docker/pkg/archive/diff.go index 2292193942..d20854e2a2 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/archive/diff.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/archive/diff.go @@ -33,10 +33,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, if options.ExcludePatterns == nil { options.ExcludePatterns = []string{} } - remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps) - if err != nil { - return 0, err - } + idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) @@ -195,27 +192,10 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, srcData = tmpFile } - // if the options contain a uid & gid maps, convert header uid/gid - // entries using the maps such that lchown sets the proper mapped - // uid/gid after writing the file. We only perform this mapping if - // the file isn't already owned by the remapped root UID or GID, as - // that specific uid/gid has no mapping from container -> host, and - // those files already have the proper ownership for inside the - // container. - if srcHdr.Uid != remappedRootUID { - xUID, err := idtools.ToHost(srcHdr.Uid, options.UIDMaps) - if err != nil { - return 0, err - } - srcHdr.Uid = xUID - } - if srcHdr.Gid != remappedRootGID { - xGID, err := idtools.ToHost(srcHdr.Gid, options.GIDMaps) - if err != nil { - return 0, err - } - srcHdr.Gid = xGID + if err := remapIDs(idMappings, srcHdr); err != nil { + return 0, err } + if err := createTarFile(path, dest, srcHdr, srcData, true, nil, options.InUserNS); err != nil { return 0, err } diff --git a/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools.go b/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools.go index 6bca466286..68a072db22 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools.go @@ -37,49 +37,56 @@ const ( // MkdirAllAs creates a directory (include any along the path) and then modifies // ownership to the requested uid/gid. If the directory already exists, this // function will still change ownership to the requested uid/gid pair. +// Deprecated: Use MkdirAllAndChown func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { return mkdirAs(path, mode, ownerUID, ownerGID, true, true) } -// MkdirAllNewAs creates a directory (include any along the path) and then modifies -// ownership ONLY of newly created directories to the requested uid/gid. If the -// directories along the path exist, no change of ownership will be performed -func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { - return mkdirAs(path, mode, ownerUID, ownerGID, true, false) -} - // MkdirAs creates a directory and then modifies ownership to the requested uid/gid. // If the directory already exists, this function still changes ownership +// Deprecated: Use MkdirAndChown with a IDPair func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { return mkdirAs(path, mode, ownerUID, ownerGID, false, true) } +// MkdirAllAndChown creates a directory (include any along the path) and then modifies +// ownership to the requested uid/gid. If the directory already exists, this +// function will still change ownership to the requested uid/gid pair. +func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error { + return mkdirAs(path, mode, ids.UID, ids.GID, true, true) +} + +// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. +// If the directory already exists, this function still changes ownership +func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error { + return mkdirAs(path, mode, ids.UID, ids.GID, false, true) +} + +// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies +// ownership ONLY of newly created directories to the requested uid/gid. If the +// directories along the path exist, no change of ownership will be performed +func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error { + return mkdirAs(path, mode, ids.UID, ids.GID, true, false) +} + // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. // If the maps are empty, then the root uid/gid will default to "real" 0/0 func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { - var uid, gid int - - if uidMap != nil { - xUID, err := ToHost(0, uidMap) - if err != nil { - return -1, -1, err - } - uid = xUID + uid, err := toHost(0, uidMap) + if err != nil { + return -1, -1, err } - if gidMap != nil { - xGID, err := ToHost(0, gidMap) - if err != nil { - return -1, -1, err - } - gid = xGID + gid, err := toHost(0, gidMap) + if err != nil { + return -1, -1, err } return uid, gid, nil } -// ToContainer takes an id mapping, and uses it to translate a +// toContainer takes an id mapping, and uses it to translate a // host ID to the remapped ID. If no map is provided, then the translation // assumes a 1-to-1 mapping and returns the passed in id -func ToContainer(hostID int, idMap []IDMap) (int, error) { +func toContainer(hostID int, idMap []IDMap) (int, error) { if idMap == nil { return hostID, nil } @@ -92,10 +99,10 @@ func ToContainer(hostID int, idMap []IDMap) (int, error) { return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID) } -// ToHost takes an id mapping and a remapped ID, and translates the +// toHost takes an id mapping and a remapped ID, and translates the // ID to the mapped host ID. If no map is provided, then the translation // assumes a 1-to-1 mapping and returns the passed in id # -func ToHost(contID int, idMap []IDMap) (int, error) { +func toHost(contID int, idMap []IDMap) (int, error) { if idMap == nil { return contID, nil } @@ -108,26 +115,101 @@ func ToHost(contID int, idMap []IDMap) (int, error) { return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) } -// CreateIDMappings takes a requested user and group name and +// IDPair is a UID and GID pair +type IDPair struct { + UID int + GID int +} + +// IDMappings contains a mappings of UIDs and GIDs +type IDMappings struct { + uids []IDMap + gids []IDMap +} + +// NewIDMappings takes a requested user and group name and // using the data from /etc/sub{uid,gid} ranges, creates the // proper uid and gid remapping ranges for that user/group pair -func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) { +func NewIDMappings(username, groupname string) (*IDMappings, error) { subuidRanges, err := parseSubuid(username) if err != nil { - return nil, nil, err + return nil, err } subgidRanges, err := parseSubgid(groupname) if err != nil { - return nil, nil, err + return nil, err } if len(subuidRanges) == 0 { - return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username) + return nil, fmt.Errorf("No subuid ranges found for user %q", username) } if len(subgidRanges) == 0 { - return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname) + return nil, fmt.Errorf("No subgid ranges found for group %q", groupname) } - return createIDMap(subuidRanges), createIDMap(subgidRanges), nil + return &IDMappings{ + uids: createIDMap(subuidRanges), + gids: createIDMap(subgidRanges), + }, nil +} + +// NewIDMappingsFromMaps creates a new mapping from two slices +// Deprecated: this is a temporary shim while transitioning to IDMapping +func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings { + return &IDMappings{uids: uids, gids: gids} +} + +// RootPair returns a uid and gid pair for the root user. The error is ignored +// because a root user always exists, and the defaults are correct when the uid +// and gid maps are empty. +func (i *IDMappings) RootPair() IDPair { + uid, gid, _ := GetRootUIDGID(i.uids, i.gids) + return IDPair{UID: uid, GID: gid} +} + +// ToHost returns the host UID and GID for the container uid, gid. +// Remapping is only performed if the ids aren't already the remapped root ids +func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) { + var err error + target := i.RootPair() + + if pair.UID != target.UID { + target.UID, err = toHost(pair.UID, i.uids) + if err != nil { + return target, err + } + } + + if pair.GID != target.GID { + target.GID, err = toHost(pair.GID, i.gids) + } + return target, err +} + +// ToContainer returns the container UID and GID for the host uid and gid +func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { + uid, err := toContainer(pair.UID, i.uids) + if err != nil { + return -1, -1, err + } + gid, err := toContainer(pair.GID, i.gids) + return uid, gid, err +} + +// Empty returns true if there are no id mappings +func (i *IDMappings) Empty() bool { + return len(i.uids) == 0 && len(i.gids) == 0 +} + +// UIDs return the UID mapping +// TODO: remove this once everything has been refactored to use pairs +func (i *IDMappings) UIDs() []IDMap { + return i.uids +} + +// GIDs return the UID mapping +// TODO: remove this once everything has been refactored to use pairs +func (i *IDMappings) GIDs() []IDMap { + return i.gids } func createIDMap(subidRanges ranges) []IDMap { diff --git a/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go b/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go index 7c7e82aee2..0b28249fa8 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go @@ -69,15 +69,15 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // CanAccess takes a valid (existing) directory and a uid, gid pair and determines // if that uid, gid pair has access (execute bit) to the directory -func CanAccess(path string, uid, gid int) bool { +func CanAccess(path string, pair IDPair) bool { statInfo, err := system.Stat(path) if err != nil { return false } fileMode := os.FileMode(statInfo.Mode()) permBits := fileMode.Perm() - return accessible(statInfo.UID() == uint32(uid), - statInfo.GID() == uint32(gid), permBits) + return accessible(statInfo.UID() == uint32(pair.UID), + statInfo.GID() == uint32(pair.GID), permBits) } func accessible(isOwner, isGroup bool, perms os.FileMode) bool { diff --git a/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go b/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go index 49f67e78c1..8ed8353060 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go @@ -20,6 +20,6 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // CanAccess takes a valid (existing) directory and a uid, gid pair and determines // if that uid, gid pair has access (execute bit) to the directory // Windows does not require/support this function, so always return true -func CanAccess(path string, uid, gid int) bool { +func CanAccess(path string, pair IDPair) bool { return true } diff --git a/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go b/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go index 9fdc720f25..c47341e873 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/term/termios_bsd.go @@ -27,7 +27,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState := oldState.termios newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) - newState.Oflag |= unix.OPOST + newState.Oflag &^= unix.OPOST newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) newState.Cflag &^= (unix.CSIZE | unix.PARENB) newState.Cflag |= unix.CS8 diff --git a/components/cli/vendor/github.com/docker/docker/pkg/term/termios_linux.go b/components/cli/vendor/github.com/docker/docker/pkg/term/termios_linux.go index b1d9c8d1b5..31bfa8419e 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/term/termios_linux.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/term/termios_linux.go @@ -26,7 +26,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState := oldState.termios newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON) - newState.Oflag |= unix.OPOST + newState.Oflag &^= unix.OPOST newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) newState.Cflag &^= (unix.CSIZE | unix.PARENB) newState.Cflag |= unix.CS8 diff --git a/components/cli/vendor/github.com/docker/docker/registry/session.go b/components/cli/vendor/github.com/docker/docker/registry/session.go index 746b8fb5b5..9d7f32193f 100644 --- a/components/cli/vendor/github.com/docker/docker/registry/session.go +++ b/components/cli/vendor/github.com/docker/docker/registry/session.go @@ -757,19 +757,6 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea return result, json.NewDecoder(res.Body).Decode(result) } -// GetAuthConfig returns the authentication settings for a session -// TODO(tiborvass): remove this once registry client v2 is vendored -func (r *Session) GetAuthConfig(withPasswd bool) *types.AuthConfig { - password := "" - if withPasswd { - password = r.authConfig.Password - } - return &types.AuthConfig{ - Username: r.authConfig.Username, - Password: password, - } -} - func isTimeout(err error) bool { type timeout interface { Timeout() bool diff --git a/components/cli/vendor/github.com/docker/docker/vendor.conf b/components/cli/vendor/github.com/docker/docker/vendor.conf index 35708e168c..a9f8671560 100644 --- a/components/cli/vendor/github.com/docker/docker/vendor.conf +++ b/components/cli/vendor/github.com/docker/docker/vendor.conf @@ -26,7 +26,7 @@ github.com/imdario/mergo 0.2.1 golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 #get libnetwork packages -github.com/docker/libnetwork 83e1e49475b88a9f1f8ba89a690a7d5de42e24b9 +github.com/docker/libnetwork eb57059e91bc54c9da23c5a633b75b3faf910a68 github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec @@ -38,7 +38,7 @@ github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 -github.com/vishvananda/netlink 1e86b2bee5b6a7d377e4c02bb7f98209d6a7297c +github.com/vishvananda/netlink bd6d5de5ccef2d66b0a26177928d0d8895d7f969 github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d @@ -61,7 +61,7 @@ google.golang.org/grpc v1.0.4 github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f # When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly -github.com/opencontainers/runc 992a5be178a62e026f4069f443c6164912adbf09 +github.com/opencontainers/runc 2d41c047c83e09a6d61d464906feb2a2f3c52aa4 https://github.com/docker/runc github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe github.com/opencontainers/runtime-spec d42f1eb741e6361e858d83fc75aa6893b66292c4 # specs diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume.go b/components/cli/vendor/github.com/docker/docker/volume/volume.go index ef362370e5..2abfcc7b58 100644 --- a/components/cli/vendor/github.com/docker/docker/volume/volume.go +++ b/components/cli/vendor/github.com/docker/docker/volume/volume.go @@ -149,7 +149,9 @@ func (m *MountPoint) Cleanup() error { // Setup sets up a mount point by either mounting the volume if it is // configured, or creating the source directory if supplied. -func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (path string, err error) { +// The, optional, checkFun parameter allows doing additional checking +// before creating the source directory on the host. +func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) { defer func() { if err == nil { if label.RelabelNeeded(m.Mode) { @@ -184,9 +186,17 @@ func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (path string // system.MkdirAll() produces an error if m.Source exists and is a file (not a directory), if m.Type == mounttypes.TypeBind { + // Before creating the source directory on the host, invoke checkFun if it's not nil. One of + // the use case is to forbid creating the daemon socket as a directory if the daemon is in + // the process of shutting down. + if checkFun != nil { + if err := checkFun(m); err != nil { + return "", err + } + } // idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory) // also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it - if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil { if perr, ok := err.(*os.PathError); ok { if perr.Err != syscall.ENOTDIR { return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source) From a2df58226d5cfa220a35e5bd3d9a510fe02025a2 Mon Sep 17 00:00:00 2001 From: Rogelio Canedo Date: Tue, 6 Jun 2017 22:33:05 +0200 Subject: [PATCH 21/67] Always exit whith -1 when remove image with force option Signed-off-by: Rogelio Canedo Upstream-commit: 5c8d702af57114c8d57f0639813b994fc7b512db Component: cli --- components/cli/cli/command/image/remove.go | 9 ++++++--- components/cli/cli/command/image/remove_test.go | 8 ++++++++ ...ommand-success.Image Deleted with force option.golden | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden diff --git a/components/cli/cli/command/image/remove.go b/components/cli/cli/command/image/remove.go index 91bf2f8786..0a4bef3caf 100644 --- a/components/cli/cli/command/image/remove.go +++ b/components/cli/cli/command/image/remove.go @@ -56,9 +56,12 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error } var errs []string - for _, image := range images { - dels, err := client.ImageRemove(ctx, image, options) + for _, img := range images { + dels, err := client.ImageRemove(ctx, img, options) if err != nil { + if opts.force { + fmt.Fprintf(dockerCli.Out(), "NotFound: %s\n", img) + } errs = append(errs, err.Error()) } else { for _, del := range dels { @@ -71,7 +74,7 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error } } - if len(errs) > 0 { + if !opts.force && len(errs) > 0 { return errors.Errorf("%s", strings.Join(errs, "\n")) } return nil diff --git a/components/cli/cli/command/image/remove_test.go b/components/cli/cli/command/image/remove_test.go index 0915729b20..ff671285ee 100644 --- a/components/cli/cli/command/image/remove_test.go +++ b/components/cli/cli/command/image/remove_test.go @@ -67,6 +67,14 @@ func TestNewRemoveCommandSuccess(t *testing.T) { return []types.ImageDeleteResponseItem{{Deleted: image}}, nil }, }, + { + name: "Image Deleted with force option", + args: []string{"-f", "image1"}, + imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) { + assert.Equal(t, "image1", image) + return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image") + }, + }, { name: "Image Untagged", args: []string{"image1"}, diff --git a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden new file mode 100644 index 0000000000..f965906f58 --- /dev/null +++ b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden @@ -0,0 +1,2 @@ +NotFound:image1 +NotFound:image1 \ No newline at end of file From 501eac530513bece630b7cac3d25f0cd04b5c05d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 8 Jun 2017 21:45:37 +0200 Subject: [PATCH 22/67] Update version to 17.07-dev Signed-off-by: Sebastiaan van Stijn Upstream-commit: 75238f3aa9bd44e1396d54f56931388f7a35007a Component: cli --- components/cli/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/VERSION b/components/cli/VERSION index 2d736aaa18..3e9257aa26 100644 --- a/components/cli/VERSION +++ b/components/cli/VERSION @@ -1 +1 @@ -17.06.0-dev +17.07.0-dev From e390db6238b07a46f32f359c97d548d555e251a3 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 8 Jun 2017 16:08:11 -0400 Subject: [PATCH 23/67] Move IsArchive and HeaderSize to build/context Previously these were in docker/docker/pkg/archive, but unused Signed-off-by: Daniel Nephin Upstream-commit: 0310de521369d128fd849c0ece4c74db430a821a Component: cli --- .../cli/cli/command/image/build/context.go | 18 +++++++++-- .../cli/command/image/build/context_test.go | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/components/cli/cli/command/image/build/context.go b/components/cli/cli/command/image/build/context.go index 8b47dac026..b1457bdcc2 100644 --- a/components/cli/cli/command/image/build/context.go +++ b/components/cli/cli/command/image/build/context.go @@ -28,6 +28,8 @@ import ( const ( // DefaultDockerfileName is the Default filename with Docker commands, read by docker build DefaultDockerfileName string = "Dockerfile" + // archiveHeaderSize is the number of bytes in an archive header + archiveHeaderSize = 512 ) // ValidateContextDirectory checks if all the contents of the directory @@ -84,12 +86,12 @@ func ValidateContextDirectory(srcPath string, excludes []string) error { func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) { buf := bufio.NewReader(r) - magic, err := buf.Peek(archive.HeaderSize) + magic, err := buf.Peek(archiveHeaderSize) if err != nil && err != io.EOF { return nil, "", errors.Errorf("failed to peek context header from STDIN: %v", err) } - if archive.IsArchive(magic) { + if IsArchive(magic) { return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil } @@ -133,6 +135,18 @@ func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCl } +// IsArchive checks for the magic bytes of a tar or any supported compression +// algorithm. +func IsArchive(header []byte) bool { + compression := archive.DetectCompression(header) + if compression != archive.Uncompressed { + return true + } + r := tar.NewReader(bytes.NewBuffer(header)) + _, err := r.Next() + return err == nil +} + // GetContextFromGitURL uses a Git URL as context for a `docker build`. The // git repo is cloned into a temporary directory used as the context directory. // Returns the absolute path to the temporary context directory, the relative diff --git a/components/cli/cli/command/image/build/context_test.go b/components/cli/cli/command/image/build/context_test.go index cda0bfb3f1..8bcbea48dd 100644 --- a/components/cli/cli/command/image/build/context_test.go +++ b/components/cli/cli/command/image/build/context_test.go @@ -266,3 +266,35 @@ func chdir(t *testing.T, dir string) func() { require.NoError(t, os.Chdir(dir)) return func() { require.NoError(t, os.Chdir(workingDirectory)) } } + +func TestIsArchive(t *testing.T) { + var testcases = []struct { + doc string + header []byte + expected bool + }{ + { + doc: "nil is not a valid header", + header: nil, + expected: false, + }, + { + doc: "invalid header bytes", + header: []byte{0x00, 0x01, 0x02}, + expected: false, + }, + { + doc: "header for bzip2 archive", + header: []byte{0x42, 0x5A, 0x68}, + expected: true, + }, + { + doc: "header for 7zip archive is not supported", + header: []byte{0x50, 0x4b, 0x03, 0x04}, + expected: false, + }, + } + for _, testcase := range testcases { + assert.Equal(t, testcase.expected, IsArchive(testcase.header), testcase.doc) + } +} From 2180c64b2ffdacc714cab0fe04454a7a23385c07 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Fri, 9 Jun 2017 11:58:42 -0400 Subject: [PATCH 24/67] removed the output leaked from stack remove error test case Signed-off-by: Arash Deshmeh Upstream-commit: 852bf0f96d2838cc57713f8b285b23a8f9946f96 Component: cli --- components/cli/cli/command/stack/remove_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/cli/cli/command/stack/remove_test.go b/components/cli/cli/command/stack/remove_test.go index d6efb90106..05092d84ac 100644 --- a/components/cli/cli/command/stack/remove_test.go +++ b/components/cli/cli/command/stack/remove_test.go @@ -3,6 +3,7 @@ package stack import ( "bytes" "errors" + "io/ioutil" "strings" "testing" @@ -116,6 +117,7 @@ func TestContinueAfterError(t *testing.T) { }, } cmd := newRemoveCommand(test.NewFakeCli(cli, &bytes.Buffer{})) + cmd.SetOutput(ioutil.Discard) cmd.SetArgs([]string{"foo", "bar"}) assert.EqualError(t, cmd.Execute(), "Failed to remove some resources from stack: foo") From 15c3a892cabeffa475141c9ca2ab4adb8428e4f0 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Fri, 9 Jun 2017 13:21:14 -0400 Subject: [PATCH 25/67] fixed the output leak from secret/remove command error test case Signed-off-by: Arash Deshmeh Upstream-commit: 77062a09dc777df35100fd2251e22fbee6cc9b5e Component: cli --- components/cli/cli/command/secret/remove_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/cli/cli/command/secret/remove_test.go b/components/cli/cli/command/secret/remove_test.go index b6085c9890..08443ec9ae 100644 --- a/components/cli/cli/command/secret/remove_test.go +++ b/components/cli/cli/command/secret/remove_test.go @@ -76,6 +76,7 @@ func TestSecretRemoveContinueAfterError(t *testing.T) { }, buf) cmd := newSecretRemoveCommand(cli) + cmd.SetOutput(ioutil.Discard) cmd.SetArgs(names) assert.EqualError(t, cmd.Execute(), "error removing secret: foo") assert.Equal(t, names, removedSecrets) From e9ff96097981983a3f5541bcb7cb1bb9e98bf9d8 Mon Sep 17 00:00:00 2001 From: Stefan Scherer Date: Fri, 9 Jun 2017 19:39:24 +0200 Subject: [PATCH 26/67] Update golang 1.8.3 Signed-off-by: Stefan Scherer Upstream-commit: b38c49411b68884d2424aca311ea1ccff237b270 Component: cli --- components/cli/dockerfiles/Dockerfile.cross | 2 +- components/cli/dockerfiles/Dockerfile.dev | 2 +- components/cli/dockerfiles/Dockerfile.lint | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/cli/dockerfiles/Dockerfile.cross b/components/cli/dockerfiles/Dockerfile.cross index f52c3e10dd..19907ced49 100644 --- a/components/cli/dockerfiles/Dockerfile.cross +++ b/components/cli/dockerfiles/Dockerfile.cross @@ -1,5 +1,5 @@ -FROM golang:1.8.1 +FROM golang:1.8.3 # allow replacing httpredir or deb mirror ARG APT_MIRROR=deb.debian.org diff --git a/components/cli/dockerfiles/Dockerfile.dev b/components/cli/dockerfiles/Dockerfile.dev index f3e058e285..aef37382c8 100644 --- a/components/cli/dockerfiles/Dockerfile.dev +++ b/components/cli/dockerfiles/Dockerfile.dev @@ -1,5 +1,5 @@ -FROM golang:1.8-alpine +FROM golang:1.8.3-alpine RUN apk add -U git make bash coreutils diff --git a/components/cli/dockerfiles/Dockerfile.lint b/components/cli/dockerfiles/Dockerfile.lint index 2bba9595e5..14c124642e 100644 --- a/components/cli/dockerfiles/Dockerfile.lint +++ b/components/cli/dockerfiles/Dockerfile.lint @@ -1,4 +1,4 @@ -FROM golang:1.8-alpine +FROM golang:1.8.3-alpine RUN apk add -U git From 1a30bcfcf23b37c859a535f89732a31f3db41ebf Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 9 Jun 2017 15:50:42 -0400 Subject: [PATCH 27/67] Add a docker version print to CI Signed-off-by: Daniel Nephin Upstream-commit: cda8281da8f5ff8a93bf37739ae882bfc3e58fc2 Component: cli --- components/cli/circle.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/cli/circle.yml b/components/cli/circle.yml index dabc4c5aad..06d639e004 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -11,6 +11,8 @@ jobs: command: apk add -U git openssh - checkout - setup_remote_docker + - run: + command: docker version - run: name: "Lint" command: | From 04d4acede50bc28ff72260c405b1412e9b9cd527 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 13 Jun 2017 02:53:25 +0000 Subject: [PATCH 28/67] Update docker/docker to 4310f7da7e6bcd8185bf05e032f9b7321cfa6ea2 This fix updates docker/docker to 4310f7da7e6bcd8185bf05e032f9b7321cfa6ea2 This fix is related to moby/moby#33630 and docker/cli#167 Signed-off-by: Yong Tang Upstream-commit: 8c2f81892beb7634a97efeec053f7052c36b4039 Component: cli --- components/cli/vendor.conf | 2 +- .../github.com/docker/docker/api/common.go | 2 +- .../docker/docker/api/types/types.go | 6 ++++ .../github.com/docker/docker/client/hijack.go | 20 ++++++++++-- .../docker/docker/client/interface.go | 4 +-- .../docker/docker/client/network_inspect.go | 11 ++++--- .../docker/docker/pkg/pools/pools.go | 31 ++++++++++++++++--- .../docker/pkg/system/syscall_windows.go | 21 +++++++++++-- .../docker/docker/registry/config_unix.go | 2 +- .../github.com/docker/docker/vendor.conf | 12 +++---- 10 files changed, 86 insertions(+), 25 deletions(-) mode change 100644 => 100755 components/cli/vendor.conf diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf old mode 100644 new mode 100755 index 7c618e5b5c..43758294ed --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -7,7 +7,7 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20 github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 -github.com/docker/docker cd35e4beee13a7c193e2a89008cd87d38fcd0161 +github.com/docker/docker 4310f7da7e6bcd8185bf05e032f9b7321cfa6ea2 github.com/docker/docker-credential-helpers v0.5.1 github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 #? github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 diff --git a/components/cli/vendor/github.com/docker/docker/api/common.go b/components/cli/vendor/github.com/docker/docker/api/common.go index 9df6255c08..142b76966c 100644 --- a/components/cli/vendor/github.com/docker/docker/api/common.go +++ b/components/cli/vendor/github.com/docker/docker/api/common.go @@ -21,7 +21,7 @@ import ( // Common constants for daemon and client. const ( // DefaultVersion of Current REST API - DefaultVersion string = "1.30" + DefaultVersion string = "1.31" // NoBaseImageSpecifier is the symbol used by the FROM // command to specify that no base image is to be used. diff --git a/components/cli/vendor/github.com/docker/docker/api/types/types.go b/components/cli/vendor/github.com/docker/docker/api/types/types.go index c905466e29..37adca2f57 100644 --- a/components/cli/vendor/github.com/docker/docker/api/types/types.go +++ b/components/cli/vendor/github.com/docker/docker/api/types/types.go @@ -468,6 +468,12 @@ type NetworkDisconnect struct { Force bool } +// NetworkInspectOptions holds parameters to inspect network +type NetworkInspectOptions struct { + Scope string + Verbose bool +} + // Checkpoint represents the details of a checkpoint type Checkpoint struct { Name string // Name is the name of the checkpoint diff --git a/components/cli/vendor/github.com/docker/docker/client/hijack.go b/components/cli/vendor/github.com/docker/docker/client/hijack.go index 74c53f52b3..2b61a80950 100644 --- a/components/cli/vendor/github.com/docker/docker/client/hijack.go +++ b/components/cli/vendor/github.com/docker/docker/client/hijack.go @@ -1,9 +1,11 @@ package client import ( + "bytes" "crypto/tls" "errors" "fmt" + "io/ioutil" "net" "net/http" "net/http/httputil" @@ -72,11 +74,23 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected - _, err = clientconn.Do(req) + resp, err := clientconn.Do(req) + if err != nil { + return types.HijackedResponse{}, err + } - rwc, br := clientconn.Hijack() + defer resp.Body.Close() + switch resp.StatusCode { + case http.StatusOK, http.StatusSwitchingProtocols: + rwc, br := clientconn.Hijack() + return types.HijackedResponse{Conn: rwc, Reader: br}, err + } - return types.HijackedResponse{Conn: rwc, Reader: br}, err + errbody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return types.HijackedResponse{}, err + } + return types.HijackedResponse{}, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(errbody)) } func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { diff --git a/components/cli/vendor/github.com/docker/docker/client/interface.go b/components/cli/vendor/github.com/docker/docker/client/interface.go index d5f3a14ec3..aa00622127 100644 --- a/components/cli/vendor/github.com/docker/docker/client/interface.go +++ b/components/cli/vendor/github.com/docker/docker/client/interface.go @@ -99,8 +99,8 @@ type NetworkAPIClient interface { NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error - NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) - NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) + NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) + NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) NetworkRemove(ctx context.Context, networkID string) error NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) diff --git a/components/cli/vendor/github.com/docker/docker/client/network_inspect.go b/components/cli/vendor/github.com/docker/docker/client/network_inspect.go index 7242304025..848c9799fb 100644 --- a/components/cli/vendor/github.com/docker/docker/client/network_inspect.go +++ b/components/cli/vendor/github.com/docker/docker/client/network_inspect.go @@ -12,22 +12,25 @@ import ( ) // NetworkInspect returns the information for a specific network configured in the docker host. -func (cli *Client) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) { - networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, verbose) +func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) { + networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, options) return networkResource, err } // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. -func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) { +func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { var ( networkResource types.NetworkResource resp serverResponse err error ) query := url.Values{} - if verbose { + if options.Verbose { query.Set("verbose", "true") } + if options.Scope != "" { + query.Set("scope", options.Scope) + } resp, err = cli.get(ctx, "/networks/"+networkID, query, nil) if err != nil { if resp.statusCode == http.StatusNotFound { diff --git a/components/cli/vendor/github.com/docker/docker/pkg/pools/pools.go b/components/cli/vendor/github.com/docker/docker/pkg/pools/pools.go index 5c5aead698..6a111a3ba7 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/pools/pools.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/pools/pools.go @@ -17,15 +17,16 @@ import ( "github.com/docker/docker/pkg/ioutils" ) +const buffer32K = 32 * 1024 + var ( // BufioReader32KPool is a pool which returns bufio.Reader with a 32K buffer. BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K) // BufioWriter32KPool is a pool which returns bufio.Writer with a 32K buffer. BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K) + buffer32KPool = newBufferPoolWithSize(buffer32K) ) -const buffer32K = 32 * 1024 - // BufioReaderPool is a bufio reader that uses sync.Pool. type BufioReaderPool struct { pool sync.Pool @@ -54,11 +55,31 @@ func (bufPool *BufioReaderPool) Put(b *bufio.Reader) { bufPool.pool.Put(b) } +type bufferPool struct { + pool sync.Pool +} + +func newBufferPoolWithSize(size int) *bufferPool { + return &bufferPool{ + pool: sync.Pool{ + New: func() interface{} { return make([]byte, size) }, + }, + } +} + +func (bp *bufferPool) Get() []byte { + return bp.pool.Get().([]byte) +} + +func (bp *bufferPool) Put(b []byte) { + bp.pool.Put(b) +} + // Copy is a convenience wrapper which uses a buffer to avoid allocation in io.Copy. func Copy(dst io.Writer, src io.Reader) (written int64, err error) { - buf := BufioReader32KPool.Get(src) - written, err = io.Copy(dst, buf) - BufioReader32KPool.Put(buf) + buf := buffer32KPool.Get() + written, err = io.CopyBuffer(dst, src, buf) + buffer32KPool.Put(buf) return } diff --git a/components/cli/vendor/github.com/docker/docker/pkg/system/syscall_windows.go b/components/cli/vendor/github.com/docker/docker/pkg/system/syscall_windows.go index 1f311874f4..c328a6fb86 100644 --- a/components/cli/vendor/github.com/docker/docker/pkg/system/syscall_windows.go +++ b/components/cli/vendor/github.com/docker/docker/pkg/system/syscall_windows.go @@ -8,8 +8,9 @@ import ( ) var ( - ntuserApiset = syscall.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") - procGetVersionExW = modkernel32.NewProc("GetVersionExW") + ntuserApiset = syscall.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") + procGetVersionExW = modkernel32.NewProc("GetVersionExW") + procGetProductInfo = modkernel32.NewProc("GetProductInfo") ) // OSVersion is a wrapper for Windows version information @@ -66,6 +67,22 @@ func IsWindowsClient() bool { return osviex.ProductType == verNTWorkstation } +// IsIoTCore returns true if the currently running image is based off of +// Windows 10 IoT Core. +// @engine maintainers - this function should not be removed or modified as it +// is used to enforce licensing restrictions on Windows. +func IsIoTCore() bool { + var returnedProductType uint32 + r1, _, err := procGetProductInfo.Call(6, 1, 0, 0, uintptr(unsafe.Pointer(&returnedProductType))) + if r1 == 0 { + logrus.Warnf("GetProductInfo failed - assuming this is not IoT: %v", err) + return false + } + const productIoTUAP = 0x0000007B + const productIoTUAPCommercial = 0x00000083 + return returnedProductType == productIoTUAP || returnedProductType == productIoTUAPCommercial +} + // Unmount is a platform-specific helper function to call // the unmount syscall. Not supported on Windows func Unmount(dest string) error { diff --git a/components/cli/vendor/github.com/docker/docker/registry/config_unix.go b/components/cli/vendor/github.com/docker/docker/registry/config_unix.go index d692e8ef50..fdc39a1d68 100644 --- a/components/cli/vendor/github.com/docker/docker/registry/config_unix.go +++ b/components/cli/vendor/github.com/docker/docker/registry/config_unix.go @@ -21,5 +21,5 @@ func cleanPath(s string) string { // installCliPlatformFlags handles any platform specific flags for the service. func (options *ServiceOptions) installCliPlatformFlags(flags *pflag.FlagSet) { - flags.BoolVar(&options.V2Only, "disable-legacy-registry", false, "Disable contacting legacy registries") + flags.BoolVar(&options.V2Only, "disable-legacy-registry", true, "Disable contacting legacy registries") } diff --git a/components/cli/vendor/github.com/docker/docker/vendor.conf b/components/cli/vendor/github.com/docker/docker/vendor.conf index a9f8671560..e524c518ab 100644 --- a/components/cli/vendor/github.com/docker/docker/vendor.conf +++ b/components/cli/vendor/github.com/docker/docker/vendor.conf @@ -13,7 +13,7 @@ github.com/mattn/go-shellwords v1.0.3 github.com/tchap/go-patricia v2.2.6 github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 # forked golang.org/x/net package includes a patch for lazy loading trace templates -golang.org/x/net c427ad74c6d7a814201695e9ffde0c5d400a7674 +golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 @@ -26,7 +26,7 @@ github.com/imdario/mergo 0.2.1 golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 #get libnetwork packages -github.com/docker/libnetwork eb57059e91bc54c9da23c5a633b75b3faf910a68 +github.com/docker/libnetwork f4a15a0890383619ad797b3bd2481cc6f46a978d github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec @@ -57,7 +57,7 @@ github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb github.com/mistifyio/go-zfs 22c9b32c84eb0d0c6f4043b6e90fc94073de92fa github.com/pborman/uuid v1.0 -google.golang.org/grpc v1.0.4 +google.golang.org/grpc v1.3.0 github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f # When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly @@ -71,7 +71,7 @@ github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 github.com/coreos/go-systemd v4 github.com/godbus/dbus v4.0.0 github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 -github.com/golang/protobuf 8ee79997227bf9b34611aee7946ae64735e6fd93 +github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 # gelf logging driver deps github.com/Graylog2/go-gelf 7029da823dad4ef3a876df61065156acb703b2ea @@ -97,14 +97,14 @@ golang.org/x/oauth2 96382aa079b72d8c014eb0c50f6c223d1e6a2de0 google.golang.org/api 3cc2e591b550923a2c5f0ab5a803feda924d5823 cloud.google.com/go 9d965e63e8cceb1b5d7977a202f0fcb8866d6525 github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 -google.golang.org/genproto b3e7c2fb04031add52c4817f53f43757ccbf9c18 +google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 # containerd github.com/containerd/containerd 3addd840653146c90a254301d6c3a663c7fd6429 github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 # cluster -github.com/docker/swarmkit eb07af52aa2216100cff1ad0b13df48daa8914bf +github.com/docker/swarmkit a4bf0135f63fb60f0e76ae81579cde87f580db6e github.com/gogo/protobuf v0.4 github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e From 59b1e60ea569cafe22ccf752568c7a8a2ad954ca Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Tue, 13 Jun 2017 13:34:52 +0200 Subject: [PATCH 29/67] Fix remove_test output duplication Signed-off-by: Vincent Demeester Upstream-commit: dd924ebf4fcaae83ce1dfa59eb74d33713c76a3f Component: cli --- components/cli/cli/command/image/remove_test.go | 2 -- .../remove-command-success.Image Deleted and Untagged.golden | 2 -- ...emove-command-success.Image Deleted with force option.golden | 1 - .../image/testdata/remove-command-success.Image Deleted.golden | 1 - .../image/testdata/remove-command-success.Image Untagged.golden | 1 - 5 files changed, 7 deletions(-) diff --git a/components/cli/cli/command/image/remove_test.go b/components/cli/cli/command/image/remove_test.go index ff671285ee..4420b29d4b 100644 --- a/components/cli/cli/command/image/remove_test.go +++ b/components/cli/cli/command/image/remove_test.go @@ -102,8 +102,6 @@ func TestNewRemoveCommandSuccess(t *testing.T) { cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) - err := cmd.Execute() - assert.NoError(t, err) actual := buf.String() expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:]) testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) diff --git a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden index 4efc53719d..94db08448d 100644 --- a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden +++ b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden @@ -1,4 +1,2 @@ Untagged: image1 Deleted: image2 -Untagged: image1 -Deleted: image2 diff --git a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden index f965906f58..0d3cae73f7 100644 --- a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden +++ b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden @@ -1,2 +1 @@ -NotFound:image1 NotFound:image1 \ No newline at end of file diff --git a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted.golden b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted.golden index 382724d39f..445df11a97 100644 --- a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted.golden +++ b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted.golden @@ -1,2 +1 @@ Deleted: image1 -Deleted: image1 diff --git a/components/cli/cli/command/image/testdata/remove-command-success.Image Untagged.golden b/components/cli/cli/command/image/testdata/remove-command-success.Image Untagged.golden index c795dac19f..ebbb4075e8 100644 --- a/components/cli/cli/command/image/testdata/remove-command-success.Image Untagged.golden +++ b/components/cli/cli/command/image/testdata/remove-command-success.Image Untagged.golden @@ -1,2 +1 @@ Untagged: image1 -Untagged: image1 From 45b64dbaab2c8b6675996e41933c02c0409e26e7 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 13 Jun 2017 12:24:42 -0400 Subject: [PATCH 30/67] fixed the output leak from error test case for config/remove Signed-off-by: Arash Deshmeh Upstream-commit: 006b9b126dca278db450d164a2f402268a545b6f Component: cli --- components/cli/cli/command/config/remove_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/cli/cli/command/config/remove_test.go b/components/cli/cli/command/config/remove_test.go index 84423c0254..c183a93c8b 100644 --- a/components/cli/cli/command/config/remove_test.go +++ b/components/cli/cli/command/config/remove_test.go @@ -77,6 +77,7 @@ func TestConfigRemoveContinueAfterError(t *testing.T) { cmd := newConfigRemoveCommand(cli) cmd.SetArgs(names) + cmd.SetOutput(ioutil.Discard) assert.EqualError(t, cmd.Execute(), "error removing config: foo") assert.Equal(t, names, removedConfigs) } From 8af6b6b3cd9465d096748712b09ca090bb0d0884 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 13 Jun 2017 11:51:41 -0700 Subject: [PATCH 31/67] Fix some problems with image remove force. Signed-off-by: Daniel Nephin Upstream-commit: 676b71eaaab2a90091e1019ecf5f81088c3ccd67 Component: cli --- components/cli/cli/command/image/remove.go | 11 ++++++----- components/cli/cli/command/image/remove_test.go | 12 +++++++++--- ...nd-success.Image Deleted with force option.golden | 1 - 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/components/cli/cli/command/image/remove.go b/components/cli/cli/command/image/remove.go index 0a4bef3caf..894d30574b 100644 --- a/components/cli/cli/command/image/remove.go +++ b/components/cli/cli/command/image/remove.go @@ -59,9 +59,6 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error for _, img := range images { dels, err := client.ImageRemove(ctx, img, options) if err != nil { - if opts.force { - fmt.Fprintf(dockerCli.Out(), "NotFound: %s\n", img) - } errs = append(errs, err.Error()) } else { for _, del := range dels { @@ -74,8 +71,12 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error } } - if !opts.force && len(errs) > 0 { - return errors.Errorf("%s", strings.Join(errs, "\n")) + if len(errs) > 0 { + msg := strings.Join(errs, "\n") + if !opts.force { + return errors.New(msg) + } + fmt.Fprintf(dockerCli.Err(), msg) } return nil } diff --git a/components/cli/cli/command/image/remove_test.go b/components/cli/cli/command/image/remove_test.go index 4420b29d4b..9b02b24a90 100644 --- a/components/cli/cli/command/image/remove_test.go +++ b/components/cli/cli/command/image/remove_test.go @@ -58,6 +58,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) { name string args []string imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) + expectedErrMsg string }{ { name: "Image Deleted", @@ -74,6 +75,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) { assert.Equal(t, "image1", image) return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image") }, + expectedErrMsg: "error removing image", }, { name: "Image Untagged", @@ -96,12 +98,16 @@ func TestNewRemoveCommandSuccess(t *testing.T) { } for _, tc := range testCases { buf := new(bytes.Buffer) - cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{ - imageRemoveFunc: tc.imageRemoveFunc, - }, buf)) + errBuf := new(bytes.Buffer) + fakeCli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc}, buf) + fakeCli.SetErr(errBuf) + cmd := NewRemoveCommand(fakeCli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) + if tc.expectedErrMsg != "" { + assert.Equal(t, tc.expectedErrMsg, errBuf.String()) + } actual := buf.String() expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:]) testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) diff --git a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden index 0d3cae73f7..e69de29bb2 100644 --- a/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden +++ b/components/cli/cli/command/image/testdata/remove-command-success.Image Deleted with force option.golden @@ -1 +0,0 @@ -NotFound:image1 \ No newline at end of file From a55a5185664c83159f50e047f365e12bc55b2d12 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 13 Jun 2017 02:53:53 +0000 Subject: [PATCH 32/67] Use `scope=swarm` for service related network inspect. This fix use `scope=swarm` for service related network inspect. The purpose is that, in case multiple networks with the same name exist in different scopes, it is still possible to obtain the network for services. This fix is related to moby/moby#33630 and docker/cli#167 Signed-off-by: Yong Tang Upstream-commit: 657457ee2cc1b4ced804674ad1b3bfe19b849f31 Component: cli --- components/cli/cli/command/network/inspect.go | 3 ++- components/cli/cli/command/network/remove.go | 3 ++- components/cli/cli/command/service/inspect.go | 2 +- components/cli/cli/command/service/opts.go | 3 ++- components/cli/cli/command/service/update.go | 2 +- components/cli/cli/command/stack/deploy_composefile.go | 2 +- .../cli/cli/command/stack/deploy_composefile_test.go | 2 +- components/cli/cli/command/system/inspect.go | 2 +- components/cli/cli/internal/test/network/client.go | 8 ++++---- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/components/cli/cli/command/network/inspect.go b/components/cli/cli/command/network/inspect.go index 0c76e87351..9856c04e4c 100644 --- a/components/cli/cli/command/network/inspect.go +++ b/components/cli/cli/command/network/inspect.go @@ -6,6 +6,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/inspect" + "github.com/docker/docker/api/types" "github.com/spf13/cobra" ) @@ -40,7 +41,7 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { ctx := context.Background() getNetFunc := func(name string) (interface{}, []byte, error) { - return client.NetworkInspectWithRaw(ctx, name, opts.verbose) + return client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{Verbose: opts.verbose}) } return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc) diff --git a/components/cli/cli/command/network/remove.go b/components/cli/cli/command/network/remove.go index c83016a408..7dfe8da2f7 100644 --- a/components/cli/cli/command/network/remove.go +++ b/components/cli/cli/command/network/remove.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/docker/api/types" "github.com/spf13/cobra" ) @@ -33,7 +34,7 @@ func runRemove(dockerCli *command.DockerCli, networks []string) error { status := 0 for _, name := range networks { - if nw, _, err := client.NetworkInspectWithRaw(ctx, name, false); err == nil && + if nw, _, err := client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{}); err == nil && nw.Ingress && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), ingressWarning) { continue diff --git a/components/cli/cli/command/service/inspect.go b/components/cli/cli/command/service/inspect.go index 45e7459dd4..ea0009d3a5 100644 --- a/components/cli/cli/command/service/inspect.go +++ b/components/cli/cli/command/service/inspect.go @@ -61,7 +61,7 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { } getNetwork := func(ref string) (interface{}, []byte, error) { - network, _, err := client.NetworkInspectWithRaw(ctx, ref, false) + network, _, err := client.NetworkInspectWithRaw(ctx, ref, types.NetworkInspectOptions{Scope: "swarm"}) if err == nil || !apiclient.IsErrNetworkNotFound(err) { return network, nil, err } diff --git a/components/cli/cli/command/service/opts.go b/components/cli/cli/command/service/opts.go index 35cdedc70e..5e70863f01 100644 --- a/components/cli/cli/command/service/opts.go +++ b/components/cli/cli/command/service/opts.go @@ -8,6 +8,7 @@ import ( "time" "github.com/docker/cli/opts" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" @@ -349,7 +350,7 @@ func convertNetworks(ctx context.Context, apiClient client.NetworkAPIClient, net var netAttach []swarm.NetworkAttachmentConfig for _, net := range networks.Value() { networkIDOrName := net.Target - _, err := apiClient.NetworkInspect(ctx, networkIDOrName, false) + _, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"}) if err != nil { return nil, err } diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index 48552e2453..4b49b738ee 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -1008,7 +1008,7 @@ func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flag toRemove := buildToRemoveSet(flags, flagNetworkRemove) idsToRemove := make(map[string]struct{}) for networkIDOrName := range toRemove { - network, err := apiClient.NetworkInspect(ctx, networkIDOrName, false) + network, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"}) if err != nil { return err } diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index 1a2266af80..e8cab8fa4e 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -174,7 +174,7 @@ func validateExternalNetworks( externalNetworks []string, ) error { for _, networkName := range externalNetworks { - network, err := client.NetworkInspect(ctx, networkName, false) + network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}) switch { case dockerclient.IsErrNotFound(err): return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName) diff --git a/components/cli/cli/command/stack/deploy_composefile_test.go b/components/cli/cli/command/stack/deploy_composefile_test.go index 6d811ed208..5d4813bc05 100644 --- a/components/cli/cli/command/stack/deploy_composefile_test.go +++ b/components/cli/cli/command/stack/deploy_composefile_test.go @@ -70,7 +70,7 @@ func TestValidateExternalNetworks(t *testing.T) { for _, testcase := range testcases { fakeClient := &network.FakeClient{ - NetworkInspectFunc: func(_ context.Context, _ string, _ bool) (types.NetworkResource, error) { + NetworkInspectFunc: func(_ context.Context, _ string, _ types.NetworkInspectOptions) (types.NetworkResource, error) { return testcase.inspectResponse, testcase.inspectError }, } diff --git a/components/cli/cli/command/system/inspect.go b/components/cli/cli/command/system/inspect.go index cac6d0fb2f..6d079cd69e 100644 --- a/components/cli/cli/command/system/inspect.go +++ b/components/cli/cli/command/system/inspect.go @@ -68,7 +68,7 @@ func inspectImages(ctx context.Context, dockerCli *command.DockerCli) inspect.Ge func inspectNetwork(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc { return func(ref string) (interface{}, []byte, error) { - return dockerCli.Client().NetworkInspectWithRaw(ctx, ref, false) + return dockerCli.Client().NetworkInspectWithRaw(ctx, ref, types.NetworkInspectOptions{}) } } diff --git a/components/cli/cli/internal/test/network/client.go b/components/cli/cli/internal/test/network/client.go index 5f35cd4514..b12fdec6f2 100644 --- a/components/cli/cli/internal/test/network/client.go +++ b/components/cli/cli/internal/test/network/client.go @@ -9,7 +9,7 @@ import ( // FakeClient is a fake NetworkAPIClient type FakeClient struct { - NetworkInspectFunc func(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) + NetworkInspectFunc func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) } // NetworkConnect fakes connecting to a network @@ -28,15 +28,15 @@ func (c *FakeClient) NetworkDisconnect(ctx context.Context, networkID, container } // NetworkInspect fakes inspecting a network -func (c *FakeClient) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) { +func (c *FakeClient) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) { if c.NetworkInspectFunc != nil { - return c.NetworkInspectFunc(ctx, networkID, verbose) + return c.NetworkInspectFunc(ctx, networkID, options) } return types.NetworkResource{}, nil } // NetworkInspectWithRaw fakes inspecting a network with a raw response -func (c *FakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) { +func (c *FakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { return types.NetworkResource{}, nil, nil } From d17a741bef0efd353732451c903028a5fec492d4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 1 Jun 2017 17:05:42 +0200 Subject: [PATCH 33/67] Update deprecated.md for removal of --email flag Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 43239f62bedc4721d27744d21c122622988bb3ae) Signed-off-by: Tibor Vass Upstream-commit: 6ee8cf85c5dd0de7b516ca647c1c57a94d1bb560 Component: cli --- components/cli/docs/deprecated.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docs/deprecated.md b/components/cli/docs/deprecated.md index 7e0bfc0a60..e1e7e12645 100644 --- a/components/cli/docs/deprecated.md +++ b/components/cli/docs/deprecated.md @@ -138,7 +138,7 @@ on all subcommands (due to it conflicting with, e.g. `-h` / `--hostname` on ### `-e` and `--email` flags on `docker login` **Deprecated In Release: [v1.11.0](https://github.com/docker/docker/releases/tag/v1.11.0)** -**Target For Removal In Release: v17.06** +**Removed In Release: [v17.06](https://github.com/docker/docker-ce/releases/tag/v17.06.0-ce)** The docker login command is removing the ability to automatically register for an account with the target registry if the given username doesn't exist. Due to this change, the email flag is no longer required, and will be deprecated. From a08039c09b1d9dd79f3b23ea2e60fd3950afe57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B5nis=20Tiigi?= Date: Thu, 20 Apr 2017 20:11:26 -0700 Subject: [PATCH 34/67] Merge pull request #32684 from scjane/patch-3 Update builder.md (cherry picked from commit 831066337743fc29ff122fce51afe44b8b3b3ba9) Signed-off-by: Sebastiaan van Stijn (cherry picked from commit bc66821abbcf50c721ce9b8f52b339fda102d389) Signed-off-by: Tibor Vass Upstream-commit: 7955683eb27e161a159cb464f1f17b27bcc09002 Component: cli --- components/cli/docs/reference/builder.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/cli/docs/reference/builder.md b/components/cli/docs/reference/builder.md index 2571511b64..502e41dfbb 100644 --- a/components/cli/docs/reference/builder.md +++ b/components/cli/docs/reference/builder.md @@ -94,8 +94,8 @@ instructions. Whenever possible, Docker will re-use the intermediate images (cache), to accelerate the `docker build` process significantly. This is indicated by the `Using cache` message in the console output. -(For more information, see the [Build cache section](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/build-cache)) in the -`Dockerfile` best practices guide: +(For more information, see the [Build cache section](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#build-cache) in the +`Dockerfile` best practices guide): $ docker build -t svendowideit/ambassador . Sending build context to Docker daemon 15.36 kB From fc9f540d08815b15fc9abbb64b3752a5190e7976 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 26 Apr 2017 15:30:09 +0200 Subject: [PATCH 35/67] Merge pull request #32724 from PatrickLang/patricklang-win-memory Adding more on -m and --memory (cherry picked from commit c3fbca106552f2dadcb89510ff87945b50f36419) Signed-off-by: Sebastiaan van Stijn (cherry picked from commit b4047a849bd3018f8a8eabf34613a4fca57f818e) Signed-off-by: Tibor Vass Upstream-commit: b1738f45059b60945dc97f4d53cc9f4e2814a3e1 Component: cli --- .../cli/docs/reference/commandline/run.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/components/cli/docs/reference/commandline/run.md b/components/cli/docs/reference/commandline/run.md index ecb676dc20..2216319415 100644 --- a/components/cli/docs/reference/commandline/run.md +++ b/components/cli/docs/reference/commandline/run.md @@ -745,6 +745,41 @@ PS C:\> docker run -d --isolation default microsoft/nanoserver powershell echo h PS C:\> docker run -d --isolation hyperv microsoft/nanoserver powershell echo hyperv ``` +### Specify hard limits on memory available to containers (-m, --memory) + +These parameters always set an upper limit on the memory available to the container. On Linux, this +is set on the cgroup and applications in a container can query it at `/sys/fs/cgroup/memory/memory.limit_in_bytes`. + +On Windows, this will affect containers differently depending on what type of isolation is used. + +- With `process` isolation, Windows will report the full memory of the host system, not the limit to applications running inside the container + ```powershell + docker run -it -m 2GB --isolation=process microsoft/nanoserver powershell Get-ComputerInfo *memory* + + CsTotalPhysicalMemory : 17064509440 + CsPhyicallyInstalledMemory : 16777216 + OsTotalVisibleMemorySize : 16664560 + OsFreePhysicalMemory : 14646720 + OsTotalVirtualMemorySize : 19154928 + OsFreeVirtualMemory : 17197440 + OsInUseVirtualMemory : 1957488 + OsMaxProcessMemorySize : 137438953344 + ``` +- With `hyperv` isolation, Windows will create a utility VM that is big enough to hold the memory limit, plus the minimal OS needed to host the container. That size is reported as "Total Physical Memory." + ```powershell + docker run -it -m 2GB --isolation=hyperv microsoft/nanoserver powershell Get-ComputerInfo *memory* + + CsTotalPhysicalMemory : 2683355136 + CsPhyicallyInstalledMemory : + OsTotalVisibleMemorySize : 2620464 + OsFreePhysicalMemory : 2306552 + OsTotalVirtualMemorySize : 2620464 + OsFreeVirtualMemory : 2356692 + OsInUseVirtualMemory : 263772 + OsMaxProcessMemorySize : 137438953344 + ``` + + ### Configure namespaced kernel parameters (sysctls) at runtime The `--sysctl` sets namespaced kernel parameters (sysctls) in the From 13d31e4cd8228173f20f79232f163ee81381ad07 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 25 Apr 2017 10:18:16 -0700 Subject: [PATCH 36/67] Merge pull request #32735 from bhavin192/patch-1 Add note about host-dir in VOLUME (cherry picked from commit f2fff9d913a8ab0436dd56033189a7c3713a59a2) Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 8fd6547fc3eb67e7efa7efb007ae6a4494cd2bb3) Signed-off-by: Tibor Vass Upstream-commit: 5bf86c198488c7f78f2ffd05a8a2441f719d3b98 Component: cli --- components/cli/docs/reference/builder.md | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/components/cli/docs/reference/builder.md b/components/cli/docs/reference/builder.md index 502e41dfbb..f8ef9cf56b 100644 --- a/components/cli/docs/reference/builder.md +++ b/components/cli/docs/reference/builder.md @@ -1281,18 +1281,28 @@ This Dockerfile results in an image that causes `docker run`, to create a new mount point at `/myvol` and copy the `greeting` file into the newly created volume. -> **Note**: -> When using Windows-based containers, the destination of a volume inside the -> container must be one of: a non-existing or empty directory; or a drive other -> than C:. +### Notes about specifying volumes -> **Note**: -> If any build steps change the data within the volume after it has been -> declared, those changes will be discarded. +Keep the following things in mind about volumes in the `Dockerfile`. -> **Note**: -> The list is parsed as a JSON array, which means that -> you must use double-quotes (") around words not single-quotes ('). +- **Volumes on Windows-based containers**: When using Windows-based containers, + the destination of a volume inside the container must be one of: + + - a non-existing or empty directory + - a drive other than `C:` + +- **Changing the volume from within the Dockerfile**: If any build steps change the + data within the volume after it has been declared, those changes will be discarded. + +- **JSON formatting**: The list is parsed as a JSON array. + You must enclose words with double quotes (`"`)rather than single quotes (`'`). + +- **The host directory is declared at container run-time**: The host directory + (the mountpoint) is, by its nature, host-dependent. This is to preserve image + portability. since a given host directory can't be guaranteed to be available + on all hosts.For this reason, you can't mount a host directory from + within the Dockerfile. The `VOLUME` instruction does not support specifying a `host-dir` + parameter. You must specify the mountpoint when you create or run the container. ## USER From 090c6ccf05fc066a08ac64e14db905eb5af9b0f8 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 24 Apr 2017 08:53:30 -0400 Subject: [PATCH 37/67] Merge pull request #32791 from djalal/patch-1 fix typo (cherry picked from commit 32a52716b964373b4ac464052e73ea5da79856c6) Signed-off-by: Sebastiaan van Stijn (cherry picked from commit fcbd93f52032593cc71b298c00a46fd354356650) Signed-off-by: Tibor Vass Upstream-commit: 8247a36058932059dd9e8aac82b1a7f426197b32 Component: cli --- components/cli/docs/reference/commandline/node_ls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docs/reference/commandline/node_ls.md b/components/cli/docs/reference/commandline/node_ls.md index 224642c2e5..2f9c2becdf 100644 --- a/components/cli/docs/reference/commandline/node_ls.md +++ b/components/cli/docs/reference/commandline/node_ls.md @@ -89,7 +89,7 @@ ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS 1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Ready Active ``` -#### membersip +#### membership The `membership` filter matches nodes based on the presence of a `membership` and a value `accepted` or `pending`. From 9a66d3e2b0eff474e17f08aed3b8b620d810ae93 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 28 Apr 2017 18:39:52 -0400 Subject: [PATCH 38/67] Merge pull request #32804 from bbodenmiller/patch-1 remove extra word (cherry picked from commit 9db03bd8cdad3c8804105cb5794ebad5e728f48f) Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 3eaec0071c3ce1b7201e37859afe5bcb78d4f215) Signed-off-by: Tibor Vass Upstream-commit: ba4dcadcaa2bdd99c96b289c2443396e4b992391 Component: cli --- components/cli/docs/reference/run.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docs/reference/run.md b/components/cli/docs/reference/run.md index b209343b58..817c20c969 100644 --- a/components/cli/docs/reference/run.md +++ b/components/cli/docs/reference/run.md @@ -1123,7 +1123,7 @@ by default a container is not allowed to access any devices, but a the documentation on [cgroups devices](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt)). When the operator executes `docker run --privileged`, Docker will enable -to access to all devices on the host as well as set some configuration +access to all devices on the host as well as set some configuration in AppArmor or SELinux to allow the container nearly all the same access to the host as processes running outside containers on the host. Additional information about running with `--privileged` is available on the From 786d489f9922f50eea0869b95310227d73d1b34d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 8 Jun 2017 01:09:45 +0200 Subject: [PATCH 39/67] Merge pull request #33572 from hsluoyz/patch-1 Add Casbin plugin to the list of Authorization plugins in docs. (cherry picked from commit 220831d541bfe9bf566c1038773198d431560dd3) Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 0ad3e3294e74f443130b5e1fb1ef6b31f4f92366) Signed-off-by: Tibor Vass Upstream-commit: 8b11d46ecdb82f6eded069d1a005e05bbbee62f2 Component: cli --- components/cli/docs/extend/legacy_plugins.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/cli/docs/extend/legacy_plugins.md b/components/cli/docs/extend/legacy_plugins.md index 68bba59f46..dc77743b86 100644 --- a/components/cli/docs/extend/legacy_plugins.md +++ b/components/cli/docs/extend/legacy_plugins.md @@ -87,8 +87,9 @@ Plugin Plugin | Description ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - [Twistlock AuthZ Broker](https://github.com/twistlock/authz) | A basic extendable authorization plugin that runs directly on the host or inside a container. This plugin allows you to define user policies that it evaluates during authorization. Basic authorization is provided if Docker daemon is started with the --tlsverify flag (username is extracted from the certificate common name). - [HBM plugin](https://github.com/kassisol/hbm) | An authorization plugin that prevents from executing commands with certains parameters. +[Casbin AuthZ Plugin](https://github.com/casbin/casbin-authz-plugin) | An authorization plugin based on [Casbin](https://github.com/casbin/casbin), which supports access control models like ACL, RBAC, ABAC. The access control model can be customized. The policy can be persisted into file or DB. +[HBM plugin](https://github.com/kassisol/hbm) | An authorization plugin that prevents from executing commands with certains parameters. +[Twistlock AuthZ Broker](https://github.com/twistlock/authz) | A basic extendable authorization plugin that runs directly on the host or inside a container. This plugin allows you to define user policies that it evaluates during authorization. Basic authorization is provided if Docker daemon is started with the --tlsverify flag (username is extracted from the certificate common name). ## Troubleshooting a plugin From 2f5b714872bb4d06de2c2e5fa4582aba974d61dc Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 11 Jun 2017 14:47:23 +0200 Subject: [PATCH 40/67] Update docs, completion scripts for disable-legacy-registry Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2b8f0eef7338f37104464154ba65aef7db3b9703) Signed-off-by: Tibor Vass Upstream-commit: ee1bbab62093f8a2fa4e365c0884d5bf8898354a Component: cli --- components/cli/contrib/completion/zsh/_docker | 2 +- components/cli/docs/deprecated.md | 2 +- .../cli/docs/reference/commandline/dockerd.md | 15 +++++++++++++-- components/cli/man/dockerd.8.md | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/components/cli/contrib/completion/zsh/_docker b/components/cli/contrib/completion/zsh/_docker index 0860907839..4b0c1e25c7 100644 --- a/components/cli/contrib/completion/zsh/_docker +++ b/components/cli/contrib/completion/zsh/_docker @@ -2620,7 +2620,7 @@ __docker_subcommand() { "($help)--default-gateway-v6[Container default gateway IPv6 address]:IPv6 address: " \ "($help)--default-shm-size=[Default shm size for containers]:size:" \ "($help)*--default-ulimit=[Default ulimits for containers]:ulimit: " \ - "($help)--disable-legacy-registry[Disable contacting legacy registries]" \ + "($help)--disable-legacy-registry[Disable contacting legacy registries (default true)]" \ "($help)*--dns=[DNS server to use]:DNS: " \ "($help)*--dns-opt=[DNS options to use]:DNS option: " \ "($help)*--dns-search=[DNS search domains to use]:DNS search: " \ diff --git a/components/cli/docs/deprecated.md b/components/cli/docs/deprecated.md index e1e7e12645..9905994b73 100644 --- a/components/cli/docs/deprecated.md +++ b/components/cli/docs/deprecated.md @@ -292,7 +292,7 @@ of the `--changes` flag that allows to pass `Dockerfile` commands. **Target For Removal In Release: v17.12** -Version 1.9 adds a flag (`--disable-legacy-registry=false`) which prevents the +Version 1.8.3 added a flag (`--disable-legacy-registry=false`) which prevents the docker daemon from `pull`, `push`, and `login` operations against v1 registries. Though enabled by default, this signals the intent to deprecate the v1 protocol. diff --git a/components/cli/docs/reference/commandline/dockerd.md b/components/cli/docs/reference/commandline/dockerd.md index 93774c841b..ef6bcd3831 100644 --- a/components/cli/docs/reference/commandline/dockerd.md +++ b/components/cli/docs/reference/commandline/dockerd.md @@ -42,7 +42,7 @@ Options: --default-gateway-v6 ip Container default gateway IPv6 address --default-runtime string Default OCI runtime for containers (default "runc") --default-ulimit ulimit Default ulimits for containers (default []) - --disable-legacy-registry Disable contacting legacy registries + --disable-legacy-registry Disable contacting legacy registries (default true) --dns list DNS server to use (default []) --dns-opt list DNS options to use (default []) --dns-search list DNS search domains to use (default []) @@ -901,7 +901,18 @@ system's list of trusted CAs instead of enabling `--insecure-registry`. ##### Legacy Registries -Enabling `--disable-legacy-registry` forces a docker daemon to only interact with registries which support the V2 protocol. Specifically, the daemon will not attempt `push`, `pull` and `login` to v1 registries. The exception to this is `search` which can still be performed on v1 registries. +Operations against registries supporting only the legacy v1 protocol are +disabled by default. Specifically, the daemon will not attempt `push`, +`pull` and `login` to v1 registries. The exception to this is `search` +which can still be performed on v1 registries. + +Add `"disable-legacy-registry":false` to the [daemon configuration +file](#daemon-configuration-file), or set the +`--disable-legacy-registry=false` flag, if you need to interact with +registries that have not yet migrated to the v2 protocol. + +Interaction v1 registries will no longer be supported in Docker v17.12, +and the `disable-legacy-registry` configuration option will be removed. #### Running a Docker daemon behind an HTTPS_PROXY diff --git a/components/cli/man/dockerd.8.md b/components/cli/man/dockerd.8.md index a4e079074f..e9d7e68739 100644 --- a/components/cli/man/dockerd.8.md +++ b/components/cli/man/dockerd.8.md @@ -192,7 +192,7 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru Default ulimits for containers. **--disable-legacy-registry**=*true*|*false* - Disable contacting legacy registries + Disable contacting legacy registries. Default is `true`. **--dns**="" Force Docker to use specific DNS servers From f3827b078b344d3c070eb0bf24a54c3f49dae85d Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 12 Jun 2017 22:14:05 +0200 Subject: [PATCH 41/67] Do not call the config endpoint if API is lower than 1.30 Signed-off-by: Vincent Demeester Upstream-commit: 2128b3f11208197c8d0340ac22cd4ff9d36b96cc Component: cli --- components/cli/cli/command/stack/client_test.go | 8 ++++++++ components/cli/cli/command/stack/remove.go | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/components/cli/cli/command/stack/client_test.go b/components/cli/cli/command/stack/client_test.go index d4c373ce12..22be9dc06a 100644 --- a/components/cli/cli/command/stack/client_test.go +++ b/components/cli/cli/command/stack/client_test.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/docker/cli/cli/compose/convert" + "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" @@ -34,6 +35,13 @@ type fakeClient struct { configRemoveFunc func(configID string) error } +func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { + return types.Version{ + Version: "docker-dev", + APIVersion: api.DefaultVersion, + }, nil +} + func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { if cli.serviceListFunc != nil { return cli.serviceListFunc(options) diff --git a/components/cli/cli/command/stack/remove.go b/components/cli/cli/command/stack/remove.go index 09431dad70..b63c385a7e 100644 --- a/components/cli/cli/command/stack/remove.go +++ b/components/cli/cli/command/stack/remove.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/versions" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -55,10 +56,20 @@ func runRemove(dockerCli command.Cli, opts removeOptions) error { return err } - configs, err := getStackConfigs(ctx, client, namespace) + var configs []swarm.Config + + version, err := client.ServerVersion(ctx) if err != nil { return err } + if versions.LessThan(version.APIVersion, "1.30") { + fmt.Fprintf(dockerCli.Err(), `WARNING: ignoring "configs" (requires API version 1.30, but the Docker daemon API version is %s)`, version.APIVersion) + } else { + 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) From cd16e5cc8be005d4523163eaff9116a60b2fa49f Mon Sep 17 00:00:00 2001 From: Slava Semushin Date: Wed, 7 Jun 2017 18:50:16 +0200 Subject: [PATCH 42/67] docs/reference/builder.md: mention that USER directive also allows to specify the user group. Signed-off-by: Slava Semushin Upstream-commit: a84463d8d56a41c1bf9496efc35580789891c2c9 Component: cli --- components/cli/docs/reference/builder.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/components/cli/docs/reference/builder.md b/components/cli/docs/reference/builder.md index f8ef9cf56b..9ab4ba4df9 100644 --- a/components/cli/docs/reference/builder.md +++ b/components/cli/docs/reference/builder.md @@ -1306,11 +1306,18 @@ Keep the following things in mind about volumes in the `Dockerfile`. ## USER - USER daemon + USER [:] +or + USER [:] + +The `USER` instruction sets the user name (or UID) and optionally the user +group (or GID) to use when running the image and for any `RUN`, `CMD` and +`ENTRYPOINT` instructions that follow it in the `Dockerfile`. + +> **Warning**: +> When the user does doesn't have a primary group then the image (or the next +> instructions) will be run with the `root` group. -The `USER` instruction sets the user name or UID to use when running the image -and for any `RUN`, `CMD` and `ENTRYPOINT` instructions that follow it in the -`Dockerfile`. ## WORKDIR From 9a1729bb2b1a6ce8e02f83bd06cdb683229f7010 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 14 Jun 2017 12:38:31 -0400 Subject: [PATCH 43/67] removed logging from service/inspect test to clean up the output of running tests Signed-off-by: Arash Deshmeh Upstream-commit: 751278abe6a194addc774ea43d78363137e794c9 Component: cli --- components/cli/cli/command/service/inspect_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/cli/cli/command/service/inspect_test.go b/components/cli/cli/command/service/inspect_test.go index c2984a787a..e7b34da212 100644 --- a/components/cli/cli/command/service/inspect_test.go +++ b/components/cli/cli/command/service/inspect_test.go @@ -117,11 +117,7 @@ func TestJSONFormatWithNoUpdateConfig(t *testing.T) { // s1: [{"ID":..}] // s2: {"ID":..} s1 := formatServiceInspect(t, formatter.NewServiceFormat(""), now) - t.Log("// s1") - t.Logf("%s", s1) s2 := formatServiceInspect(t, formatter.NewServiceFormat("{{json .}}"), now) - t.Log("// s2") - t.Logf("%s", s2) var m1Wrap []map[string]interface{} if err := json.Unmarshal([]byte(s1), &m1Wrap); err != nil { t.Fatal(err) @@ -130,11 +126,9 @@ func TestJSONFormatWithNoUpdateConfig(t *testing.T) { t.Fatalf("strange s1=%s", s1) } m1 := m1Wrap[0] - t.Logf("m1=%+v", m1) var m2 map[string]interface{} if err := json.Unmarshal([]byte(s2), &m2); err != nil { t.Fatal(err) } - t.Logf("m2=%+v", m2) assert.Equal(t, m1, m2) } From 6ca362a6db6a86b497333017e910edc63db4b824 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 14 Jun 2017 10:55:11 -0700 Subject: [PATCH 44/67] Speed up testing with coverage By running a 'go test -i' on all the packages first the overall runtime is significantly decreased. Signed-off-by: Daniel Nephin Upstream-commit: 3a9ba545bc4a31b12b3c9b4572b3bb09022ed1e5 Component: cli --- components/cli/scripts/test/unit-with-coverage | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/cli/scripts/test/unit-with-coverage b/components/cli/scripts/test/unit-with-coverage index 8c599b43ff..cd1ead8477 100755 --- a/components/cli/scripts/test/unit-with-coverage +++ b/components/cli/scripts/test/unit-with-coverage @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -eu -o pipefail +# install test dependencies once before running tests for each package. This +# reduces the runtime from 200s down to 23s +go test -i $@ + for pkg in $@; do ./scripts/test/unit \ -cover \ From 1bcf02da4fb5ee8becd1605579dfdbdb6a6cd281 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 14 Jun 2017 13:42:58 -0700 Subject: [PATCH 45/67] Run docs and man generation in CI Also cleanup the scripts a bit to be more consistent, and fail on errors. Signed-off-by: Daniel Nephin Upstream-commit: 343d836a9506a4bb9d6ae39b46f9eab78584e012 Component: cli --- components/cli/circle.yml | 4 +- components/cli/man/import.go | 7 +++ components/cli/scripts/docs/generate-man.sh | 19 ++----- components/cli/scripts/docs/generate-yaml.sh | 7 ++- .../github.com/cpuguy83/go-md2man/md2man.go | 51 +++++++++++++++++++ 5 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 components/cli/man/import.go create mode 100644 components/cli/vendor/github.com/cpuguy83/go-md2man/md2man.go diff --git a/components/cli/circle.yml b/components/cli/circle.yml index 76c8bff90d..3ba3f1d2fe 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -42,14 +42,14 @@ jobs: apk add -U bash curl curl -s https://codecov.io/bash | bash - run: - name: "Validate Vendor and Code Generation" + name: "Validate Vendor, Docs, and Code Generation" command: | if [ "$CIRCLE_NODE_INDEX" != "3" ]; then exit; fi dockerfile=dockerfiles/Dockerfile.dev echo "COPY . ." >> $dockerfile rm -f .dockerignore # include .git docker build -f $dockerfile --tag cli-builder . - docker run cli-builder make -B vendor compose-jsonschema + docker run cli-builder make -B vendor compose-jsonschema manpages yamldocs - store_artifacts: path: /work/build diff --git a/components/cli/man/import.go b/components/cli/man/import.go new file mode 100644 index 0000000000..bc117bdcfb --- /dev/null +++ b/components/cli/man/import.go @@ -0,0 +1,7 @@ +// +build never + +package main + +// Not used, but required for generating other man pages. +// Import it here so that the package is included by vndr. +import _ "github.com/cpuguy83/go-md2man" diff --git a/components/cli/scripts/docs/generate-man.sh b/components/cli/scripts/docs/generate-man.sh index 28fea4e1ce..eb0a68da51 100755 --- a/components/cli/scripts/docs/generate-man.sh +++ b/components/cli/scripts/docs/generate-man.sh @@ -1,25 +1,14 @@ #!/usr/bin/env bash -# -# Generate man pages for docker/docker -# - -set -eu +# Generate man pages for docker/cli +set -eu -o pipefail mkdir -p ./man/man1 -MD2MAN_REPO=github.com/cpuguy83/go-md2man -MD2MAN_COMMIT=$(grep -F "$MD2MAN_REPO" vendor.conf | cut -d' ' -f2) - -( - go get -d "$MD2MAN_REPO" - cd "$GOPATH"/src/"$MD2MAN_REPO" - git checkout "$MD2MAN_COMMIT" &> /dev/null - go install "$MD2MAN_REPO" -) +go install ./vendor/github.com/cpuguy83/go-md2man # Generate man pages from cobra commands go build -o /tmp/gen-manpages github.com/docker/cli/man -/tmp/gen-manpages --root . --target ./man/man1 +/tmp/gen-manpages --root $(pwd) --target $(pwd)/man/man1 # Generate legacy pages from markdown ./man/md2man-all.sh -q diff --git a/components/cli/scripts/docs/generate-yaml.sh b/components/cli/scripts/docs/generate-yaml.sh index c322071bcf..a8afed1796 100755 --- a/components/cli/scripts/docs/generate-yaml.sh +++ b/components/cli/scripts/docs/generate-yaml.sh @@ -1,5 +1,8 @@ -#!/bin/sh +#!/usr/bin/env bash +# Generate yaml for docker/cli reference docs +set -eu -o pipefail + +mkdir -p docs/yaml/gen go build -o build/yaml-docs-generator github.com/docker/cli/docs/yaml -mkdir docs/yaml/gen build/yaml-docs-generator --root $(pwd) --target $(pwd)/docs/yaml/gen diff --git a/components/cli/vendor/github.com/cpuguy83/go-md2man/md2man.go b/components/cli/vendor/github.com/cpuguy83/go-md2man/md2man.go new file mode 100644 index 0000000000..8f6dcdaedf --- /dev/null +++ b/components/cli/vendor/github.com/cpuguy83/go-md2man/md2man.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/cpuguy83/go-md2man/md2man" +) + +var inFilePath = flag.String("in", "", "Path to file to be processed (default: stdin)") +var outFilePath = flag.String("out", "", "Path to output processed file (default: stdout)") + +func main() { + var err error + flag.Parse() + + inFile := os.Stdin + if *inFilePath != "" { + inFile, err = os.Open(*inFilePath) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + defer inFile.Close() + + doc, err := ioutil.ReadAll(inFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + out := md2man.Render(doc) + + outFile := os.Stdout + if *outFilePath != "" { + outFile, err = os.Create(*outFilePath) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + defer outFile.Close() + } + _, err = outFile.Write(out) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} From 2cdc30d8918cc0bf48292544ad26da461b15c501 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 9 Jun 2017 12:32:14 -0400 Subject: [PATCH 46/67] Add unused linter. Signed-off-by: Daniel Nephin Upstream-commit: 01e1e58adaec1147f6e223791fe1da9d5f026588 Component: cli --- .../cli/command/container/stats_helpers.go | 5 ++--- .../cli/cli/command/formatter/disk_usage.go | 2 -- .../cli/cli/command/formatter/reflect_test.go | 2 +- components/cli/cli/command/image/pull_test.go | 19 +++++-------------- components/cli/cli/command/image/push_test.go | 11 ++--------- components/cli/cli/command/node/opts.go | 1 - components/cli/cli/command/registry/login.go | 1 - .../cli/cli/command/swarm/unlock_test.go | 1 - .../cli/cli/compose/loader/loader_test.go | 4 ---- components/cli/dockerfiles/Dockerfile.lint | 3 ++- components/cli/gometalinter.json | 3 ++- 11 files changed, 14 insertions(+), 38 deletions(-) diff --git a/components/cli/cli/command/container/stats_helpers.go b/components/cli/cli/command/container/stats_helpers.go index c6849c805a..eb12cd0dec 100644 --- a/components/cli/cli/command/container/stats_helpers.go +++ b/components/cli/cli/command/container/stats_helpers.go @@ -16,9 +16,8 @@ import ( ) type stats struct { - ostype string - mu sync.Mutex - cs []*formatter.ContainerStats + mu sync.Mutex + cs []*formatter.ContainerStats } // daemonOSType is set once we have at least one stat for a container diff --git a/components/cli/cli/command/formatter/disk_usage.go b/components/cli/cli/command/formatter/disk_usage.go index 07e39826ed..caea53f924 100644 --- a/components/cli/cli/command/formatter/disk_usage.go +++ b/components/cli/cli/command/formatter/disk_usage.go @@ -234,7 +234,6 @@ func (c *diskUsageImagesContext) Reclaimable() string { type diskUsageContainersContext struct { HeaderContext - verbose bool containers []*types.Container } @@ -297,7 +296,6 @@ func (c *diskUsageContainersContext) Reclaimable() string { type diskUsageVolumesContext struct { HeaderContext - verbose bool volumes []*types.Volume } diff --git a/components/cli/cli/command/formatter/reflect_test.go b/components/cli/cli/command/formatter/reflect_test.go index e547b18411..ffda51b858 100644 --- a/components/cli/cli/command/formatter/reflect_test.go +++ b/components/cli/cli/command/formatter/reflect_test.go @@ -12,7 +12,7 @@ func (d *dummy) Func1() string { return "Func1" } -func (d *dummy) func2() string { +func (d *dummy) func2() string { // nolint: unused return "func2(should not be marshalled)" } diff --git a/components/cli/cli/command/image/pull_test.go b/components/cli/cli/command/image/pull_test.go index b4b57e2abc..d72531b768 100644 --- a/components/cli/cli/command/image/pull_test.go +++ b/components/cli/cli/command/image/pull_test.go @@ -6,24 +6,17 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/internal/test" - "github.com/docker/distribution/reference" - "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/testutil" "github.com/docker/docker/pkg/testutil/golden" - "github.com/docker/docker/registry" "github.com/stretchr/testify/assert" - "golang.org/x/net/context" ) func TestNewPullCommandErrors(t *testing.T) { testCases := []struct { - name string - args []string - expectedError string - trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, - authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error + name string + args []string + expectedError string }{ { name: "wrong-args", @@ -57,10 +50,8 @@ func TestNewPullCommandErrors(t *testing.T) { func TestNewPullCommandSuccess(t *testing.T) { testCases := []struct { - name string - args []string - trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, - authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error + name string + args []string }{ { name: "simple", diff --git a/components/cli/cli/command/image/push_test.go b/components/cli/cli/command/image/push_test.go index b382ad7ee1..559b1b89c3 100644 --- a/components/cli/cli/command/image/push_test.go +++ b/components/cli/cli/command/image/push_test.go @@ -7,15 +7,11 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/internal/test" - "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/registry" "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "golang.org/x/net/context" ) func TestNewPushCommandErrors(t *testing.T) { @@ -60,11 +56,8 @@ func TestNewPushCommandErrors(t *testing.T) { func TestNewPushCommandSuccess(t *testing.T) { testCases := []struct { - name string - args []string - trustedPushFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, - ref reference.Named, authConfig types.AuthConfig, - requestPrivilege types.RequestPrivilegeFunc) error + name string + args []string }{ { name: "simple", diff --git a/components/cli/cli/command/node/opts.go b/components/cli/cli/command/node/opts.go index 484ab5edaf..e30e5de910 100644 --- a/components/cli/cli/command/node/opts.go +++ b/components/cli/cli/command/node/opts.go @@ -11,7 +11,6 @@ type nodeOptions struct { } type annotations struct { - name string labels opts.ListOpts } diff --git a/components/cli/cli/command/registry/login.go b/components/cli/cli/command/registry/login.go index af79e967cc..ba1b133054 100644 --- a/components/cli/cli/command/registry/login.go +++ b/components/cli/cli/command/registry/login.go @@ -16,7 +16,6 @@ type loginOptions struct { serverAddress string user string password string - email string } // NewLoginCommand creates a new `docker login` command diff --git a/components/cli/cli/command/swarm/unlock_test.go b/components/cli/cli/command/swarm/unlock_test.go index 991365f873..3dae5239f5 100644 --- a/components/cli/cli/command/swarm/unlock_test.go +++ b/components/cli/cli/command/swarm/unlock_test.go @@ -19,7 +19,6 @@ func TestSwarmUnlockErrors(t *testing.T) { testCases := []struct { name string args []string - input string swarmUnlockFunc func(req swarm.UnlockRequest) error infoFunc func() (types.Info, error) expectedError string diff --git a/components/cli/cli/compose/loader/loader_test.go b/components/cli/cli/compose/loader/loader_test.go index 4e604431d3..43a02204b7 100644 --- a/components/cli/cli/compose/loader/loader_test.go +++ b/components/cli/cli/compose/loader/loader_test.go @@ -632,10 +632,6 @@ func durationPtr(value time.Duration) *time.Duration { return &value } -func int64Ptr(value int64) *int64 { - return &value -} - func uint64Ptr(value uint64) *uint64 { return &value } diff --git a/components/cli/dockerfiles/Dockerfile.lint b/components/cli/dockerfiles/Dockerfile.lint index 14c124642e..71ea437914 100644 --- a/components/cli/dockerfiles/Dockerfile.lint +++ b/components/cli/dockerfiles/Dockerfile.lint @@ -7,5 +7,6 @@ RUN go get -u gopkg.in/alecthomas/gometalinter.v1 && \ gometalinter --install WORKDIR /go/src/github.com/docker/cli +ENV CGO_ENABLED=0 ENTRYPOINT ["/usr/local/bin/gometalinter"] -CMD ["--config=gometalinter.json", "./..."] +CMD ["--config=gometalinter.json", "./..."] diff --git a/components/cli/gometalinter.json b/components/cli/gometalinter.json index f7c6a88a5d..27b6735195 100644 --- a/components/cli/gometalinter.json +++ b/components/cli/gometalinter.json @@ -1,6 +1,6 @@ { "Vendor": true, - "Deadline": "2m", + "Deadline": "3m", "Sort": ["linter", "severity", "path"], "Exclude": ["cli/compose/schema/bindata.go"], @@ -14,6 +14,7 @@ "ineffassign", "interfacer", "lll", + "unused", "vet" ], From 16de65eef40a91072cf2561767ac417c7af6d235 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 23 May 2017 11:33:38 -0400 Subject: [PATCH 47/67] Add misspell lint Signed-off-by: Daniel Nephin Upstream-commit: a712993e93605b9c153eaab16c2bf0cf449a7ec7 Component: cli --- components/cli/cli/command/container/hijack.go | 2 +- components/cli/gometalinter.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/cli/cli/command/container/hijack.go b/components/cli/cli/command/container/hijack.go index 603557f3bd..b6d467d081 100644 --- a/components/cli/cli/command/container/hijack.go +++ b/components/cli/cli/command/container/hijack.go @@ -110,7 +110,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) { func (h *hijackedIOStreamer) beginOutputStream(restoreInput func()) <-chan error { if h.outputStream == nil && h.errorStream == nil { - // Ther is no need to copy output. + // There is no need to copy output. return nil } diff --git a/components/cli/gometalinter.json b/components/cli/gometalinter.json index 27b6735195..eb86268e63 100644 --- a/components/cli/gometalinter.json +++ b/components/cli/gometalinter.json @@ -14,6 +14,7 @@ "ineffassign", "interfacer", "lll", + "misspell", "unused", "vet" ], From c9b100a0be05ca9b41bf47825f85ce5608d8ea54 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 9 Jun 2017 16:41:53 -0400 Subject: [PATCH 48/67] Add unconvert linter Signed-off-by: Daniel Nephin Upstream-commit: 3bf0317fead60e2b8526b47abb2dd243ab57c74d Component: cli --- components/cli/cli/command/formatter/container.go | 4 ++-- components/cli/cli/command/formatter/history.go | 9 +++------ components/cli/cli/command/formatter/image.go | 4 ++-- components/cli/cli/command/swarm/opts.go | 4 ++-- components/cli/cli/command/system/info.go | 3 +-- components/cli/cli/trust/trust.go | 2 +- components/cli/gometalinter.json | 1 + components/cli/opts/opts.go | 2 +- components/cli/opts/quotedstring.go | 2 +- components/cli/opts/throttledevice.go | 5 +---- 10 files changed, 15 insertions(+), 21 deletions(-) diff --git a/components/cli/cli/command/formatter/container.go b/components/cli/cli/command/formatter/container.go index 9b5c24636c..8ffb1a69b2 100644 --- a/components/cli/cli/command/formatter/container.go +++ b/components/cli/cli/command/formatter/container.go @@ -171,11 +171,11 @@ func (c *containerContext) Command() string { } func (c *containerContext) CreatedAt() string { - return time.Unix(int64(c.c.Created), 0).String() + return time.Unix(c.c.Created, 0).String() } func (c *containerContext) RunningFor() string { - createdAt := time.Unix(int64(c.c.Created), 0) + createdAt := time.Unix(c.c.Created, 0) return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago" } diff --git a/components/cli/cli/command/formatter/history.go b/components/cli/cli/command/formatter/history.go index ed2af4637e..161aaa4b00 100644 --- a/components/cli/cli/command/formatter/history.go +++ b/components/cli/cli/command/formatter/history.go @@ -79,21 +79,18 @@ func (c *historyContext) ID() string { } func (c *historyContext) CreatedAt() string { - var created string - created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0))) - return created + return units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0))) } func (c *historyContext) CreatedSince() string { - var created string - created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0))) + created := units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0))) return created + " ago" } func (c *historyContext) CreatedBy() string { createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1) if c.trunc { - createdBy = stringutils.Ellipsis(createdBy, 45) + return stringutils.Ellipsis(createdBy, 45) } return createdBy } diff --git a/components/cli/cli/command/formatter/image.go b/components/cli/cli/command/formatter/image.go index 3aae34ea11..cbd3edad3c 100644 --- a/components/cli/cli/command/formatter/image.go +++ b/components/cli/cli/command/formatter/image.go @@ -234,12 +234,12 @@ func (c *imageContext) Digest() string { } func (c *imageContext) CreatedSince() string { - createdAt := time.Unix(int64(c.i.Created), 0) + createdAt := time.Unix(c.i.Created, 0) return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago" } func (c *imageContext) CreatedAt() string { - return time.Unix(int64(c.i.Created), 0).String() + return time.Unix(c.i.Created, 0).String() } func (c *imageContext) Size() string { diff --git a/components/cli/cli/command/swarm/opts.go b/components/cli/cli/command/swarm/opts.go index 4625835055..0522f9fdfa 100644 --- a/components/cli/cli/command/swarm/opts.go +++ b/components/cli/cli/command/swarm/opts.go @@ -217,13 +217,13 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) { } func addSwarmCAFlags(flags *pflag.FlagSet, opts *swarmOptions) { - flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates (ns|us|ms|s|m|h)") + flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, 90*24*time.Hour, "Validity period for node certificates (ns|us|ms|s|m|h)") flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints") } func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) { flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 5, "Task history retention limit") - flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period (ns|us|ms|s|m|h)") + flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, 5*time.Second, "Dispatcher heartbeat period (ns|us|ms|s|m|h)") flags.Uint64Var(&opts.maxSnapshots, flagMaxSnapshots, 0, "Number of additional Raft snapshots to retain") flags.SetAnnotation(flagMaxSnapshots, "version", []string{"1.25"}) flags.Uint64Var(&opts.snapshotInterval, flagSnapshotInterval, 10000, "Number of log entries between Raft snapshots") diff --git a/components/cli/cli/command/system/info.go b/components/cli/cli/command/system/info.go index 7b91066974..55b867b296 100644 --- a/components/cli/cli/command/system/info.go +++ b/components/cli/cli/command/system/info.go @@ -5,7 +5,6 @@ import ( "io" "sort" "strings" - "time" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -120,7 +119,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { fmt.Fprintf(dockerCli.Out(), " Heartbeat Tick: %d\n", info.Swarm.Cluster.Spec.Raft.HeartbeatTick) fmt.Fprintf(dockerCli.Out(), " Election Tick: %d\n", info.Swarm.Cluster.Spec.Raft.ElectionTick) fmt.Fprintf(dockerCli.Out(), " Dispatcher:\n") - fmt.Fprintf(dockerCli.Out(), " Heartbeat Period: %s\n", units.HumanDuration(time.Duration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod))) + fmt.Fprintf(dockerCli.Out(), " Heartbeat Period: %s\n", units.HumanDuration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod)) fmt.Fprintf(dockerCli.Out(), " CA Configuration:\n") fmt.Fprintf(dockerCli.Out(), " Expiry Duration: %s\n", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry)) fmt.Fprintf(dockerCli.Out(), " Force Rotate: %d\n", info.Swarm.Cluster.Spec.CAConfig.ForceRotate) diff --git a/components/cli/cli/trust/trust.go b/components/cli/cli/trust/trust.go index c325b966a8..600a2acc41 100644 --- a/components/cli/cli/trust/trust.go +++ b/components/cli/cli/trust/trust.go @@ -161,7 +161,7 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI } tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) basicHandler := auth.NewBasicHandler(creds) - modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) + modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) tr := transport.NewTransport(base, modifiers...) return client.NewNotaryRepository( diff --git a/components/cli/gometalinter.json b/components/cli/gometalinter.json index eb86268e63..9ba9ea47a5 100644 --- a/components/cli/gometalinter.json +++ b/components/cli/gometalinter.json @@ -15,6 +15,7 @@ "interfacer", "lll", "misspell", + "unconvert", "unused", "vet" ], diff --git a/components/cli/opts/opts.go b/components/cli/opts/opts.go index f76f308051..d915ec2f76 100644 --- a/components/cli/opts/opts.go +++ b/components/cli/opts/opts.go @@ -179,7 +179,7 @@ func (opts *MapOpts) GetAll() map[string]string { } func (opts *MapOpts) String() string { - return fmt.Sprintf("%v", map[string]string((opts.values))) + return fmt.Sprintf("%v", opts.values) } // Type returns a string name for this Option type diff --git a/components/cli/opts/quotedstring.go b/components/cli/opts/quotedstring.go index fb1e5374bc..09c68a5261 100644 --- a/components/cli/opts/quotedstring.go +++ b/components/cli/opts/quotedstring.go @@ -18,7 +18,7 @@ func (s *QuotedString) Type() string { } func (s *QuotedString) String() string { - return string(*s.value) + return *s.value } func trimQuotes(value string) string { diff --git a/components/cli/opts/throttledevice.go b/components/cli/opts/throttledevice.go index 65dd3ebf68..0959efae35 100644 --- a/components/cli/opts/throttledevice.go +++ b/components/cli/opts/throttledevice.go @@ -52,10 +52,7 @@ func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) } - return &blkiodev.ThrottleDevice{ - Path: split[0], - Rate: uint64(rate), - }, nil + return &blkiodev.ThrottleDevice{Path: split[0], Rate: rate}, nil } // ThrottledeviceOpt defines a map of ThrottleDevices From 003a07af7c82a56ea67585f60a1dba2cbd468934 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 9 Jun 2017 17:16:56 -0400 Subject: [PATCH 49/67] Add unparam linter Signed-off-by: Daniel Nephin Upstream-commit: 3e3934c19f4541b0026a45385ccc0c0dfc389fb4 Component: cli --- .../cli/cli/command/container/opts_test.go | 47 ++++++------------- components/cli/cli/command/container/stats.go | 8 ++-- components/cli/cli/command/formatter/stats.go | 6 +-- components/cli/cli/command/swarm/init.go | 2 +- .../cli/cli/command/swarm/unlock_key.go | 8 ++-- components/cli/cli/command/swarm/update.go | 2 +- .../cli/cli/internal/test/network/client.go | 10 ++-- components/cli/docs/yaml/yaml.go | 4 +- components/cli/gometalinter.json | 3 +- 9 files changed, 36 insertions(+), 54 deletions(-) diff --git a/components/cli/cli/command/container/opts_test.go b/components/cli/cli/command/container/opts_test.go index e10754f31b..6001cde2c0 100644 --- a/components/cli/cli/command/container/opts_test.go +++ b/components/cli/cli/command/container/opts_test.go @@ -61,16 +61,14 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err } -func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) { - config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) - return config, hostConfig, err +func parseMustError(t *testing.T, args string) { + _, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) + assert.Error(t, err, args) } func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) { - config, hostConfig, err := parsetest(t, args) - if err != nil { - t.Fatal(err) - } + config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash")) + assert.NoError(t, err) return config, hostConfig } @@ -86,7 +84,6 @@ func TestParseRunLinks(t *testing.T) { } } -// nolint: gocyclo func TestParseRunAttach(t *testing.T) { if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr { t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) @@ -103,31 +100,17 @@ func TestParseRunAttach(t *testing.T) { if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) } +} - if _, _, err := parsetest(t, "-a"); err == nil { - t.Fatal("Error parsing attach flags, `-a` should be an error but is not") - } - if _, _, err := parsetest(t, "-a invalid"); err == nil { - t.Fatal("Error parsing attach flags, `-a invalid` should be an error but is not") - } - if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil { - t.Fatal("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not") - } - if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil { - t.Fatal("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not") - } - if _, _, err := parsetest(t, "-a stdin -d"); err == nil { - t.Fatal("Error parsing attach flags, `-a stdin -d` should be an error but is not") - } - if _, _, err := parsetest(t, "-a stdout -d"); err == nil { - t.Fatal("Error parsing attach flags, `-a stdout -d` should be an error but is not") - } - if _, _, err := parsetest(t, "-a stderr -d"); err == nil { - t.Fatal("Error parsing attach flags, `-a stderr -d` should be an error but is not") - } - if _, _, err := parsetest(t, "-d --rm"); err == nil { - t.Fatal("Error parsing attach flags, `-d --rm` should be an error but is not") - } +func TestParseRunWithInvalidArgs(t *testing.T) { + parseMustError(t, "-a") + parseMustError(t, "-a invalid") + parseMustError(t, "-a invalid -a stdout") + parseMustError(t, "-a stdout -a stderr -d") + parseMustError(t, "-a stdin -d") + parseMustError(t, "-a stdout -d") + parseMustError(t, "-a stderr -d") + parseMustError(t, "-d --rm") } // nolint: gocyclo diff --git a/components/cli/cli/command/container/stats.go b/components/cli/cli/command/container/stats.go index da6a6d059b..50495f00c3 100644 --- a/components/cli/cli/command/container/stats.go +++ b/components/cli/cli/command/container/stats.go @@ -106,7 +106,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error { closeChan <- err } for _, container := range cs { - s := formatter.NewContainerStats(container.ID[:12], daemonOSType) + s := formatter.NewContainerStats(container.ID[:12]) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -123,7 +123,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error { eh := command.InitEventHandler() eh.Handle("create", func(e events.Message) { if opts.all { - s := formatter.NewContainerStats(e.ID[:12], daemonOSType) + s := formatter.NewContainerStats(e.ID[:12]) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -132,7 +132,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error { }) eh.Handle("start", func(e events.Message) { - s := formatter.NewContainerStats(e.ID[:12], daemonOSType) + s := formatter.NewContainerStats(e.ID[:12]) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -158,7 +158,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error { // Artificially send creation events for the containers we were asked to // monitor (same code path than we use when monitoring all containers). for _, name := range opts.containers { - s := formatter.NewContainerStats(name, daemonOSType) + s := formatter.NewContainerStats(name) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) diff --git a/components/cli/cli/command/formatter/stats.go b/components/cli/cli/command/formatter/stats.go index c0151101a0..f53387e90a 100644 --- a/components/cli/cli/command/formatter/stats.go +++ b/components/cli/cli/command/formatter/stats.go @@ -109,10 +109,8 @@ func NewStatsFormat(source, osType string) Format { } // NewContainerStats returns a new ContainerStats entity and sets in it the given name -func NewContainerStats(container, osType string) *ContainerStats { - return &ContainerStats{ - StatsEntry: StatsEntry{Container: container}, - } +func NewContainerStats(container string) *ContainerStats { + return &ContainerStats{StatsEntry: StatsEntry{Container: container}} } // ContainerStatsWrite renders the context for a list of containers statistics diff --git a/components/cli/cli/command/swarm/init.go b/components/cli/cli/command/swarm/init.go index ea3189a0c7..91b827eb97 100644 --- a/components/cli/cli/command/swarm/init.go +++ b/components/cli/cli/command/swarm/init.go @@ -92,7 +92,7 @@ func runInit(dockerCli command.Cli, flags *pflag.FlagSet, opts initOptions) erro if err != nil { return errors.Wrap(err, "could not fetch unlock key") } - printUnlockCommand(ctx, dockerCli, unlockKeyResp.UnlockKey) + printUnlockCommand(dockerCli.Out(), unlockKeyResp.UnlockKey) } return nil diff --git a/components/cli/cli/command/swarm/unlock_key.go b/components/cli/cli/command/swarm/unlock_key.go index 870f8e4435..4618de7dea 100644 --- a/components/cli/cli/command/swarm/unlock_key.go +++ b/components/cli/cli/command/swarm/unlock_key.go @@ -2,6 +2,7 @@ package swarm import ( "fmt" + "io" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -74,16 +75,15 @@ func runUnlockKey(dockerCli command.Cli, opts unlockKeyOptions) error { return nil } - printUnlockCommand(ctx, dockerCli, unlockKeyResp.UnlockKey) + printUnlockCommand(dockerCli.Out(), unlockKeyResp.UnlockKey) return nil } -func printUnlockCommand(ctx context.Context, dockerCli command.Cli, unlockKey string) { +func printUnlockCommand(out io.Writer, unlockKey string) { if len(unlockKey) > 0 { - fmt.Fprintf(dockerCli.Out(), "To unlock a swarm manager after it restarts, "+ + fmt.Fprintf(out, "To unlock a swarm manager after it restarts, "+ "run the `docker swarm unlock`\ncommand and provide the following key:\n\n %s\n\n"+ "Please remember to store this key in a password manager, since without it you\n"+ "will not be able to restart the manager.\n", unlockKey) } - return } diff --git a/components/cli/cli/command/swarm/update.go b/components/cli/cli/command/swarm/update.go index 561ca0a768..4d751bbd4e 100644 --- a/components/cli/cli/command/swarm/update.go +++ b/components/cli/cli/command/swarm/update.go @@ -65,7 +65,7 @@ func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, opts swarmOptions) e if err != nil { return errors.Wrap(err, "could not fetch unlock key") } - printUnlockCommand(ctx, dockerCli, unlockKeyResp.UnlockKey) + printUnlockCommand(dockerCli.Out(), unlockKeyResp.UnlockKey) } return nil diff --git a/components/cli/cli/internal/test/network/client.go b/components/cli/cli/internal/test/network/client.go index b12fdec6f2..d83288d619 100644 --- a/components/cli/cli/internal/test/network/client.go +++ b/components/cli/cli/internal/test/network/client.go @@ -18,11 +18,11 @@ func (c *FakeClient) NetworkConnect(ctx context.Context, networkID, container st } // NetworkCreate fakes creating a network -func (c *FakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { +func (c *FakeClient) NetworkCreate(_ context.Context, _ string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { return types.NetworkCreateResponse{}, nil } -// NetworkDisconnect fakes disconencting from a network +// NetworkDisconnect fakes disconnecting from a network func (c *FakeClient) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error { return nil } @@ -36,12 +36,12 @@ func (c *FakeClient) NetworkInspect(ctx context.Context, networkID string, optio } // NetworkInspectWithRaw fakes inspecting a network with a raw response -func (c *FakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { +func (c *FakeClient) NetworkInspectWithRaw(_ context.Context, _ string, _ types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { return types.NetworkResource{}, nil, nil } // NetworkList fakes listing networks -func (c *FakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { +func (c *FakeClient) NetworkList(_ context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { return nil, nil } @@ -51,6 +51,6 @@ func (c *FakeClient) NetworkRemove(ctx context.Context, networkID string) error } // NetworksPrune fakes pruning networks -func (c *FakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) { +func (c *FakeClient) NetworksPrune(_ context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) { return types.NetworksPruneReport{}, nil } diff --git a/components/cli/docs/yaml/yaml.go b/components/cli/docs/yaml/yaml.go index 575f9bec5c..edf5e9dd7f 100644 --- a/components/cli/docs/yaml/yaml.go +++ b/components/cli/docs/yaml/yaml.go @@ -66,14 +66,14 @@ func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandle if _, err := io.WriteString(f, filePrepender(filename)); err != nil { return err } - if err := GenYamlCustom(cmd, f, linkHandler); err != nil { + if err := GenYamlCustom(cmd, f); err != nil { return err } return nil } // GenYamlCustom creates custom yaml output -func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error { +func GenYamlCustom(cmd *cobra.Command, w io.Writer) error { cliDoc := cmdDoc{} cliDoc.Name = cmd.CommandPath() diff --git a/components/cli/gometalinter.json b/components/cli/gometalinter.json index 9ba9ea47a5..873f9a2bd8 100644 --- a/components/cli/gometalinter.json +++ b/components/cli/gometalinter.json @@ -1,6 +1,6 @@ { "Vendor": true, - "Deadline": "3m", + "Deadline": "5m", "Sort": ["linter", "severity", "path"], "Exclude": ["cli/compose/schema/bindata.go"], @@ -16,6 +16,7 @@ "lll", "misspell", "unconvert", + "unparam", "unused", "vet" ], From b88ae58881e51ab6d1da0926d40fe6116ba726e8 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 9 Jun 2017 17:42:16 -0400 Subject: [PATCH 50/67] Add gosimple lint Signed-off-by: Daniel Nephin Upstream-commit: 3724fb7f37c81e050ca9d66c79c6026b928af884 Component: cli --- components/cli/cli/command/container/exec.go | 7 +------ components/cli/cli/command/container/opts.go | 2 +- components/cli/cli/command/formatter/disk_usage.go | 2 +- components/cli/cli/command/service/logs.go | 2 +- components/cli/cli/command/service/opts.go | 2 +- components/cli/cli/command/stack/deploy_composefile.go | 5 +---- components/cli/cli/command/stack/list.go | 2 +- components/cli/cli/command/system/info.go | 2 +- components/cli/cli/compose/convert/service.go | 7 ++----- components/cli/cli/config/credentials/native_store.go | 2 +- components/cli/cli/internal/test/store.go | 3 +-- components/cli/gometalinter.json | 3 ++- components/cli/opts/weightdevice.go | 7 +------ 13 files changed, 15 insertions(+), 31 deletions(-) diff --git a/components/cli/cli/command/container/exec.go b/components/cli/cli/command/container/exec.go index 843b609a0e..bbcd34ae0c 100644 --- a/components/cli/cli/command/container/exec.go +++ b/components/cli/cli/command/container/exec.go @@ -111,12 +111,7 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec Tty: execConfig.Tty, } - if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil { - return err - } - // For now don't print this - wait for when we support exec wait() - // fmt.Fprintf(dockerCli.Out(), "%s\n", execID) - return nil + return client.ContainerExecStart(ctx, execID, execStartCheck) } // Interactive exec requested. diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go index 2b7471b352..82d7426c75 100644 --- a/components/cli/cli/command/container/opts.go +++ b/components/cli/cli/command/container/opts.go @@ -23,7 +23,7 @@ import ( ) var ( - deviceCgroupRuleRegexp = regexp.MustCompile("^[acb] ([0-9]+|\\*):([0-9]+|\\*) [rwm]{1,3}$") + deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`) ) // containerOptions is a data object with all the options for creating a container diff --git a/components/cli/cli/command/formatter/disk_usage.go b/components/cli/cli/command/formatter/disk_usage.go index caea53f924..9a3b4b9fe6 100644 --- a/components/cli/cli/command/formatter/disk_usage.go +++ b/components/cli/cli/command/formatter/disk_usage.go @@ -65,7 +65,7 @@ reclaimable: {{.Reclaimable}} } func (ctx *DiskUsageContext) Write() (err error) { - if ctx.Verbose == false { + if !ctx.Verbose { ctx.buffer = bytes.NewBufferString("") ctx.preFormat() diff --git a/components/cli/cli/command/service/logs.go b/components/cli/cli/command/service/logs.go index fbefa5f52e..3d3b3f1d46 100644 --- a/components/cli/cli/command/service/logs.go +++ b/components/cli/cli/command/service/logs.go @@ -164,7 +164,7 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error { // getMaxLength gets the maximum length of the number in base 10 func getMaxLength(i int) int { - return len(strconv.FormatInt(int64(i), 10)) + return len(strconv.Itoa(i)) } type taskFormatter struct { diff --git a/components/cli/cli/command/service/opts.go b/components/cli/cli/command/service/opts.go index 5e70863f01..5d1595b055 100644 --- a/components/cli/cli/command/service/opts.go +++ b/components/cli/cli/command/service/opts.go @@ -354,7 +354,7 @@ func convertNetworks(ctx context.Context, apiClient client.NetworkAPIClient, net if err != nil { return nil, err } - netAttach = append(netAttach, swarm.NetworkAttachmentConfig{Target: net.Target, Aliases: net.Aliases, DriverOpts: net.DriverOpts}) + netAttach = append(netAttach, swarm.NetworkAttachmentConfig(net)) } sort.Sort(byNetworkTarget(netAttach)) return netAttach, nil diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index e8cab8fa4e..8e5dfed4d5 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -134,10 +134,7 @@ func getConfigDetails(composefile string) (composetypes.ConfigDetails, error) { // TODO: support multiple files details.ConfigFiles = []composetypes.ConfigFile{*configFile} details.Environment, err = buildEnvironment(os.Environ()) - if err != nil { - return details, err - } - return details, nil + return details, err } func buildEnvironment(env []string) (map[string]string, error) { diff --git a/components/cli/cli/command/stack/list.go b/components/cli/cli/command/stack/list.go index 4560a98ec9..26566f6cb2 100644 --- a/components/cli/cli/command/stack/list.go +++ b/components/cli/cli/command/stack/list.go @@ -69,7 +69,7 @@ func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.St if err != nil { return nil, err } - m := make(map[string]*formatter.Stack, 0) + m := make(map[string]*formatter.Stack) for _, service := range services { labels := service.Spec.Labels name, ok := labels[convert.LabelNamespace] diff --git a/components/cli/cli/command/system/info.go b/components/cli/cli/command/system/info.go index 55b867b296..f2a404e905 100644 --- a/components/cli/cli/command/system/info.go +++ b/components/cli/cli/command/system/info.go @@ -263,7 +263,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) { fmt.Fprintln(dockerCli.Out(), "Insecure Registries:") for _, registry := range info.RegistryConfig.IndexConfigs { - if registry.Secure == false { + if !registry.Secure { fmt.Fprintf(dockerCli.Out(), " %s\n", registry.Name) } } diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 75c86e9e64..730f5e1ee5 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -563,9 +563,6 @@ func convertCredentialSpec(spec composetypes.CredentialSpecConfig) (*swarm.Crede if spec.File != "" && spec.Registry != "" { return nil, errors.New("Invalid credential spec - must provide one of `File` or `Registry`") } - - return &swarm.CredentialSpec{ - File: spec.File, - Registry: spec.Registry, - }, nil + swarmCredSpec := swarm.CredentialSpec(spec) + return &swarmCredSpec, nil } diff --git a/components/cli/cli/config/credentials/native_store.go b/components/cli/cli/config/credentials/native_store.go index 15bb0e912f..cef34db92f 100644 --- a/components/cli/cli/config/credentials/native_store.go +++ b/components/cli/cli/config/credentials/native_store.go @@ -73,7 +73,7 @@ func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) { if err != nil { return nil, err } - ac, _ := fileConfigs[registry] // might contain Email + ac := fileConfigs[registry] // might contain Email ac.Username = creds.Username ac.Password = creds.Password ac.IdentityToken = creds.IdentityToken diff --git a/components/cli/cli/internal/test/store.go b/components/cli/cli/internal/test/store.go index 28e52bab05..e5b3de7abb 100644 --- a/components/cli/cli/internal/test/store.go +++ b/components/cli/cli/internal/test/store.go @@ -53,8 +53,7 @@ func (c *fakeStore) Get(serverAddress string) (types.AuthConfig, error) { if c.getFunc != nil { return c.getFunc(serverAddress) } - authConfig, _ := c.store[serverAddress] - return authConfig, nil + return c.store[serverAddress], nil } func (c *fakeStore) GetAll() (map[string]types.AuthConfig, error) { diff --git a/components/cli/gometalinter.json b/components/cli/gometalinter.json index 873f9a2bd8..4791bb01fa 100644 --- a/components/cli/gometalinter.json +++ b/components/cli/gometalinter.json @@ -1,6 +1,6 @@ { "Vendor": true, - "Deadline": "5m", + "Deadline": "8m", "Sort": ["linter", "severity", "path"], "Exclude": ["cli/compose/schema/bindata.go"], @@ -11,6 +11,7 @@ "gofmt", "goimports", "golint", + "gosimple", "ineffassign", "interfacer", "lll", diff --git a/components/cli/opts/weightdevice.go b/components/cli/opts/weightdevice.go index 7e3d064f27..46ce9b6567 100644 --- a/components/cli/opts/weightdevice.go +++ b/components/cli/opts/weightdevice.go @@ -75,12 +75,7 @@ func (opt *WeightdeviceOpt) String() string { // GetList returns a slice of pointers to WeightDevices. func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { - var weightdevice []*blkiodev.WeightDevice - for _, v := range opt.values { - weightdevice = append(weightdevice, v) - } - - return weightdevice + return opt.values } // Type returns the option type From 6c8b2e5bbf0da0b969152af5516a6479020a66be Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 14 Jun 2017 17:30:45 -0700 Subject: [PATCH 51/67] Use a fork of gometalinter Until https://github.com/alecthomas/gometalinter/pull/289 is merged. This reduces the runtime of the linter by 50x Signed-off-by: Daniel Nephin Upstream-commit: 4881ef507fe7a6c3ff7c06c112cfb4025265ae3e Component: cli --- components/cli/dockerfiles/Dockerfile.lint | 2 +- components/cli/gometalinter.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/cli/dockerfiles/Dockerfile.lint b/components/cli/dockerfiles/Dockerfile.lint index 71ea437914..240af474c5 100644 --- a/components/cli/dockerfiles/Dockerfile.lint +++ b/components/cli/dockerfiles/Dockerfile.lint @@ -2,7 +2,7 @@ FROM golang:1.8.3-alpine RUN apk add -U git -RUN go get -u gopkg.in/alecthomas/gometalinter.v1 && \ +RUN go get -u gopkg.in/dnephin/gometalinter.v1 && \ mv /go/bin/gometalinter.v1 /usr/local/bin/gometalinter && \ gometalinter --install diff --git a/components/cli/gometalinter.json b/components/cli/gometalinter.json index 4791bb01fa..b5956dca60 100644 --- a/components/cli/gometalinter.json +++ b/components/cli/gometalinter.json @@ -1,6 +1,6 @@ { "Vendor": true, - "Deadline": "8m", + "Deadline": "2m", "Sort": ["linter", "severity", "path"], "Exclude": ["cli/compose/schema/bindata.go"], From cd9e55249f819ec9370d8aff578ed5d03fdd8c72 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 15 Jun 2017 17:24:41 +0200 Subject: [PATCH 52/67] Added usage example for -e for exec Signed-off-by: Vishnu Narayanan Signed-off-by: Sebastiaan van Stijn Upstream-commit: c848d9b22d43fd89121f8e1608b9c3126f347049 Component: cli --- components/cli/docs/reference/commandline/exec.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/cli/docs/reference/commandline/exec.md b/components/cli/docs/reference/commandline/exec.md index e2e5d607b9..2bfa74483c 100644 --- a/components/cli/docs/reference/commandline/exec.md +++ b/components/cli/docs/reference/commandline/exec.md @@ -76,6 +76,17 @@ $ docker exec -it ubuntu_bash bash This will create a new Bash session in the container `ubuntu_bash`. +Next, set an environment variable in the current bash session. + +```bash +$ docker exec -it -e VAR=1 ubuntu_bash bash +``` + +This will create a new Bash session in the container `ubuntu_bash` with environment +variable `$VAR` set to "1". Note that this environment variable will only be valid +on the current Bash session. + + ### Try to run `docker exec` on a paused container If the container is paused, then the `docker exec` command will fail with an error: From 3f1fb4c18f44794a5ca8ea1388ae0140cfec2262 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 15 Jun 2017 12:28:08 -0400 Subject: [PATCH 53/67] removed logging from command/formatter package tests Signed-off-by: Arash Deshmeh Upstream-commit: 7f71d0d979d9634ab72fa502f88609b901308d58 Component: cli --- .../cli/command/formatter/container_test.go | 19 +++++++++--------- .../cli/cli/command/formatter/network_test.go | 20 +++++++++---------- .../cli/cli/command/formatter/node_test.go | 20 +++++++++---------- .../cli/cli/command/formatter/service_test.go | 20 +++++++++---------- .../cli/cli/command/formatter/volume_test.go | 20 +++++++++---------- 5 files changed, 49 insertions(+), 50 deletions(-) diff --git a/components/cli/cli/command/formatter/container_test.go b/components/cli/cli/command/formatter/container_test.go index cfe42df49f..5282386469 100644 --- a/components/cli/cli/command/formatter/container_test.go +++ b/components/cli/cli/command/formatter/container_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestContainerPsContext(t *testing.T) { @@ -357,12 +358,11 @@ func TestContainerContextWriteJSON(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var m map[string]interface{} - if err := json.Unmarshal([]byte(line), &m); err != nil { - t.Fatal(err) - } - assert.Equal(t, expectedJSONs[i], m) + err := json.Unmarshal([]byte(line), &m) + require.NoError(t, err, msg) + assert.Equal(t, expectedJSONs[i], m, msg) } } @@ -377,12 +377,11 @@ func TestContainerContextWriteJSONField(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var s string - if err := json.Unmarshal([]byte(line), &s); err != nil { - t.Fatal(err) - } - assert.Equal(t, containers[i].ID, s) + err := json.Unmarshal([]byte(line), &s) + require.NoError(t, err, msg) + assert.Equal(t, containers[i].ID, s, msg) } } diff --git a/components/cli/cli/command/formatter/network_test.go b/components/cli/cli/command/formatter/network_test.go index b8cab078e7..201838cf74 100644 --- a/components/cli/cli/command/formatter/network_test.go +++ b/components/cli/cli/command/formatter/network_test.go @@ -3,6 +3,7 @@ package formatter import ( "bytes" "encoding/json" + "fmt" "strings" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNetworkContext(t *testing.T) { @@ -183,12 +185,11 @@ func TestNetworkContextWriteJSON(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var m map[string]interface{} - if err := json.Unmarshal([]byte(line), &m); err != nil { - t.Fatal(err) - } - assert.Equal(t, expectedJSONs[i], m) + err := json.Unmarshal([]byte(line), &m) + require.NoError(t, err, msg) + assert.Equal(t, expectedJSONs[i], m, msg) } } @@ -203,11 +204,10 @@ func TestNetworkContextWriteJSONField(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var s string - if err := json.Unmarshal([]byte(line), &s); err != nil { - t.Fatal(err) - } - assert.Equal(t, networks[i].ID, s) + err := json.Unmarshal([]byte(line), &s) + require.NoError(t, err, msg) + assert.Equal(t, networks[i].ID, s, msg) } } diff --git a/components/cli/cli/command/formatter/node_test.go b/components/cli/cli/command/formatter/node_test.go index 9ad25d10d5..42fdf5138d 100644 --- a/components/cli/cli/command/formatter/node_test.go +++ b/components/cli/cli/command/formatter/node_test.go @@ -3,6 +3,7 @@ package formatter import ( "bytes" "encoding/json" + "fmt" "strings" "testing" @@ -10,6 +11,7 @@ import ( "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/stringid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNodeContext(t *testing.T) { @@ -248,12 +250,11 @@ func TestNodeContextWriteJSON(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var m map[string]interface{} - if err := json.Unmarshal([]byte(line), &m); err != nil { - t.Fatal(err) - } - assert.Equal(t, testcase.expected[i], m) + err := json.Unmarshal([]byte(line), &m) + require.NoError(t, err, msg) + assert.Equal(t, testcase.expected[i], m, msg) } } } @@ -269,12 +270,11 @@ func TestNodeContextWriteJSONField(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var s string - if err := json.Unmarshal([]byte(line), &s); err != nil { - t.Fatal(err) - } - assert.Equal(t, nodes[i].ID, s) + err := json.Unmarshal([]byte(line), &s) + require.NoError(t, err, msg) + assert.Equal(t, nodes[i].ID, s, msg) } } diff --git a/components/cli/cli/command/formatter/service_test.go b/components/cli/cli/command/formatter/service_test.go index 629f853930..e230f80fbf 100644 --- a/components/cli/cli/command/formatter/service_test.go +++ b/components/cli/cli/command/formatter/service_test.go @@ -3,11 +3,13 @@ package formatter import ( "bytes" "encoding/json" + "fmt" "strings" "testing" "github.com/docker/docker/api/types/swarm" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestServiceContextWrite(t *testing.T) { @@ -200,12 +202,11 @@ func TestServiceContextWriteJSON(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var m map[string]interface{} - if err := json.Unmarshal([]byte(line), &m); err != nil { - t.Fatal(err) - } - assert.Equal(t, expectedJSONs[i], m) + err := json.Unmarshal([]byte(line), &m) + require.NoError(t, err, msg) + assert.Equal(t, expectedJSONs[i], m, msg) } } func TestServiceContextWriteJSONField(t *testing.T) { @@ -229,11 +230,10 @@ func TestServiceContextWriteJSONField(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var s string - if err := json.Unmarshal([]byte(line), &s); err != nil { - t.Fatal(err) - } - assert.Equal(t, services[i].Spec.Name, s) + err := json.Unmarshal([]byte(line), &s) + require.NoError(t, err, msg) + assert.Equal(t, services[i].Spec.Name, s, msg) } } diff --git a/components/cli/cli/command/formatter/volume_test.go b/components/cli/cli/command/formatter/volume_test.go index bf1100893f..f2c21a83c2 100644 --- a/components/cli/cli/command/formatter/volume_test.go +++ b/components/cli/cli/command/formatter/volume_test.go @@ -3,12 +3,14 @@ package formatter import ( "bytes" "encoding/json" + "fmt" "strings" "testing" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestVolumeContext(t *testing.T) { @@ -153,12 +155,11 @@ func TestVolumeContextWriteJSON(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var m map[string]interface{} - if err := json.Unmarshal([]byte(line), &m); err != nil { - t.Fatal(err) - } - assert.Equal(t, expectedJSONs[i], m) + err := json.Unmarshal([]byte(line), &m) + require.NoError(t, err, msg) + assert.Equal(t, expectedJSONs[i], m, msg) } } @@ -173,11 +174,10 @@ func TestVolumeContextWriteJSONField(t *testing.T) { t.Fatal(err) } for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - t.Logf("Output: line %d: %s", i, line) + msg := fmt.Sprintf("Output: line %d: %s", i, line) var s string - if err := json.Unmarshal([]byte(line), &s); err != nil { - t.Fatal(err) - } - assert.Equal(t, volumes[i].Name, s) + err := json.Unmarshal([]byte(line), &s) + require.NoError(t, err, msg) + assert.Equal(t, volumes[i].Name, s, msg) } } From ad6108e6a5842b204bfb7690262cf6cd9219b583 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 14 Jun 2017 15:44:06 -0700 Subject: [PATCH 54/67] Try out circleci workflow Signed-off-by: Daniel Nephin Upstream-commit: 75aebdd4639d8dfb59e1526bea68e4da3ad5e267 Component: cli --- components/cli/circle.yml | 52 +++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/components/cli/circle.yml b/components/cli/circle.yml index 3ba3f1d2fe..9e271b0a8c 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -1,14 +1,11 @@ version: 2 + jobs: - build: + + lint: working_directory: /work - docker: - - image: docker:17.05 - parallelism: 4 + docker: [{image: 'docker:17.05'}] steps: - - run: - name: "Install Git and SSH" - command: apk add -U git openssh - checkout - setup_remote_docker - run: @@ -16,40 +13,69 @@ jobs: - run: name: "Lint" command: | - if [ "$CIRCLE_NODE_INDEX" != "0" ]; then exit; fi dockerfile=dockerfiles/Dockerfile.lint echo "COPY . ." >> $dockerfile docker build -f $dockerfile --tag cli-linter . docker run cli-linter + + cross: + working_directory: /work + docker: [{image: 'docker:17.05'}] + steps: + - checkout + - setup_remote_docker - run: name: "Cross" command: | - if [ "$CIRCLE_NODE_INDEX" != "1" ]; then exit; fi dockerfile=dockerfiles/Dockerfile.cross echo "COPY . ." >> $dockerfile docker build -f $dockerfile --tag cli-builder . docker run --name cross cli-builder make cross docker cp cross:/go/src/github.com/docker/cli/build /work/build + - store_artifacts: + path: /work/build + + test: + working_directory: /work + docker: [{image: 'docker:17.05'}] + steps: + - checkout + - setup_remote_docker - run: name: "Unit Test with Coverage" command: | - if [ "$CIRCLE_NODE_INDEX" != "2" ]; then exit; fi dockerfile=dockerfiles/Dockerfile.dev echo "COPY . ." >> $dockerfile docker build -f $dockerfile --tag cli-builder . docker run --name test cli-builder make test-coverage + + - run: + name: "Upload to Codecov" + command: | docker cp test:/go/src/github.com/docker/cli/coverage.txt coverage.txt apk add -U bash curl curl -s https://codecov.io/bash | bash + + validate: + working_directory: /work + docker: [{image: 'docker:17.05'}] + steps: + - checkout + - setup_remote_docker - run: name: "Validate Vendor, Docs, and Code Generation" command: | - if [ "$CIRCLE_NODE_INDEX" != "3" ]; then exit; fi dockerfile=dockerfiles/Dockerfile.dev echo "COPY . ." >> $dockerfile rm -f .dockerignore # include .git docker build -f $dockerfile --tag cli-builder . docker run cli-builder make -B vendor compose-jsonschema manpages yamldocs - - store_artifacts: - path: /work/build +workflows: + version: 2 + ci: + jobs: + - lint + - cross + - test + - validate From 9fa6954b472d6ac0d11a6848d5aeac4d7362fc7d Mon Sep 17 00:00:00 2001 From: zebrilee Date: Wed, 14 Jun 2017 22:25:24 +0200 Subject: [PATCH 55/67] comment the return of setHelpFunc in order to show --help even if the daemon is not running. Then add a if statement in isSupported function to check if the daemon is running Signed-off-by: zebrilee revert change on docker.go, set HasExperimental to true in cli.go Signed-off-by: zebrilee Upstream-commit: cca30cb1d9d1a8b9facce27e92e0c331a41659b7 Component: cli --- components/cli/cli/command/cli.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/cli/cli/command/cli.go b/components/cli/cli/command/cli.go index 59efa9ac2a..a8529b512d 100644 --- a/components/cli/cli/command/cli.go +++ b/components/cli/cli/command/cli.go @@ -202,6 +202,9 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) { cli.client.UpdateClientVersion(ping.APIVersion) } + } else { + // Default to true if we fail to connect to daemon + cli.server = ServerInfo{HasExperimental: true} } return nil From d49d72e7d6b1f5d0756a6ed3e3f2460922205e33 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Tue, 31 Jan 2017 05:15:51 +0000 Subject: [PATCH 56/67] Allow Proxy Configuration in config.json This commit modifies config.json to allow for any proxies allowed in build-args to be configured. These values will then be used by default as build-args in docker build. Signed-off-by: Dave Tucker Upstream-commit: 35f1e301b573f057937c0d3f3232e4f03cd21a5d Component: cli --- components/cli/cli/command/container/run.go | 16 ++- components/cli/cli/command/image/build.go | 2 +- components/cli/cli/config/configfile/file.go | 43 +++++++ .../cli/cli/config/configfile/file_test.go | 117 ++++++++++++++++++ 4 files changed, 175 insertions(+), 3 deletions(-) diff --git a/components/cli/cli/command/container/run.go b/components/cli/cli/command/container/run.go index 0164219b7f..56f0d0d57b 100644 --- a/components/cli/cli/command/container/run.go +++ b/components/cli/cli/command/container/run.go @@ -13,6 +13,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/promise" @@ -96,14 +97,24 @@ func isLocalhost(ip string) bool { return localhostIPRegexp.MatchString(ip) } -func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error { +func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error { + proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), copts.env.GetAll()) + newEnv := []string{} + for k, v := range proxyConfig { + if v == nil { + newEnv = append(newEnv, k) + } else { + newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v)) + } + } + copts.env = *opts.NewListOptsRef(&newEnv, nil) containerConfig, err := parse(flags, copts) // just in case the parse does not exit if err != nil { reportError(dockerCli.Err(), "run", err.Error(), true) return cli.StatusError{StatusCode: 125} } - return runContainer(dockerCli, opts, copts, containerConfig) + return runContainer(dockerCli, ropts, copts, containerConfig) } // nolint: gocyclo @@ -159,6 +170,7 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID) defer signal.StopCatch(sigc) } + var ( waitDisplayID chan struct{} errCh chan error diff --git a/components/cli/cli/command/image/build.go b/components/cli/cli/command/image/build.go index 1a6cb951bc..2077914717 100644 --- a/components/cli/cli/command/image/build.go +++ b/components/cli/cli/command/image/build.go @@ -290,7 +290,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { Dockerfile: relDockerfile, ShmSize: options.shmSize.Value(), Ulimits: options.ulimits.GetList(), - BuildArgs: opts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll()), + BuildArgs: dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()), AuthConfigs: authConfigs, Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), CacheFrom: options.cacheFrom, diff --git a/components/cli/cli/config/configfile/file.go b/components/cli/cli/config/configfile/file.go index 7214325d87..78024acf54 100644 --- a/components/cli/cli/config/configfile/file.go +++ b/components/cli/cli/config/configfile/file.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/pkg/errors" ) @@ -41,6 +42,15 @@ type ConfigFile struct { ConfigFormat string `json:"configFormat,omitempty"` NodesFormat string `json:"nodesFormat,omitempty"` PruneFilters []string `json:"pruneFilters,omitempty"` + Proxies map[string]ProxyConfig `json:"proxies,omitempty"` +} + +// ProxyConfig contains proxy configuration settings +type ProxyConfig struct { + HTTPProxy string `json:"httpProxy,omitempty"` + HTTPSProxy string `json:"httpsProxy,omitempty"` + NoProxy string `json:"noProxy,omitempty"` + FTPProxy string `json:"ftpProxy,omitempty"` } // LegacyLoadFromReader reads the non-nested configuration data given and sets up the @@ -152,6 +162,39 @@ func (configFile *ConfigFile) Save() error { return configFile.SaveToWriter(f) } +// ParseProxyConfig computes proxy configuration by retreiving the config for the provided host and +// then checking this against any environment variables provided to the container +func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts []string) map[string]*string { + var cfgKey string + + if _, ok := configFile.Proxies[host]; !ok { + cfgKey = "default" + } else { + cfgKey = host + } + + config, _ := configFile.Proxies[cfgKey] + permitted := map[string]*string{ + "HTTP_PROXY": &config.HTTPProxy, + "HTTPS_PROXY": &config.HTTPSProxy, + "NO_PROXY": &config.NoProxy, + "FTP_PROXY": &config.FTPProxy, + } + m := opts.ConvertKVStringsToMapWithNil(runOpts) + for k := range permitted { + if *permitted[k] == "" { + continue + } + if _, ok := m[k]; !ok { + m[k] = permitted[k] + } + if _, ok := m[strings.ToLower(k)]; !ok { + m[strings.ToLower(k)] = permitted[k] + } + } + return m +} + // encodeAuth creates a base64 encoded string to containing authorization information func encodeAuth(authConfig *types.AuthConfig) string { if authConfig.Username == "" && authConfig.Password == "" { diff --git a/components/cli/cli/config/configfile/file_test.go b/components/cli/cli/config/configfile/file_test.go index 435797f681..8c84347719 100644 --- a/components/cli/cli/config/configfile/file_test.go +++ b/components/cli/cli/config/configfile/file_test.go @@ -1,9 +1,11 @@ package configfile import ( + "fmt" "testing" "github.com/docker/docker/api/types" + "github.com/stretchr/testify/assert" ) func TestEncodeAuth(t *testing.T) { @@ -25,3 +27,118 @@ func TestEncodeAuth(t *testing.T) { t.Fatal("AuthString encoding isn't correct.") } } + +func TestProxyConfig(t *testing.T) { + httpProxy := "http://proxy.mycorp.com:3128" + httpsProxy := "https://user:password@proxy.mycorp.com:3129" + ftpProxy := "http://ftpproxy.mycorp.com:21" + noProxy := "*.intra.mycorp.com" + defaultProxyConfig := ProxyConfig{ + HTTPProxy: httpProxy, + HTTPSProxy: httpsProxy, + FTPProxy: ftpProxy, + NoProxy: noProxy, + } + + cfg := ConfigFile{ + Proxies: map[string]ProxyConfig{ + "default": defaultProxyConfig, + }, + } + + proxyConfig := cfg.ParseProxyConfig("/var/run/docker.sock", []string{}) + expected := map[string]*string{ + "HTTP_PROXY": &httpProxy, + "http_proxy": &httpProxy, + "HTTPS_PROXY": &httpsProxy, + "https_proxy": &httpsProxy, + "FTP_PROXY": &ftpProxy, + "ftp_proxy": &ftpProxy, + "NO_PROXY": &noProxy, + "no_proxy": &noProxy, + } + assert.Equal(t, expected, proxyConfig) +} + +func TestProxyConfigOverride(t *testing.T) { + httpProxy := "http://proxy.mycorp.com:3128" + overrideHTTPProxy := "http://proxy.example.com:3128" + overrideNoProxy := "" + httpsProxy := "https://user:password@proxy.mycorp.com:3129" + ftpProxy := "http://ftpproxy.mycorp.com:21" + noProxy := "*.intra.mycorp.com" + defaultProxyConfig := ProxyConfig{ + HTTPProxy: httpProxy, + HTTPSProxy: httpsProxy, + FTPProxy: ftpProxy, + NoProxy: noProxy, + } + + cfg := ConfigFile{ + Proxies: map[string]ProxyConfig{ + "default": defaultProxyConfig, + }, + } + + ropts := []string{ + fmt.Sprintf("HTTP_PROXY=%s", overrideHTTPProxy), + "NO_PROXY=", + } + proxyConfig := cfg.ParseProxyConfig("/var/run/docker.sock", ropts) + expected := map[string]*string{ + "HTTP_PROXY": &overrideHTTPProxy, + "http_proxy": &httpProxy, + "HTTPS_PROXY": &httpsProxy, + "https_proxy": &httpsProxy, + "FTP_PROXY": &ftpProxy, + "ftp_proxy": &ftpProxy, + "NO_PROXY": &overrideNoProxy, + "no_proxy": &noProxy, + } + assert.Equal(t, expected, proxyConfig) +} + +func TestProxyConfigPerHost(t *testing.T) { + httpProxy := "http://proxy.mycorp.com:3128" + httpsProxy := "https://user:password@proxy.mycorp.com:3129" + ftpProxy := "http://ftpproxy.mycorp.com:21" + noProxy := "*.intra.mycorp.com" + + extHTTPProxy := "http://proxy.example.com:3128" + extHTTPSProxy := "https://user:password@proxy.example.com:3129" + extFTPProxy := "http://ftpproxy.example.com:21" + extNoProxy := "*.intra.example.com" + + defaultProxyConfig := ProxyConfig{ + HTTPProxy: httpProxy, + HTTPSProxy: httpsProxy, + FTPProxy: ftpProxy, + NoProxy: noProxy, + } + externalProxyConfig := ProxyConfig{ + HTTPProxy: extHTTPProxy, + HTTPSProxy: extHTTPSProxy, + FTPProxy: extFTPProxy, + NoProxy: extNoProxy, + } + + cfg := ConfigFile{ + Proxies: map[string]ProxyConfig{ + "default": defaultProxyConfig, + "tcp://example.docker.com:2376": externalProxyConfig, + }, + } + + proxyConfig := cfg.ParseProxyConfig("tcp://example.docker.com:2376", []string{}) + expected := map[string]*string{ + "HTTP_PROXY": &extHTTPProxy, + "http_proxy": &extHTTPProxy, + "HTTPS_PROXY": &extHTTPSProxy, + "https_proxy": &extHTTPSProxy, + "FTP_PROXY": &extFTPProxy, + "ftp_proxy": &extFTPProxy, + "NO_PROXY": &extNoProxy, + "no_proxy": &extNoProxy, + } + assert.Equal(t, expected, proxyConfig) +} From e2861e049e918cfd01a21315b79c1120021a79c0 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 20 Jun 2017 14:00:01 -0400 Subject: [PATCH 57/67] add unit tests to stack package Signed-off-by: Arash Deshmeh Upstream-commit: 535af2d8689ae33e78ac45de9b70553845efea48 Component: cli --- components/cli/cli/command/node/ps_test.go | 6 +- .../cli/cli/command/stack/client_test.go | 40 +++- components/cli/cli/command/stack/list.go | 4 +- components/cli/cli/command/stack/list_test.go | 122 ++++++++++++ components/cli/cli/command/stack/opts_test.go | 49 +++++ components/cli/cli/command/stack/ps_test.go | 185 ++++++++++++++++++ .../cli/cli/command/stack/remove_test.go | 2 +- components/cli/cli/command/stack/services.go | 4 +- .../cli/cli/command/stack/services_test.go | 180 +++++++++++++++++ .../testdata/bundlefile_with_two_services.dab | 29 +++ .../stack/testdata/stack-list-sort.golden | 3 + .../testdata/stack-list-with-format.golden | 1 + .../testdata/stack-list-without-format.golden | 2 + .../stack-ps-with-config-format.golden | 1 + .../testdata/stack-ps-with-format.golden | 1 + .../stack-ps-with-no-resolve-option.golden | 1 + .../stack-ps-with-no-trunc-option.golden | 1 + .../stack-ps-with-quiet-option.golden | 1 + .../testdata/stack-ps-without-format.golden | 2 + .../stack-services-with-config-format.golden | 1 + .../stack-services-with-format.golden | 1 + .../stack-services-with-quiet-option.golden | 1 + .../stack-services-without-format.golden | 2 + .../cli/cli/internal/test/builders/service.go | 36 ++++ .../cli/cli/internal/test/builders/task.go | 46 ++++- 25 files changed, 703 insertions(+), 18 deletions(-) create mode 100644 components/cli/cli/command/stack/list_test.go create mode 100644 components/cli/cli/command/stack/opts_test.go create mode 100644 components/cli/cli/command/stack/ps_test.go create mode 100644 components/cli/cli/command/stack/services_test.go create mode 100644 components/cli/cli/command/stack/testdata/bundlefile_with_two_services.dab create mode 100644 components/cli/cli/command/stack/testdata/stack-list-sort.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-list-with-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-list-without-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-ps-with-config-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-ps-with-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-ps-with-no-resolve-option.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-ps-with-no-trunc-option.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-ps-with-quiet-option.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-ps-without-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-services-with-config-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-services-with-format.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-services-with-quiet-option.golden create mode 100644 components/cli/cli/command/stack/testdata/stack-services-without-format.golden diff --git a/components/cli/cli/command/node/ps_test.go b/components/cli/cli/command/node/ps_test.go index ddc0eba0d4..d25a55f0cf 100644 --- a/components/cli/cli/command/node/ps_test.go +++ b/components/cli/cli/command/node/ps_test.go @@ -103,11 +103,11 @@ func TestNodePs(t *testing.T) { }, taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { return []swarm.Task{ - *Task(TaskID("taskID1"), ServiceID("failure"), + *Task(TaskID("taskID1"), TaskServiceID("failure"), WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), StatusErr("a task error"))), - *Task(TaskID("taskID2"), ServiceID("failure"), + *Task(TaskID("taskID2"), TaskServiceID("failure"), WithStatus(Timestamp(time.Now().Add(-3*time.Hour)), StatusErr("a task error"))), - *Task(TaskID("taskID3"), ServiceID("failure"), + *Task(TaskID("taskID3"), TaskServiceID("failure"), WithStatus(Timestamp(time.Now().Add(-4*time.Hour)), StatusErr("a task error"))), }, nil }, diff --git a/components/cli/cli/command/stack/client_test.go b/components/cli/cli/command/stack/client_test.go index 22be9dc06a..50442783fa 100644 --- a/components/cli/cli/command/stack/client_test.go +++ b/components/cli/cli/command/stack/client_test.go @@ -25,14 +25,17 @@ type fakeClient struct { 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 + 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) + nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error) + taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error) + nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error) + serviceRemoveFunc func(serviceID string) error + networkRemoveFunc func(networkID string) error + secretRemoveFunc func(secretID string) error + configRemoveFunc func(configID string) error } func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { @@ -102,6 +105,27 @@ func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListO return configsList, nil } +func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { + if cli.taskListFunc != nil { + return cli.taskListFunc(options) + } + return []swarm.Task{}, nil +} + +func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { + if cli.nodeListFunc != nil { + return cli.nodeListFunc(options) + } + return []swarm.Node{}, nil +} + +func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) { + if cli.nodeInspectWithRaw != nil { + return cli.nodeInspectWithRaw(ref) + } + return swarm.Node{}, nil, nil +} + func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error { if cli.serviceRemoveFunc != nil { return cli.serviceRemoveFunc(serviceID) diff --git a/components/cli/cli/command/stack/list.go b/components/cli/cli/command/stack/list.go index 4560a98ec9..a14af0ad21 100644 --- a/components/cli/cli/command/stack/list.go +++ b/components/cli/cli/command/stack/list.go @@ -18,7 +18,7 @@ type listOptions struct { format string } -func newListCommand(dockerCli *command.DockerCli) *cobra.Command { +func newListCommand(dockerCli command.Cli) *cobra.Command { opts := listOptions{} cmd := &cobra.Command{ @@ -36,7 +36,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runList(dockerCli *command.DockerCli, opts listOptions) error { +func runList(dockerCli command.Cli, opts listOptions) error { client := dockerCli.Client() ctx := context.Background() diff --git a/components/cli/cli/command/stack/list_test.go b/components/cli/cli/command/stack/list_test.go new file mode 100644 index 0000000000..4f258977ed --- /dev/null +++ b/components/cli/cli/command/stack/list_test.go @@ -0,0 +1,122 @@ +package stack + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/docker/cli/cli/internal/test" + // Import builders to get the builder function as package function + . "github.com/docker/cli/cli/internal/test/builders" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/pkg/testutil" + "github.com/docker/docker/pkg/testutil/golden" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func TestListErrors(t *testing.T) { + testCases := []struct { + args []string + flags map[string]string + serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error) + expectedError string + }{ + { + args: []string{"foo"}, + expectedError: "accepts no argument", + }, + { + flags: map[string]string{ + "format": "{{invalid format}}", + }, + expectedError: "Template parsing error", + }, + { + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{}, errors.Errorf("error getting services") + }, + expectedError: "error getting services", + }, + { + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{*Service()}, nil + }, + expectedError: "cannot get label", + }, + } + + for _, tc := range testCases { + cmd := newListCommand(test.NewFakeCli(&fakeClient{ + serviceListFunc: tc.serviceListFunc, + }, &bytes.Buffer{})) + cmd.SetArgs(tc.args) + cmd.SetOutput(ioutil.Discard) + for key, value := range tc.flags { + cmd.Flags().Set(key, value) + } + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} + +func TestListWithFormat(t *testing.T) { + buf := new(bytes.Buffer) + cmd := newListCommand(test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-foo", + }), + )}, nil + }, + }, buf)) + cmd.Flags().Set("format", "{{ .Name }}") + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-list-with-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestListWithoutFormat(t *testing.T) { + buf := new(bytes.Buffer) + cmd := newListCommand(test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-foo", + }), + )}, nil + }, + }, buf)) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-list-without-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestListOrder(t *testing.T) { + buf := new(bytes.Buffer) + cmd := newListCommand(test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-foo", + }), + ), + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-bar", + }), + ), + }, nil + }, + }, buf)) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-list-sort.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} diff --git a/components/cli/cli/command/stack/opts_test.go b/components/cli/cli/command/stack/opts_test.go new file mode 100644 index 0000000000..b57dcd89f6 --- /dev/null +++ b/components/cli/cli/command/stack/opts_test.go @@ -0,0 +1,49 @@ +package stack + +import ( + "bytes" + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadBundlefileErrors(t *testing.T) { + testCases := []struct { + namespace string + path string + expectedError error + }{ + { + namespace: "namespace_foo", + expectedError: fmt.Errorf("Bundle %s.dab not found", "namespace_foo"), + }, + { + namespace: "namespace_foo", + path: "invalid_path", + expectedError: fmt.Errorf("Bundle %s not found", "invalid_path"), + }, + { + namespace: "namespace_foo", + path: filepath.Join("testdata", "bundlefile_with_invalid_syntax"), + expectedError: fmt.Errorf("Error reading"), + }, + } + + for _, tc := range testCases { + _, err := loadBundlefile(&bytes.Buffer{}, tc.namespace, tc.path) + assert.Error(t, err, tc.expectedError) + } +} + +func TestLoadBundlefile(t *testing.T) { + buf := new(bytes.Buffer) + + namespace := "" + path := filepath.Join("testdata", "bundlefile_with_two_services.dab") + bundleFile, err := loadBundlefile(buf, namespace, path) + + assert.NoError(t, err) + assert.Equal(t, len(bundleFile.Services), 2) +} diff --git a/components/cli/cli/command/stack/ps_test.go b/components/cli/cli/command/stack/ps_test.go new file mode 100644 index 0000000000..afda419eb4 --- /dev/null +++ b/components/cli/cli/command/stack/ps_test.go @@ -0,0 +1,185 @@ +package stack + +import ( + "bytes" + "io/ioutil" + "testing" + "time" + + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/internal/test" + // Import builders to get the builder function as package function + . "github.com/docker/cli/cli/internal/test/builders" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/pkg/testutil" + "github.com/docker/docker/pkg/testutil/golden" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func TestStackPsErrors(t *testing.T) { + testCases := []struct { + args []string + taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error) + expectedError string + }{ + + { + args: []string{}, + expectedError: "requires exactly 1 argument", + }, + { + args: []string{"foo", "bar"}, + expectedError: "requires exactly 1 argument", + }, + { + args: []string{"foo"}, + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return nil, errors.Errorf("error getting tasks") + }, + expectedError: "error getting tasks", + }, + } + + for _, tc := range testCases { + cmd := newPsCommand(test.NewFakeCli(&fakeClient{ + taskListFunc: tc.taskListFunc, + }, &bytes.Buffer{})) + cmd.SetArgs(tc.args) + cmd.SetOutput(ioutil.Discard) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} + +func TestStackPsEmptyStack(t *testing.T) { + buf := new(bytes.Buffer) + cmd := newPsCommand(test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{}, nil + }, + }, buf)) + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + testutil.EqualNormalizedString(t, testutil.RemoveSpace, buf.String(), "Nothing found in stack: foo") +} + +func TestStackPsWithQuietOption(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{*Task(TaskID("id-foo"))}, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newPsCommand(cli) + cmd.SetArgs([]string{"foo"}) + cmd.Flags().Set("quiet", "true") + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-ps-with-quiet-option.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + +} + +func TestStackPsWithNoTruncOption(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newPsCommand(cli) + cmd.SetArgs([]string{"foo"}) + cmd.Flags().Set("no-trunc", "true") + cmd.Flags().Set("format", "{{ .ID }}") + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-ps-with-no-trunc-option.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackPsWithNoResolveOption(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{*Task( + TaskNodeID("id-node-foo"), + )}, nil + }, + nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) { + return *Node(NodeName("node-name-bar")), nil, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newPsCommand(cli) + cmd.SetArgs([]string{"foo"}) + cmd.Flags().Set("no-resolve", "true") + cmd.Flags().Set("format", "{{ .Node }}") + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-ps-with-no-resolve-option.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackPsWithFormat(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newPsCommand(cli) + cmd.SetArgs([]string{"foo"}) + cmd.Flags().Set("format", "{{ .Name }}") + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-ps-with-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackPsWithConfigFormat(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{ + TasksFormat: "{{ .Name }}", + }) + cmd := newPsCommand(cli) + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-ps-with-config-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackPsWithoutFormat(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return []swarm.Task{*Task( + TaskID("id-foo"), + TaskServiceID("service-id-foo"), + TaskNodeID("id-node"), + WithTaskSpec(TaskImage("myimage:mytag")), + TaskDesiredState(swarm.TaskStateReady), + WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))), + )}, nil + }, + nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) { + return *Node(NodeName("node-name-bar")), nil, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newPsCommand(cli) + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-ps-without-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} diff --git a/components/cli/cli/command/stack/remove_test.go b/components/cli/cli/command/stack/remove_test.go index 05092d84ac..498416ebe8 100644 --- a/components/cli/cli/command/stack/remove_test.go +++ b/components/cli/cli/command/stack/remove_test.go @@ -87,7 +87,7 @@ func TestSkipEmptyStack(t *testing.T) { assert.Equal(t, allConfigIDs, cli.removedConfigs) } -func TestContinueAfterError(t *testing.T) { +func TestRemoveContinueAfterError(t *testing.T) { allServices := []string{objectName("foo", "service1"), objectName("bar", "service1")} allServiceIDs := buildObjectIDs(allServices) diff --git a/components/cli/cli/command/stack/services.go b/components/cli/cli/command/stack/services.go index 459d7dd5a2..5b59c479c6 100644 --- a/components/cli/cli/command/stack/services.go +++ b/components/cli/cli/command/stack/services.go @@ -21,7 +21,7 @@ type servicesOptions struct { namespace string } -func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command { +func newServicesCommand(dockerCli command.Cli) *cobra.Command { options := servicesOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -41,7 +41,7 @@ func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runServices(dockerCli *command.DockerCli, options servicesOptions) error { +func runServices(dockerCli command.Cli, options servicesOptions) error { ctx := context.Background() client := dockerCli.Client() diff --git a/components/cli/cli/command/stack/services_test.go b/components/cli/cli/command/stack/services_test.go new file mode 100644 index 0000000000..a87174a14d --- /dev/null +++ b/components/cli/cli/command/stack/services_test.go @@ -0,0 +1,180 @@ +package stack + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/internal/test" + // Import builders to get the builder function as package function + . "github.com/docker/cli/cli/internal/test/builders" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/pkg/testutil" + "github.com/docker/docker/pkg/testutil/golden" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func TestStackServicesErrors(t *testing.T) { + testCases := []struct { + args []string + flags map[string]string + serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error) + nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error) + taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error) + expectedError string + }{ + { + args: []string{"foo"}, + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return nil, errors.Errorf("error getting services") + }, + expectedError: "error getting services", + }, + { + args: []string{"foo"}, + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{*Service()}, nil + }, + nodeListFunc: func(options types.NodeListOptions) ([]swarm.Node, error) { + return nil, errors.Errorf("error getting nodes") + }, + expectedError: "error getting nodes", + }, + { + args: []string{"foo"}, + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{*Service()}, nil + }, + taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { + return nil, errors.Errorf("error getting tasks") + }, + expectedError: "error getting tasks", + }, + { + args: []string{"foo"}, + flags: map[string]string{ + "format": "{{invalid format}}", + }, + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{*Service()}, nil + }, + expectedError: "Template parsing error", + }, + } + + for _, tc := range testCases { + cli := test.NewFakeCli(&fakeClient{ + serviceListFunc: tc.serviceListFunc, + nodeListFunc: tc.nodeListFunc, + taskListFunc: tc.taskListFunc, + }, &bytes.Buffer{}) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newServicesCommand(cli) + cmd.SetArgs(tc.args) + for key, value := range tc.flags { + cmd.Flags().Set(key, value) + } + cmd.SetOutput(ioutil.Discard) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} + +func TestStackServicesEmptyServiceList(t *testing.T) { + buf := new(bytes.Buffer) + cmd := newServicesCommand( + test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{}, nil + }, + }, buf), + ) + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + testutil.EqualNormalizedString(t, testutil.RemoveSpace, buf.String(), "Nothing found in stack: foo") +} + +func TestStackServicesWithQuietOption(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{*Service(ServiceID("id-foo"))}, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newServicesCommand(cli) + cmd.Flags().Set("quiet", "true") + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-services-with-quiet-option.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackServicesWithFormat(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + *Service(ServiceName("service-name-foo")), + }, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newServicesCommand(cli) + cmd.SetArgs([]string{"foo"}) + cmd.Flags().Set("format", "{{ .Name }}") + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-services-with-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackServicesWithConfigFormat(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + *Service(ServiceName("service-name-foo")), + }, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{ + ServicesFormat: "{{ .Name }}", + }) + cmd := newServicesCommand(cli) + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-services-with-config-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} + +func TestStackServicesWithoutFormat(t *testing.T) { + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{*Service( + ServiceName("name-foo"), + ServiceID("id-foo"), + ReplicatedService(2), + ServiceImage("busybox:latest"), + ServicePort(swarm.PortConfig{ + PublishMode: swarm.PortConfigPublishModeIngress, + PublishedPort: 0, + TargetPort: 3232, + Protocol: swarm.PortConfigProtocolTCP, + }), + )}, nil + }, + }, buf) + cli.SetConfigfile(&configfile.ConfigFile{}) + cmd := newServicesCommand(cli) + cmd.SetArgs([]string{"foo"}) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), "stack-services-without-format.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} diff --git a/components/cli/cli/command/stack/testdata/bundlefile_with_two_services.dab b/components/cli/cli/command/stack/testdata/bundlefile_with_two_services.dab new file mode 100644 index 0000000000..ced8180dc0 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/bundlefile_with_two_services.dab @@ -0,0 +1,29 @@ +{ + "Services": { + "visualizer": { + "Image": "busybox@sha256:32f093055929dbc23dec4d03e09dfe971f5973a9ca5cf059cbfb644c206aa83f", + "Networks": [ + "webnet" + ], + "Ports": [ + { + "Port": 8080, + "Protocol": "tcp" + } + ] + }, + "web": { + "Image": "busybox@sha256:32f093055929dbc23dec4d03e09dfe971f5973a9ca5cf059cbfb644c206aa83f", + "Networks": [ + "webnet" + ], + "Ports": [ + { + "Port": 80, + "Protocol": "tcp" + } + ] + } + }, + "Version": "0.1" +} diff --git a/components/cli/cli/command/stack/testdata/stack-list-sort.golden b/components/cli/cli/command/stack/testdata/stack-list-sort.golden new file mode 100644 index 0000000000..6bd116ebc6 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-list-sort.golden @@ -0,0 +1,3 @@ +NAME SERVICES +service-name-bar 1 +service-name-foo 1 diff --git a/components/cli/cli/command/stack/testdata/stack-list-with-format.golden b/components/cli/cli/command/stack/testdata/stack-list-with-format.golden new file mode 100644 index 0000000000..b53e6401f7 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-list-with-format.golden @@ -0,0 +1 @@ +service-name-foo diff --git a/components/cli/cli/command/stack/testdata/stack-list-without-format.golden b/components/cli/cli/command/stack/testdata/stack-list-without-format.golden new file mode 100644 index 0000000000..1b255654df --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-list-without-format.golden @@ -0,0 +1,2 @@ +NAME SERVICES +service-name-foo 1 diff --git a/components/cli/cli/command/stack/testdata/stack-ps-with-config-format.golden b/components/cli/cli/command/stack/testdata/stack-ps-with-config-format.golden new file mode 100644 index 0000000000..9ecebdafe3 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-ps-with-config-format.golden @@ -0,0 +1 @@ +service-id-foo.1 diff --git a/components/cli/cli/command/stack/testdata/stack-ps-with-format.golden b/components/cli/cli/command/stack/testdata/stack-ps-with-format.golden new file mode 100644 index 0000000000..9ecebdafe3 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-ps-with-format.golden @@ -0,0 +1 @@ +service-id-foo.1 diff --git a/components/cli/cli/command/stack/testdata/stack-ps-with-no-resolve-option.golden b/components/cli/cli/command/stack/testdata/stack-ps-with-no-resolve-option.golden new file mode 100644 index 0000000000..b90d743b8a --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-ps-with-no-resolve-option.golden @@ -0,0 +1 @@ +id-node-foo diff --git a/components/cli/cli/command/stack/testdata/stack-ps-with-no-trunc-option.golden b/components/cli/cli/command/stack/testdata/stack-ps-with-no-trunc-option.golden new file mode 100644 index 0000000000..8179bf4d65 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-ps-with-no-trunc-option.golden @@ -0,0 +1 @@ +xn4cypcov06f2w8gsbaf2lst3 diff --git a/components/cli/cli/command/stack/testdata/stack-ps-with-quiet-option.golden b/components/cli/cli/command/stack/testdata/stack-ps-with-quiet-option.golden new file mode 100644 index 0000000000..e2faeb6067 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-ps-with-quiet-option.golden @@ -0,0 +1 @@ +id-foo diff --git a/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden b/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden new file mode 100644 index 0000000000..9ca75f5890 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden @@ -0,0 +1,2 @@ +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +id-foo service-id-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago diff --git a/components/cli/cli/command/stack/testdata/stack-services-with-config-format.golden b/components/cli/cli/command/stack/testdata/stack-services-with-config-format.golden new file mode 100644 index 0000000000..b53e6401f7 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-services-with-config-format.golden @@ -0,0 +1 @@ +service-name-foo diff --git a/components/cli/cli/command/stack/testdata/stack-services-with-format.golden b/components/cli/cli/command/stack/testdata/stack-services-with-format.golden new file mode 100644 index 0000000000..b53e6401f7 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-services-with-format.golden @@ -0,0 +1 @@ +service-name-foo diff --git a/components/cli/cli/command/stack/testdata/stack-services-with-quiet-option.golden b/components/cli/cli/command/stack/testdata/stack-services-with-quiet-option.golden new file mode 100644 index 0000000000..e2faeb6067 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-services-with-quiet-option.golden @@ -0,0 +1 @@ +id-foo diff --git a/components/cli/cli/command/stack/testdata/stack-services-without-format.golden b/components/cli/cli/command/stack/testdata/stack-services-without-format.golden new file mode 100644 index 0000000000..c892250c20 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-services-without-format.golden @@ -0,0 +1,2 @@ +ID NAME MODE REPLICAS IMAGE PORTS +id-foo name-foo replicated 0/2 busybox:latest *:0->3232/tcp diff --git a/components/cli/cli/internal/test/builders/service.go b/components/cli/cli/internal/test/builders/service.go index dee91f4baa..71718268e1 100644 --- a/components/cli/cli/internal/test/builders/service.go +++ b/components/cli/cli/internal/test/builders/service.go @@ -14,6 +14,7 @@ func Service(builders ...func(*swarm.Service)) *swarm.Service { Annotations: swarm.Annotations{ Name: "defaultServiceName", }, + EndpointSpec: &swarm.EndpointSpec{}, }, } @@ -24,9 +25,44 @@ func Service(builders ...func(*swarm.Service)) *swarm.Service { return service } +// ServiceID sets the service ID +func ServiceID(ID string) func(*swarm.Service) { + return func(service *swarm.Service) { + service.ID = ID + } +} + // ServiceName sets the service name func ServiceName(name string) func(*swarm.Service) { return func(service *swarm.Service) { service.Spec.Annotations.Name = name } } + +// ServiceLabels sets the service's labels +func ServiceLabels(labels map[string]string) func(*swarm.Service) { + return func(service *swarm.Service) { + service.Spec.Annotations.Labels = labels + } +} + +// ReplicatedService sets the number of replicas for the service +func ReplicatedService(replicas uint64) func(*swarm.Service) { + return func(service *swarm.Service) { + service.Spec.Mode = swarm.ServiceMode{Replicated: &swarm.ReplicatedService{Replicas: &replicas}} + } +} + +// ServiceImage sets the service's image +func ServiceImage(image string) func(*swarm.Service) { + return func(service *swarm.Service) { + service.Spec.TaskTemplate = swarm.TaskSpec{ContainerSpec: swarm.ContainerSpec{Image: image}} + } +} + +// ServicePort sets the service's port +func ServicePort(port swarm.PortConfig) func(*swarm.Service) { + return func(service *swarm.Service) { + service.Spec.EndpointSpec.Ports = append(service.Spec.EndpointSpec.Ports, port) + } +} diff --git a/components/cli/cli/internal/test/builders/task.go b/components/cli/cli/internal/test/builders/task.go index 688c62a3a8..b4551c983b 100644 --- a/components/cli/cli/internal/test/builders/task.go +++ b/components/cli/cli/internal/test/builders/task.go @@ -42,13 +42,34 @@ func TaskID(id string) func(*swarm.Task) { } } -// ServiceID sets the task service's ID -func ServiceID(id string) func(*swarm.Task) { +// TaskName sets the task name +func TaskName(name string) func(*swarm.Task) { + return func(task *swarm.Task) { + task.Annotations.Name = name + } +} + +// TaskServiceID sets the task service's ID +func TaskServiceID(id string) func(*swarm.Task) { return func(task *swarm.Task) { task.ServiceID = id } } +// TaskNodeID sets the task's node id +func TaskNodeID(id string) func(*swarm.Task) { + return func(task *swarm.Task) { + task.NodeID = id + } +} + +// TaskDesiredState sets the task's desired state +func TaskDesiredState(state swarm.TaskState) func(*swarm.Task) { + return func(task *swarm.Task) { + task.DesiredState = state + } +} + // WithStatus sets the task status func WithStatus(statusBuilders ...func(*swarm.TaskStatus)) func(*swarm.Task) { return func(task *swarm.Task) { @@ -86,6 +107,13 @@ func StatusErr(err string) func(*swarm.TaskStatus) { } } +// TaskState sets the task's current state +func TaskState(state swarm.TaskState) func(*swarm.TaskStatus) { + return func(taskStatus *swarm.TaskStatus) { + taskStatus.State = state + } +} + // PortStatus sets the tasks port config status // FIXME(vdemeester) should be a sub builder 👼 func PortStatus(portConfigs []swarm.PortConfig) func(*swarm.TaskStatus) { @@ -94,6 +122,13 @@ func PortStatus(portConfigs []swarm.PortConfig) func(*swarm.TaskStatus) { } } +// WithTaskSpec sets the task spec +func WithTaskSpec(specBuilders ...func(*swarm.TaskSpec)) func(*swarm.Task) { + return func(task *swarm.Task) { + task.Spec = *TaskSpec(specBuilders...) + } +} + // TaskSpec creates a task spec with default values . // Any number of taskSpec function builder can be pass to augment it. func TaskSpec(specBuilders ...func(*swarm.TaskSpec)) *swarm.TaskSpec { @@ -109,3 +144,10 @@ func TaskSpec(specBuilders ...func(*swarm.TaskSpec)) *swarm.TaskSpec { return taskSpec } + +// TaskImage sets the task's image +func TaskImage(image string) func(*swarm.TaskSpec) { + return func(taskSpec *swarm.TaskSpec) { + taskSpec.ContainerSpec.Image = image + } +} From 8f64a8f93c6fb775574749e8a26b6858849a8bfc Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 20 Jun 2017 15:15:08 -0700 Subject: [PATCH 58/67] Fix Markdown formatting of experimental "stacks" Signed-off-by: Sebastiaan van Stijn Upstream-commit: c5301ca35dd98835bbc4bcec840526373a3a2ea2 Component: cli --- components/cli/experimental/docker-stacks-and-bundles.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/cli/experimental/docker-stacks-and-bundles.md b/components/cli/experimental/docker-stacks-and-bundles.md index 8abfd76359..1271af67e0 100644 --- a/components/cli/experimental/docker-stacks-and-bundles.md +++ b/components/cli/experimental/docker-stacks-and-bundles.md @@ -161,21 +161,18 @@ A service has the following fields: only specify the container port to be exposed. These ports can be mapped on runtime hosts at the operator's discretion. -
WorkingDir string
Working directory inside the service containers.
-
User string
Username or UID (format: <name|uid>[:<group|gid>]).
-
Networks []string
From edf13621d4a570f2fd04e136e4c761b59d2ab532 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 21 Jun 2017 00:11:59 -0400 Subject: [PATCH 59/67] Fix lint errors merged while new lint branch was in PR. Signed-off-by: Daniel Nephin Upstream-commit: b84e21cd05e77d12f2cc6ae1a72b852f9d58ed59 Component: cli --- components/cli/cli/command/service/opts.go | 6 +++++- components/cli/cli/command/service/update.go | 5 ++--- components/cli/cli/config/configfile/file.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/components/cli/cli/command/service/opts.go b/components/cli/cli/command/service/opts.go index 5d1595b055..4c1425c42c 100644 --- a/components/cli/cli/command/service/opts.go +++ b/components/cli/cli/command/service/opts.go @@ -354,7 +354,11 @@ func convertNetworks(ctx context.Context, apiClient client.NetworkAPIClient, net if err != nil { return nil, err } - netAttach = append(netAttach, swarm.NetworkAttachmentConfig(net)) + netAttach = append(netAttach, swarm.NetworkAttachmentConfig{ // nolint: gosimple + Target: net.Target, + Aliases: net.Aliases, + DriverOpts: net.DriverOpts, + }) } sort.Sort(byNetworkTarget(netAttach)) return netAttach, nil diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index 4b49b738ee..a80bf01ef1 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -503,9 +503,8 @@ func updatePlacementPreferences(flags *pflag.FlagSet, placement *swarm.Placement } if flags.Changed(flagPlacementPrefAdd) { - for _, addition := range flags.Lookup(flagPlacementPrefAdd).Value.(*placementPrefOpts).prefs { - newPrefs = append(newPrefs, addition) - } + newPrefs = append(newPrefs, + flags.Lookup(flagPlacementPrefAdd).Value.(*placementPrefOpts).prefs...) } placement.Preferences = newPrefs diff --git a/components/cli/cli/config/configfile/file.go b/components/cli/cli/config/configfile/file.go index 78024acf54..9c2c4eec6d 100644 --- a/components/cli/cli/config/configfile/file.go +++ b/components/cli/cli/config/configfile/file.go @@ -173,7 +173,7 @@ func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts []string) ma cfgKey = host } - config, _ := configFile.Proxies[cfgKey] + config := configFile.Proxies[cfgKey] permitted := map[string]*string{ "HTTP_PROXY": &config.HTTPProxy, "HTTPS_PROXY": &config.HTTPSProxy, From 0e797a650fc14d9e661926a8646a0100767df13f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 20 Jun 2017 21:23:21 -0700 Subject: [PATCH 60/67] Explain `stack deploy` with multiple Compose files The Docker Compose docs suggest using a separate override configuration file for production-specific settings, but it is not obvious how to feed this to `docker stack deploy`, which only supports a single Compose file as input. Thus, we now describe how to do this by merging the configuration files with `docker-compose config`. Signed-off-by: Denis Washington Signed-off-by: Sebastiaan van Stijn Upstream-commit: 36fa4af30b5d446a56fdbbc3c3280ed9743fad0b Component: cli --- .../reference/commandline/stack_deploy.md | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/components/cli/docs/reference/commandline/stack_deploy.md b/components/cli/docs/reference/commandline/stack_deploy.md index d57ef0f76c..7f3f7f4c5b 100644 --- a/components/cli/docs/reference/commandline/stack_deploy.md +++ b/components/cli/docs/reference/commandline/stack_deploy.md @@ -40,7 +40,7 @@ has to be run targeting a manager node. ### Compose file -The `deploy` command supports compose file version `3.0` and above." +The `deploy` command supports compose file version `3.0` and above. ```bash $ docker stack deploy --compose-file docker-compose.yml vossibility @@ -57,7 +57,28 @@ Creating service vossibility_ghollector Creating service vossibility_lookupd ``` -You can verify that the services were correctly created +Only a single Compose file is accepted. If your configuration is split between +multiple Compose files, e.g. a base configuration and environment-specific overrides, +you can combine these by passing them to `docker-compose config` with the `-f` option +and redirecting the merged output into a new file. + +```bash +$ docker-compose -f docker-compose.yml -f docker-compose.prod.yml config > docker-stack.yml +$ docker stack deploy --compose-file docker-stack.yml vossibility + +Ignoring unsupported options: links + +Creating network vossibility_vossibility +Creating network vossibility_default +Creating service vossibility_nsqd +Creating service vossibility_logstash +Creating service vossibility_elasticsearch +Creating service vossibility_kibana +Creating service vossibility_ghollector +Creating service vossibility_lookupd +``` + +You can verify that the services were correctly created: ```bash $ docker service ls From 8903bd3d5783a708c17cd878a4b0bd090de62b0c Mon Sep 17 00:00:00 2001 From: Boaz Shuster Date: Sun, 18 Jun 2017 18:48:10 +0300 Subject: [PATCH 61/67] Unmarshal a number as a Number in RawInspectFallback Running `docker inspect --format "{{.ID}} {{.Size}}" alpine` prints sha256:651aa95985aa4a17a38ffcf71f598ec461924ca96865facc2c5782ef2d2be07f 3983636 While `docker inspect --format "{{.Id}} {{.Size}}" alpine` prints sha256:651aa95985aa4a17a38ffcf71f598ec461924ca96865facc2c5782ef2d2be07f 3.983636e+06 This happens because "Id" is not a field of types.ImageInspect and thus tryRawInspectFallback is called and converts the raw response into `interface{}` using a JSON decoder. However, by default that decoder converts numbers into `float64` unless `UseNumber` is set. Signed-off-by: Boaz Shuster Upstream-commit: 9a2f2d769d676224d07a90d8fdf82e088d74aa04 Component: cli --- .../cli/cli/command/inspect/inspector.go | 1 + .../cli/cli/command/inspect/inspector_test.go | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/components/cli/cli/command/inspect/inspector.go b/components/cli/cli/command/inspect/inspector.go index 62a24ff369..054381f50f 100644 --- a/components/cli/cli/command/inspect/inspector.go +++ b/components/cli/cli/command/inspect/inspector.go @@ -110,6 +110,7 @@ func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error { buffer := new(bytes.Buffer) rdr := bytes.NewReader(rawElement) dec := json.NewDecoder(rdr) + dec.UseNumber() if rawErr := dec.Decode(&raw); rawErr != nil { return errors.Errorf("unable to read inspect data: %v", rawErr) diff --git a/components/cli/cli/command/inspect/inspector_test.go b/components/cli/cli/command/inspect/inspector_test.go index 9085230ac5..721bcf84c5 100644 --- a/components/cli/cli/command/inspect/inspector_test.go +++ b/components/cli/cli/command/inspect/inspector_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/docker/docker/pkg/templates" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type testElement struct { @@ -219,3 +221,39 @@ func TestIndentedInspectorRawElements(t *testing.T) { t.Fatalf("Expected `%s`, got `%s`", expected, b.String()) } } + +// moby/moby#32235 +// This test verifies that even if `tryRawInspectFallback` is called the fields containing +// numerical values are displayed correctly. +// For example, `docker inspect --format "{{.Id}} {{.Size}} alpine` and +// `docker inspect --format "{{.ID}} {{.Size}} alpine" will have the same output which is +// sha256:651aa95985aa4a17a38ffcf71f598ec461924ca96865facc2c5782ef2d2be07f 3983636 +func TestTemplateInspectorRawFallbackNumber(t *testing.T) { + // Using typedElem to automatically fall to tryRawInspectFallback. + typedElem := struct { + ID string `json:"Id"` + }{"ad3"} + testcases := []struct { + raw []byte + exp string + }{ + {raw: []byte(`{"Id": "ad3", "Size": 53317}`), exp: "53317 ad3\n"}, + {raw: []byte(`{"Id": "ad3", "Size": 53317.102}`), exp: "53317.102 ad3\n"}, + {raw: []byte(`{"Id": "ad3", "Size": 53317.0}`), exp: "53317.0 ad3\n"}, + } + b := new(bytes.Buffer) + tmpl, err := templates.Parse("{{.Size}} {{.Id}}") + require.NoError(t, err) + + i := NewTemplateInspector(b, tmpl) + for _, tc := range testcases { + err = i.Inspect(typedElem, tc.raw) + require.NoError(t, err) + + err = i.Flush() + require.NoError(t, err) + + assert.Equal(t, tc.exp, b.String()) + b.Reset() + } +} From 49c6d18b806f787bac54e4503357f56454e9d97d Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Sat, 3 Jun 2017 22:59:09 -0400 Subject: [PATCH 62/67] Some improvements to compose volume spec parsing Signed-off-by: Daniel Nephin Upstream-commit: db6ff357a7578532f5d7eefc7a4b4e7d08617c79 Component: cli --- components/cli/cli/compose/loader/volume.go | 21 ++++++++----------- .../cli/cli/compose/loader/volume_test.go | 4 ++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/components/cli/cli/compose/loader/volume.go b/components/cli/cli/compose/loader/volume.go index 865d78ff7f..f39f27292b 100644 --- a/components/cli/cli/compose/loader/volume.go +++ b/components/cli/cli/compose/loader/volume.go @@ -10,6 +10,8 @@ import ( "github.com/pkg/errors" ) +const endOfSpec = rune(0) + func parseVolume(spec string) (types.ServiceVolumeConfig, error) { volume := types.ServiceVolumeConfig{} @@ -23,11 +25,11 @@ func parseVolume(spec string) (types.ServiceVolumeConfig, error) { } buffer := []rune{} - for _, char := range spec { + for _, char := range spec + string(endOfSpec) { switch { - case isWindowsDrive(char, buffer, volume): + case isWindowsDrive(buffer, char): buffer = append(buffer, char) - case char == ':': + case char == ':' || char == endOfSpec: if err := populateFieldFromBuffer(char, buffer, &volume); err != nil { return volume, errors.Wrapf(err, "invalid spec: %s", spec) } @@ -36,15 +38,11 @@ func parseVolume(spec string) (types.ServiceVolumeConfig, error) { buffer = append(buffer, char) } } - - if err := populateFieldFromBuffer(rune(0), buffer, &volume); err != nil { - return volume, errors.Wrapf(err, "invalid spec: %s", spec) - } populateType(&volume) return volume, nil } -func isWindowsDrive(char rune, buffer []rune, volume types.ServiceVolumeConfig) bool { +func isWindowsDrive(buffer []rune, char rune) bool { return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0]) } @@ -54,7 +52,7 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu case len(buffer) == 0: return errors.New("empty section between colons") // Anonymous volume - case volume.Source == "" && char == rune(0): + case volume.Source == "" && char == endOfSpec: volume.Target = strBuffer return nil case volume.Source == "": @@ -112,7 +110,6 @@ func isFilePath(source string) bool { return true } - // Windows absolute path - first, next := utf8.DecodeRuneInString(source) - return unicode.IsLetter(first) && source[next] == ':' + first, nextIndex := utf8.DecodeRuneInString(source) + return isWindowsDrive([]rune{first}, rune(source[nextIndex])) } diff --git a/components/cli/cli/compose/loader/volume_test.go b/components/cli/cli/compose/loader/volume_test.go index 79f1667731..d2d1afe9ea 100644 --- a/components/cli/cli/compose/loader/volume_test.go +++ b/components/cli/cli/compose/loader/volume_test.go @@ -147,3 +147,7 @@ func TestParseVolumeWithRW(t *testing.T) { assert.Equal(t, expected, volume) } } + +func TestIsFilePath(t *testing.T) { + assert.False(t, isFilePath("a界")) +} From 2b68a9d0680a6e99148e039fceff37f7fe46fe7b Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 9 May 2017 19:21:17 -0400 Subject: [PATCH 63/67] Use compose volume spec parser for container volume flag Restore testcases for Volume spec parsing. And correctly interpret the parsed volume. Signed-off-by: Daniel Nephin Upstream-commit: 732261f774f724a44f79c3138459cf190cb01558 Component: cli --- components/cli/cli/command/container/opts.go | 65 +------------ .../cli/cli/command/container/opts_test.go | 96 ++++--------------- components/cli/cli/compose/loader/loader.go | 2 +- components/cli/cli/compose/loader/volume.go | 8 +- .../cli/cli/compose/loader/volume_test.go | 77 ++++++++++++--- 5 files changed, 92 insertions(+), 156 deletions(-) diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go index 82d7426c75..cf1f931b29 100644 --- a/components/cli/cli/command/container/opts.go +++ b/components/cli/cli/command/container/opts.go @@ -12,6 +12,7 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" networktypes "github.com/docker/docker/api/types/network" @@ -332,7 +333,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err volumes := copts.volumes.GetMap() // add any bind targets to the list of container volumes for bind := range copts.volumes.GetMap() { - if arr := volumeSplitN(bind, 2); len(arr) > 1 { + parsed, _ := loader.ParseVolume(bind) + if parsed.Source != "" { // after creating the bind mount we want to delete it from the copts.volumes values because // we do not want bind mounts being committed to image configs binds = append(binds, bind) @@ -827,67 +829,6 @@ func validatePath(val string, validator func(string) bool) (string, error) { return val, nil } -// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon. -// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped). -// In Windows driver letter appears in two situations: -// a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option) -// b. A string in the format like `\\?\C:\Windows\...` (UNC). -// Therefore, a driver letter can only follow either a `:` or `\\` -// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`. -func volumeSplitN(raw string, n int) []string { - var array []string - if len(raw) == 0 || raw[0] == ':' { - // invalid - return nil - } - // numberOfParts counts the number of parts separated by a separator colon - numberOfParts := 0 - // left represents the left-most cursor in raw, updated at every `:` character considered as a separator. - left := 0 - // right represents the right-most cursor in raw incremented with the loop. Note this - // starts at index 1 as index 0 is already handle above as a special case. - for right := 1; right < len(raw); right++ { - // stop parsing if reached maximum number of parts - if n >= 0 && numberOfParts >= n { - break - } - if raw[right] != ':' { - continue - } - potentialDriveLetter := raw[right-1] - if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') { - if right > 1 { - beforePotentialDriveLetter := raw[right-2] - // Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`) - if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' { - // e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`. - array = append(array, raw[left:right]) - left = right + 1 - numberOfParts++ - } - // else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing. - } - // if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing. - } else { - // if `:` is not preceded by a potential drive letter, then consider it as a delimiter. - array = append(array, raw[left:right]) - left = right + 1 - numberOfParts++ - } - } - // need to take care of the last part - if left < len(raw) { - if n >= 0 && numberOfParts >= n { - // if the maximum number of parts is reached, just append the rest to the last part - // left-1 is at the last `:` that needs to be included since not considered a separator. - array[n-1] += raw[left-1:] - } else { - array = append(array, raw[left:]) - } - } - return array -} - // validateAttach validates that the specified string is a valid attach option. func validateAttach(val string) (string, error) { s := strings.ToLower(val) diff --git a/components/cli/cli/command/container/opts_test.go b/components/cli/cli/command/container/opts_test.go index 6001cde2c0..25e5b04fc8 100644 --- a/components/cli/cli/command/container/opts_test.go +++ b/components/cli/cli/command/container/opts_test.go @@ -19,6 +19,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestValidateAttach(t *testing.T) { @@ -366,51 +367,46 @@ func TestParseDevice(t *testing.T) { func TestParseModes(t *testing.T) { // ipc ko - if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" { - t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err) - } + _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}) + testutil.ErrorContains(t, err, "--ipc: invalid IPC mode") + // ipc ok _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !hostconfig.IpcMode.Valid() { t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode) } + // pid ko - if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" { - t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err) - } + _, _, _, err = parseRun([]string{"--pid=container:", "img", "cmd"}) + testutil.ErrorContains(t, err, "--pid: invalid PID mode") + // pid ok _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !hostconfig.PidMode.Valid() { t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode) } + // uts ko - if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" { - t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err) - } + _, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"}) + testutil.ErrorContains(t, err, "--uts: invalid UTS mode") + // uts ok _, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !hostconfig.UTSMode.Valid() { t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode) } + // shm-size ko expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'` - if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != expectedErr { - t.Fatalf("Expected an error with message '%v', got %v", expectedErr, err) - } + _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}) + testutil.ErrorContains(t, err, expectedErr) + // shm-size ok _, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if hostconfig.ShmSize != 134217728 { t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize) } @@ -754,58 +750,6 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con return c, h, err } -func TestVolumeSplitN(t *testing.T) { - for _, x := range []struct { - input string - n int - expected []string - }{ - {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}}, - {`:C:\foo:d:`, -1, nil}, - {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}}, - {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}}, - {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}}, - - {`d:\`, -1, []string{`d:\`}}, - {`d:`, -1, []string{`d:`}}, - {`d:\path`, -1, []string{`d:\path`}}, - {`d:\path with space`, -1, []string{`d:\path with space`}}, - {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}}, - {`c:\:d:\`, -1, []string{`c:\`, `d:\`}}, - {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}}, - {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}}, - {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}}, - {`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}}, - {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}}, - {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}}, - {`name:D:`, -1, []string{`name`, `D:`}}, - {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}}, - {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}}, - {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}}, - {`c:\Windows`, -1, []string{`c:\Windows`}}, - {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}}, - - {``, -1, nil}, - {`.`, -1, []string{`.`}}, - {`..\`, -1, []string{`..\`}}, - {`c:\:..\`, -1, []string{`c:\`, `..\`}}, - {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}}, - - // Cover directories with one-character name - {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}}, - } { - res := volumeSplitN(x.input, x.n) - if len(res) < len(x.expected) { - t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) - } - for i, e := range res { - if e != x.expected[i] { - t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res) - } - } - } -} - func TestValidateDevice(t *testing.T) { valid := []string{ "/home", diff --git a/components/cli/cli/compose/loader/loader.go b/components/cli/cli/compose/loader/loader.go index b9d601664c..b456e1824d 100644 --- a/components/cli/cli/compose/loader/loader.go +++ b/components/cli/cli/compose/loader/loader.go @@ -564,7 +564,7 @@ func transformStringSourceMap(data interface{}) (interface{}, error) { func transformServiceVolumeConfig(data interface{}) (interface{}, error) { switch value := data.(type) { case string: - return parseVolume(value) + return ParseVolume(value) case map[string]interface{}: return data, nil default: diff --git a/components/cli/cli/compose/loader/volume.go b/components/cli/cli/compose/loader/volume.go index f39f27292b..b026cf150f 100644 --- a/components/cli/cli/compose/loader/volume.go +++ b/components/cli/cli/compose/loader/volume.go @@ -12,7 +12,8 @@ import ( const endOfSpec = rune(0) -func parseVolume(spec string) (types.ServiceVolumeConfig, error) { +// ParseVolume parses a volume spec without any knowledge of the target platform +func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { volume := types.ServiceVolumeConfig{} switch len(spec) { @@ -31,6 +32,7 @@ func parseVolume(spec string) (types.ServiceVolumeConfig, error) { buffer = append(buffer, char) case char == ':' || char == endOfSpec: if err := populateFieldFromBuffer(char, buffer, &volume); err != nil { + populateType(&volume) return volume, errors.Wrapf(err, "invalid spec: %s", spec) } buffer = []rune{} @@ -38,6 +40,7 @@ func parseVolume(spec string) (types.ServiceVolumeConfig, error) { buffer = append(buffer, char) } } + populateType(&volume) return volume, nil } @@ -75,9 +78,8 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu default: if isBindOption(option) { volume.Bind = &types.ServiceVolumeBind{Propagation: option} - } else { - return errors.Errorf("unknown option: %s", option) } + // ignore unknown options } } return nil diff --git a/components/cli/cli/compose/loader/volume_test.go b/components/cli/cli/compose/loader/volume_test.go index d2d1afe9ea..f1b90fe8ea 100644 --- a/components/cli/cli/compose/loader/volume_test.go +++ b/components/cli/cli/compose/loader/volume_test.go @@ -1,6 +1,7 @@ package loader import ( + "fmt" "testing" "github.com/docker/cli/cli/compose/types" @@ -10,7 +11,7 @@ import ( func TestParseVolumeAnonymousVolume(t *testing.T) { for _, path := range []string{"/path", "/path/foo"} { - volume, err := parseVolume(path) + volume, err := ParseVolume(path) expected := types.ServiceVolumeConfig{Type: "volume", Target: path} assert.NoError(t, err) assert.Equal(t, expected, volume) @@ -19,7 +20,7 @@ func TestParseVolumeAnonymousVolume(t *testing.T) { func TestParseVolumeAnonymousVolumeWindows(t *testing.T) { for _, path := range []string{"C:\\path", "Z:\\path\\foo"} { - volume, err := parseVolume(path) + volume, err := ParseVolume(path) expected := types.ServiceVolumeConfig{Type: "volume", Target: path} assert.NoError(t, err) assert.Equal(t, expected, volume) @@ -27,13 +28,13 @@ func TestParseVolumeAnonymousVolumeWindows(t *testing.T) { } func TestParseVolumeTooManyColons(t *testing.T) { - _, err := parseVolume("/foo:/foo:ro:foo") + _, err := ParseVolume("/foo:/foo:ro:foo") assert.EqualError(t, err, "invalid spec: /foo:/foo:ro:foo: too many colons") } func TestParseVolumeShortVolumes(t *testing.T) { for _, path := range []string{".", "/a"} { - volume, err := parseVolume(path) + volume, err := ParseVolume(path) expected := types.ServiceVolumeConfig{Type: "volume", Target: path} assert.NoError(t, err) assert.Equal(t, expected, volume) @@ -42,14 +43,14 @@ func TestParseVolumeShortVolumes(t *testing.T) { func TestParseVolumeMissingSource(t *testing.T) { for _, spec := range []string{":foo", "/foo::ro"} { - _, err := parseVolume(spec) + _, err := ParseVolume(spec) testutil.ErrorContains(t, err, "empty section between colons") } } func TestParseVolumeBindMount(t *testing.T) { for _, path := range []string{"./foo", "~/thing", "../other", "/foo", "/home/user"} { - volume, err := parseVolume(path + ":/target") + volume, err := ParseVolume(path + ":/target") expected := types.ServiceVolumeConfig{ Type: "bind", Source: path, @@ -67,7 +68,7 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) { "../other", "D:\\path", "/home/user", } { - volume, err := parseVolume(path + ":d:\\target") + volume, err := ParseVolume(path + ":d:\\target") expected := types.ServiceVolumeConfig{ Type: "bind", Source: path, @@ -79,7 +80,7 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) { } func TestParseVolumeWithBindOptions(t *testing.T) { - volume, err := parseVolume("/source:/target:slave") + volume, err := ParseVolume("/source:/target:slave") expected := types.ServiceVolumeConfig{ Type: "bind", Source: "/source", @@ -91,7 +92,7 @@ func TestParseVolumeWithBindOptions(t *testing.T) { } func TestParseVolumeWithBindOptionsWindows(t *testing.T) { - volume, err := parseVolume("C:\\source\\foo:D:\\target:ro,rprivate") + volume, err := ParseVolume("C:\\source\\foo:D:\\target:ro,rprivate") expected := types.ServiceVolumeConfig{ Type: "bind", Source: "C:\\source\\foo", @@ -104,12 +105,12 @@ func TestParseVolumeWithBindOptionsWindows(t *testing.T) { } func TestParseVolumeWithInvalidVolumeOptions(t *testing.T) { - _, err := parseVolume("name:/target:bogus") - assert.EqualError(t, err, "invalid spec: name:/target:bogus: unknown option: bogus") + _, err := ParseVolume("name:/target:bogus") + assert.NoError(t, err) } func TestParseVolumeWithVolumeOptions(t *testing.T) { - volume, err := parseVolume("name:/target:nocopy") + volume, err := ParseVolume("name:/target:nocopy") expected := types.ServiceVolumeConfig{ Type: "volume", Source: "name", @@ -122,7 +123,7 @@ func TestParseVolumeWithVolumeOptions(t *testing.T) { func TestParseVolumeWithReadOnly(t *testing.T) { for _, path := range []string{"./foo", "/home/user"} { - volume, err := parseVolume(path + ":/target:ro") + volume, err := ParseVolume(path + ":/target:ro") expected := types.ServiceVolumeConfig{ Type: "bind", Source: path, @@ -136,7 +137,7 @@ func TestParseVolumeWithReadOnly(t *testing.T) { func TestParseVolumeWithRW(t *testing.T) { for _, path := range []string{"./foo", "/home/user"} { - volume, err := parseVolume(path + ":/target:rw") + volume, err := ParseVolume(path + ":/target:rw") expected := types.ServiceVolumeConfig{ Type: "bind", Source: path, @@ -151,3 +152,51 @@ func TestParseVolumeWithRW(t *testing.T) { func TestIsFilePath(t *testing.T) { assert.False(t, isFilePath("a界")) } + +// Preserve the test cases for VolumeSplitN +func TestParseVolumeSplitCases(t *testing.T) { + for casenumber, x := range []struct { + input string + n int + expected []string + }{ + {`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}}, + {`:C:\foo:d:`, -1, nil}, + {`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}}, + {`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}}, + {`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}}, + {`d:\`, -1, []string{`d:\`}}, + {`d:`, -1, []string{`d:`}}, + {`d:\path`, -1, []string{`d:\path`}}, + {`d:\path with space`, -1, []string{`d:\path with space`}}, + {`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}}, + + {`c:\:d:\`, -1, []string{`c:\`, `d:\`}}, + {`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}}, + {`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}}, + {`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}}, + {`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}}, + {`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}}, + {`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}}, + {`name:D:`, -1, []string{`name`, `D:`}}, + {`name:D::rW`, -1, []string{`name`, `D:`, `rW`}}, + {`name:D::RW`, -1, []string{`name`, `D:`, `RW`}}, + + {`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}}, + {`c:\Windows`, -1, []string{`c:\Windows`}}, + {`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}}, + {``, -1, nil}, + {`.`, -1, []string{`.`}}, + {`..\`, -1, []string{`..\`}}, + {`c:\:..\`, -1, []string{`c:\`, `..\`}}, + {`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}}, + // Cover directories with one-character name + {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}}, + } { + parsed, _ := ParseVolume(x.input) + + expected := len(x.expected) > 1 + msg := fmt.Sprintf("Case %d: %s", casenumber, x.input) + assert.Equal(t, expected, parsed.Source != "", msg) + } +} From 08cdfb1fb9d464872b0875e92684762c88ed5e59 Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Wed, 21 Jun 2017 11:01:54 -0700 Subject: [PATCH 64/67] Fix small typo Signed-off-by: Misty Stanley-Jones Upstream-commit: ede69a38c2cbe7d86a5a87f8e0c4738c37613160 Component: cli --- components/cli/docs/extend/plugins_authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docs/extend/plugins_authorization.md b/components/cli/docs/extend/plugins_authorization.md index ac1837f754..5b49d657ef 100644 --- a/components/cli/docs/extend/plugins_authorization.md +++ b/components/cli/docs/extend/plugins_authorization.md @@ -27,7 +27,7 @@ same is true for callers using Docker's Engine API to contact the daemon. If you require greater access control, you can create authorization plugins and add them to your Docker daemon configuration. Using an authorization plugin, a Docker administrator can configure granular access policies for managing access -to Docker daemon. +to the Docker daemon. Anyone with the appropriate skills can develop an authorization plugin. These skills, at their most basic, are knowledge of Docker, understanding of REST, and From 9fb14cdd144a2e20da8787f9d45d328758d04c6f Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Wed, 21 Jun 2017 17:47:40 -0700 Subject: [PATCH 65/67] Fix mounts for directories with weird chars Fixes: `make -f docker.Makefile binary` When directories have characters like `&&` they must be wrapped in quotes or else the docker run command will fail. Signed-off-by: Eli Uriegas Upstream-commit: 1119e992f26660c6033e6da0d2c165cb59098bce Component: cli --- components/cli/docker.Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker.Makefile b/components/cli/docker.Makefile index 1e5319bb23..ef8dbfb926 100644 --- a/components/cli/docker.Makefile +++ b/components/cli/docker.Makefile @@ -7,7 +7,7 @@ DEV_DOCKER_IMAGE_NAME = docker-cli-dev LINTER_IMAGE_NAME = docker-cli-lint CROSS_IMAGE_NAME = docker-cli-cross -MOUNTS = -v `pwd`:/go/src/github.com/docker/cli +MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli VERSION = $(shell cat VERSION) ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT From 797b9c7a2d0f241a6e8a0fa88eb0770b2430c629 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 22 Jun 2017 19:49:04 -0400 Subject: [PATCH 66/67] Use an image with git and ssh for circleCI Signed-off-by: Daniel Nephin Upstream-commit: 74c1388f66fee54050d7b7149fdda4c652c6bbd8 Component: cli --- components/cli/circle.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/cli/circle.yml b/components/cli/circle.yml index 9e271b0a8c..2035a25f54 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -4,7 +4,7 @@ jobs: lint: working_directory: /work - docker: [{image: 'docker:17.05'}] + docker: [{image: 'docker:17.05-git'}] steps: - checkout - setup_remote_docker @@ -20,7 +20,7 @@ jobs: cross: working_directory: /work - docker: [{image: 'docker:17.05'}] + docker: [{image: 'docker:17.05-git'}] steps: - checkout - setup_remote_docker @@ -37,7 +37,7 @@ jobs: test: working_directory: /work - docker: [{image: 'docker:17.05'}] + docker: [{image: 'docker:17.05-git'}] steps: - checkout - setup_remote_docker @@ -58,7 +58,7 @@ jobs: validate: working_directory: /work - docker: [{image: 'docker:17.05'}] + docker: [{image: 'docker:17.05-git'}] steps: - checkout - setup_remote_docker From a9a5f68c49b2cdeb72509fb7b5d6fb7a96a15722 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 23 Jun 2017 16:24:43 +0200 Subject: [PATCH 67/67] Import DisplayablePorts from moby/moby source This has nothing to do in the `api` package for moby as it's just a cli display function. Signed-off-by: Vincent Demeester Upstream-commit: 9e142cadc974cf16da071e538a17de05164d42f5 Component: cli --- .../cli/cli/command/formatter/container.go | 92 ++++++- .../cli/command/formatter/container_test.go | 245 ++++++++++++++++++ 2 files changed, 334 insertions(+), 3 deletions(-) diff --git a/components/cli/cli/command/formatter/container.go b/components/cli/cli/command/formatter/container.go index 8ffb1a69b2..5ee1309d28 100644 --- a/components/cli/cli/command/formatter/container.go +++ b/components/cli/cli/command/formatter/container.go @@ -2,16 +2,16 @@ package formatter import ( "fmt" + "sort" "strconv" "strings" "time" "github.com/docker/distribution/reference" - "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringutils" - units "github.com/docker/go-units" + "github.com/docker/go-units" ) const ( @@ -180,7 +180,7 @@ func (c *containerContext) RunningFor() string { } func (c *containerContext) Ports() string { - return api.DisplayablePorts(c.c.Ports) + return DisplayablePorts(c.c.Ports) } func (c *containerContext) Status() string { @@ -257,3 +257,89 @@ func (c *containerContext) Networks() string { return strings.Join(networks, ",") } + +// DisplayablePorts returns formatted string representing open ports of container +// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp" +// it's used by command 'docker ps' +func DisplayablePorts(ports []types.Port) string { + type portGroup struct { + first uint16 + last uint16 + } + groupMap := make(map[string]*portGroup) + var result []string + var hostMappings []string + var groupMapKeys []string + sort.Sort(byPortInfo(ports)) + for _, port := range ports { + current := port.PrivatePort + portKey := port.Type + if port.IP != "" { + if port.PublicPort != current { + hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)) + continue + } + portKey = fmt.Sprintf("%s/%s", port.IP, port.Type) + } + group := groupMap[portKey] + + if group == nil { + groupMap[portKey] = &portGroup{first: current, last: current} + // record order that groupMap keys are created + groupMapKeys = append(groupMapKeys, portKey) + continue + } + if current == (group.last + 1) { + group.last = current + continue + } + + result = append(result, formGroup(portKey, group.first, group.last)) + groupMap[portKey] = &portGroup{first: current, last: current} + } + for _, portKey := range groupMapKeys { + g := groupMap[portKey] + result = append(result, formGroup(portKey, g.first, g.last)) + } + result = append(result, hostMappings...) + return strings.Join(result, ", ") +} + +func formGroup(key string, start, last uint16) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} + +// byPortInfo is a temporary type used to sort types.Port by its fields +type byPortInfo []types.Port + +func (r byPortInfo) Len() int { return len(r) } +func (r byPortInfo) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r byPortInfo) Less(i, j int) bool { + if r[i].PrivatePort != r[j].PrivatePort { + return r[i].PrivatePort < r[j].PrivatePort + } + + if r[i].IP != r[j].IP { + return r[i].IP < r[j].IP + } + + if r[i].PublicPort != r[j].PublicPort { + return r[i].PublicPort < r[j].PublicPort + } + + return r[i].Type < r[j].Type +} diff --git a/components/cli/cli/command/formatter/container_test.go b/components/cli/cli/command/formatter/container_test.go index 5282386469..0977a39c02 100644 --- a/components/cli/cli/command/formatter/container_test.go +++ b/components/cli/cli/command/formatter/container_test.go @@ -410,3 +410,248 @@ func TestContainerBackCompat(t *testing.T) { buf.Reset() } } + +type ports struct { + ports []types.Port + expected string +} + +// nolint: lll +func TestDisplayablePorts(t *testing.T) { + cases := []ports{ + { + []types.Port{ + { + PrivatePort: 9988, + Type: "tcp", + }, + }, + "9988/tcp"}, + { + []types.Port{ + { + PrivatePort: 9988, + Type: "udp", + }, + }, + "9988/udp", + }, + { + []types.Port{ + { + IP: "0.0.0.0", + PrivatePort: 9988, + Type: "tcp", + }, + }, + "0.0.0.0:0->9988/tcp", + }, + { + []types.Port{ + { + PrivatePort: 9988, + PublicPort: 8899, + Type: "tcp", + }, + }, + "9988/tcp", + }, + { + []types.Port{ + { + IP: "4.3.2.1", + PrivatePort: 9988, + PublicPort: 8899, + Type: "tcp", + }, + }, + "4.3.2.1:8899->9988/tcp", + }, + { + []types.Port{ + { + IP: "4.3.2.1", + PrivatePort: 9988, + PublicPort: 9988, + Type: "tcp", + }, + }, + "4.3.2.1:9988->9988/tcp", + }, + { + []types.Port{ + { + PrivatePort: 9988, + Type: "udp", + }, { + PrivatePort: 9988, + Type: "udp", + }, + }, + "9988/udp, 9988/udp", + }, + { + []types.Port{ + { + IP: "1.2.3.4", + PublicPort: 9998, + PrivatePort: 9998, + Type: "udp", + }, { + IP: "1.2.3.4", + PublicPort: 9999, + PrivatePort: 9999, + Type: "udp", + }, + }, + "1.2.3.4:9998-9999->9998-9999/udp", + }, + { + []types.Port{ + { + IP: "1.2.3.4", + PublicPort: 8887, + PrivatePort: 9998, + Type: "udp", + }, { + IP: "1.2.3.4", + PublicPort: 8888, + PrivatePort: 9999, + Type: "udp", + }, + }, + "1.2.3.4:8887->9998/udp, 1.2.3.4:8888->9999/udp", + }, + { + []types.Port{ + { + PrivatePort: 9998, + Type: "udp", + }, { + PrivatePort: 9999, + Type: "udp", + }, + }, + "9998-9999/udp", + }, + { + []types.Port{ + { + IP: "1.2.3.4", + PrivatePort: 6677, + PublicPort: 7766, + Type: "tcp", + }, { + PrivatePort: 9988, + PublicPort: 8899, + Type: "udp", + }, + }, + "9988/udp, 1.2.3.4:7766->6677/tcp", + }, + { + []types.Port{ + { + IP: "1.2.3.4", + PrivatePort: 9988, + PublicPort: 8899, + Type: "udp", + }, { + IP: "1.2.3.4", + PrivatePort: 9988, + PublicPort: 8899, + Type: "tcp", + }, { + IP: "4.3.2.1", + PrivatePort: 2233, + PublicPort: 3322, + Type: "tcp", + }, + }, + "4.3.2.1:3322->2233/tcp, 1.2.3.4:8899->9988/tcp, 1.2.3.4:8899->9988/udp", + }, + { + []types.Port{ + { + PrivatePort: 9988, + PublicPort: 8899, + Type: "udp", + }, { + IP: "1.2.3.4", + PrivatePort: 6677, + PublicPort: 7766, + Type: "tcp", + }, { + IP: "4.3.2.1", + PrivatePort: 2233, + PublicPort: 3322, + Type: "tcp", + }, + }, + "9988/udp, 4.3.2.1:3322->2233/tcp, 1.2.3.4:7766->6677/tcp", + }, + { + []types.Port{ + { + PrivatePort: 80, + Type: "tcp", + }, { + PrivatePort: 1024, + Type: "tcp", + }, { + PrivatePort: 80, + Type: "udp", + }, { + PrivatePort: 1024, + Type: "udp", + }, { + IP: "1.1.1.1", + PublicPort: 80, + PrivatePort: 1024, + Type: "tcp", + }, { + IP: "1.1.1.1", + PublicPort: 80, + PrivatePort: 1024, + Type: "udp", + }, { + IP: "1.1.1.1", + PublicPort: 1024, + PrivatePort: 80, + Type: "tcp", + }, { + IP: "1.1.1.1", + PublicPort: 1024, + PrivatePort: 80, + Type: "udp", + }, { + IP: "2.1.1.1", + PublicPort: 80, + PrivatePort: 1024, + Type: "tcp", + }, { + IP: "2.1.1.1", + PublicPort: 80, + PrivatePort: 1024, + Type: "udp", + }, { + IP: "2.1.1.1", + PublicPort: 1024, + PrivatePort: 80, + Type: "tcp", + }, { + IP: "2.1.1.1", + PublicPort: 1024, + PrivatePort: 80, + Type: "udp", + }, + }, + "80/tcp, 80/udp, 1024/tcp, 1024/udp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp", + }, + } + + for _, port := range cases { + actual := DisplayablePorts(port.ports) + assert.Equal(t, port.expected, actual) + } +}