diff --git a/components/cli/AUTHORS b/components/cli/AUTHORS index 7196404ef3..39a4576020 100644 --- a/components/cli/AUTHORS +++ b/components/cli/AUTHORS @@ -604,6 +604,7 @@ Wang Long Wang Ping Wang Xing Wang Yuexiao +Wataru Ishida Wayne Song Wen Cheng Ma Wenzhi Liang diff --git a/components/cli/cli/command/cli.go b/components/cli/cli/command/cli.go index 484c8c537c..5a95ab06b2 100644 --- a/components/cli/cli/command/cli.go +++ b/components/cli/cli/command/cli.go @@ -300,12 +300,12 @@ func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, er Timeout: 30 * time.Second, }).DialContext, } - proto, addr, _, err := client.ParseHost(host) + hostURL, err := client.ParseHostURL(host) if err != nil { return nil, err } - sockets.ConfigureTransport(tr, proto, addr) + sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host) return &http.Client{ Transport: tr, diff --git a/components/cli/cli/command/stack/kubernetes/deploy.go b/components/cli/cli/command/stack/kubernetes/deploy.go index b319eba8b0..5149c8805d 100644 --- a/components/cli/cli/command/stack/kubernetes/deploy.go +++ b/components/cli/cli/command/stack/kubernetes/deploy.go @@ -22,11 +22,11 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error { } // Parse the compose file - cfg, version, err := loader.LoadComposefile(dockerCli, opts) + cfg, err := loader.LoadComposefile(dockerCli, opts) if err != nil { return err } - stack, err := LoadStack(opts.Namespace, version, *cfg) + stack, err := LoadStack(opts.Namespace, *cfg) if err != nil { return err } diff --git a/components/cli/cli/command/stack/kubernetes/loader.go b/components/cli/cli/command/stack/kubernetes/loader.go index f39d31529c..b4bcf96f5e 100644 --- a/components/cli/cli/command/stack/kubernetes/loader.go +++ b/components/cli/cli/command/stack/kubernetes/loader.go @@ -7,18 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type versionedConfig struct { - *composetypes.Config `yaml:",inline"` - Version string -} - // LoadStack loads a stack from a Compose config, with a given name. -func LoadStack(name, version string, cfg composetypes.Config) (*apiv1beta1.Stack, error) { - cfg.Filename = "" - res, err := yaml.Marshal(versionedConfig{ - Version: version, - Config: &cfg, - }) +func LoadStack(name string, cfg composetypes.Config) (*apiv1beta1.Stack, error) { + res, err := yaml.Marshal(cfg) if err != nil { return nil, err } diff --git a/components/cli/cli/command/stack/kubernetes/loader_test.go b/components/cli/cli/command/stack/kubernetes/loader_test.go index 7e2ff8d810..a96e66d47b 100644 --- a/components/cli/cli/command/stack/kubernetes/loader_test.go +++ b/components/cli/cli/command/stack/kubernetes/loader_test.go @@ -10,7 +10,8 @@ import ( ) func TestLoadStack(t *testing.T) { - s, err := LoadStack("foo", "3.1", composetypes.Config{ + s, err := LoadStack("foo", composetypes.Config{ + Version: "3.1", Filename: "banana", Services: []composetypes.ServiceConfig{ { @@ -29,15 +30,16 @@ func TestLoadStack(t *testing.T) { Name: "foo", }, Spec: apiv1beta1.StackSpec{ - ComposeFile: string(`configs: {} -networks: {} -secrets: {} + ComposeFile: string(`version: "3.1" services: bar: image: bar foo: image: foo +networks: {} volumes: {} +secrets: {} +configs: {} `), }, }, s) diff --git a/components/cli/cli/command/stack/loader/loader.go b/components/cli/cli/command/stack/loader/loader.go index 6283faa77b..b479094512 100644 --- a/components/cli/cli/command/stack/loader/loader.go +++ b/components/cli/cli/command/stack/loader/loader.go @@ -18,21 +18,21 @@ import ( ) // LoadComposefile parse the composefile specified in the cli and returns its Config and version. -func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.Config, string, error) { +func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.Config, error) { configDetails, err := getConfigDetails(opts.Composefiles, dockerCli.In()) if err != nil { - return nil, "", err + return nil, err } dicts := getDictsFrom(configDetails.ConfigFiles) config, err := loader.Load(configDetails) if err != nil { if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok { - return nil, "", errors.Errorf("Compose file contains unsupported options:\n\n%s\n", + return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n", propertyWarnings(fpe.Properties)) } - return nil, "", err + return nil, err } unsupportedProperties := loader.GetUnsupportedProperties(dicts...) @@ -46,7 +46,7 @@ func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes. fmt.Fprintf(dockerCli.Err(), "Ignoring deprecated options:\n\n%s\n\n", propertyWarnings(deprecatedProperties)) } - return config, configDetails.Version, nil + return config, nil } func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]interface{} { diff --git a/components/cli/cli/command/stack/swarm/deploy_composefile.go b/components/cli/cli/command/stack/swarm/deploy_composefile.go index c45f463f8d..0a3f2ac720 100644 --- a/components/cli/cli/command/stack/swarm/deploy_composefile.go +++ b/components/cli/cli/command/stack/swarm/deploy_composefile.go @@ -18,7 +18,7 @@ import ( ) func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error { - config, _, err := loader.LoadComposefile(dockerCli, opts) + config, err := loader.LoadComposefile(dockerCli, opts) if err != nil { return err } diff --git a/components/cli/cli/compose/loader/full-struct_test.go b/components/cli/cli/compose/loader/full-struct_test.go index 3e73e7553c..3f2a587bce 100644 --- a/components/cli/cli/compose/loader/full-struct_test.go +++ b/components/cli/cli/compose/loader/full-struct_test.go @@ -8,6 +8,7 @@ import ( func fullExampleConfig(workingDir, homeDir string) *types.Config { return &types.Config{ + Version: "3.6", Services: services(workingDir, homeDir), Networks: networks(), Volumes: volumes(), diff --git a/components/cli/cli/compose/loader/loader.go b/components/cli/cli/compose/loader/loader.go index 12bdf4b128..505e4c78fe 100644 --- a/components/cli/cli/compose/loader/loader.go +++ b/components/cli/cli/compose/loader/loader.go @@ -98,7 +98,9 @@ func validateForbidden(configDict map[string]interface{}) error { func loadSections(config map[string]interface{}, configDetails types.ConfigDetails) (*types.Config, error) { var err error - cfg := types.Config{} + cfg := types.Config{ + Version: schema.Version(config), + } var loaders = []struct { key string @@ -359,7 +361,9 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str return nil, err } - resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv) + if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil { + return nil, err + } return serviceConfig, nil } @@ -398,12 +402,16 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l return nil } -func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) { +func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) error { for i, volume := range volumes { if volume.Type != "bind" { continue } + if volume.Source == "" { + return errors.New(`invalid mount config for type "bind": field Source must not be empty`) + } + filePath := expandUser(volume.Source, lookupEnv) // Check for a Unix absolute path first, to handle a Windows client // with a Unix daemon. This handles a Windows client connecting to a @@ -416,6 +424,7 @@ func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, volume.Source = filePath volumes[i] = volume } + return nil } // TODO: make this more robust diff --git a/components/cli/cli/compose/loader/loader_test.go b/components/cli/cli/compose/loader/loader_test.go index 8275627d0a..09d3744e84 100644 --- a/components/cli/cli/compose/loader/loader_test.go +++ b/components/cli/cli/compose/loader/loader_test.go @@ -119,6 +119,7 @@ func strPtr(val string) *string { } var sampleConfig = types.Config{ + Version: "3.0", Services: []types.ServiceConfig{ { Name: "foo", @@ -174,6 +175,7 @@ func TestParseYAML(t *testing.T) { func TestLoad(t *testing.T) { actual, err := Load(buildConfigDetails(sampleDict, nil)) require.NoError(t, err) + assert.Equal(t, sampleConfig.Version, actual.Version) assert.Equal(t, serviceSort(sampleConfig.Services), serviceSort(actual.Services)) assert.Equal(t, sampleConfig.Networks, actual.Networks) assert.Equal(t, sampleConfig.Volumes, actual.Volumes) @@ -573,6 +575,7 @@ networks: require.NoError(t, err) expected := &types.Config{ Filename: "filename.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "web", @@ -892,6 +895,46 @@ services: assert.Contains(t, err.Error(), "services.tmpfs.volumes.0 Additional property tmpfs is not allowed") } +func TestLoadBindMountSourceMustNotBeEmpty(t *testing.T) { + _, err := loadYAML(` +version: "3.5" +services: + tmpfs: + image: nginx:latest + volumes: + - type: bind + target: /app +`) + require.EqualError(t, err, `invalid mount config for type "bind": field Source must not be empty`) +} + +func TestLoadBindMountWithSource(t *testing.T) { + config, err := loadYAML(` +version: "3.5" +services: + bind: + image: nginx:latest + volumes: + - type: bind + target: /app + source: "." +`) + require.NoError(t, err) + + workingDir, err := os.Getwd() + require.NoError(t, err) + + expected := types.ServiceVolumeConfig{ + Type: "bind", + Source: workingDir, + Target: "/app", + } + + require.Len(t, config.Services, 1) + assert.Len(t, config.Services[0].Volumes, 1) + assert.Equal(t, expected, config.Services[0].Volumes[0]) +} + func TestLoadTmpfsVolumeSizeCanBeZero(t *testing.T) { config, err := loadYAML(` version: "3.6" diff --git a/components/cli/cli/compose/loader/merge_test.go b/components/cli/cli/compose/loader/merge_test.go index 97a68043a8..d9e8599359 100644 --- a/components/cli/cli/compose/loader/merge_test.go +++ b/components/cli/cli/compose/loader/merge_test.go @@ -207,6 +207,7 @@ func TestLoadLogging(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "foo", @@ -325,6 +326,7 @@ func TestLoadMultipleServicePorts(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "foo", @@ -450,6 +452,7 @@ func TestLoadMultipleSecretsConfig(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "foo", @@ -575,6 +578,7 @@ func TestLoadMultipleConfigobjsConfig(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "foo", @@ -690,6 +694,7 @@ func TestLoadMultipleUlimits(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "foo", @@ -808,6 +813,7 @@ func TestLoadMultipleNetworks(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "foo", @@ -895,6 +901,7 @@ func TestLoadMultipleConfigs(t *testing.T) { require.NoError(t, err) require.Equal(t, &types.Config{ Filename: "base.yml", + Version: "3.4", Services: []types.ServiceConfig{ { Name: "bar", diff --git a/components/cli/cli/compose/loader/types_test.go b/components/cli/cli/compose/loader/types_test.go index 2dee0b65b1..f27539f88f 100644 --- a/components/cli/cli/compose/loader/types_test.go +++ b/components/cli/cli/compose/loader/types_test.go @@ -9,26 +9,7 @@ import ( func TestMarshallConfig(t *testing.T) { cfg := fullExampleConfig("/foo", "/bar") - expected := `configs: {} -networks: - external-network: - name: external-network - external: true - other-external-network: - name: my-cool-network - external: true - other-network: - driver: overlay - driver_opts: - baz: "1" - foo: bar - ipam: - driver: overlay - config: - - subnet: 172.16.238.0/24 - - subnet: 2001:3984:3989::/64 - some-network: {} -secrets: {} + expected := `version: "3.6" services: foo: build: @@ -292,6 +273,24 @@ services: tmpfs: size: 10000 working_dir: /code +networks: + external-network: + name: external-network + external: true + other-external-network: + name: my-cool-network + external: true + other-network: + driver: overlay + driver_opts: + baz: "1" + foo: bar + ipam: + driver: overlay + config: + - subnet: 172.16.238.0/24 + - subnet: 2001:3984:3989::/64 + some-network: {} volumes: another-volume: name: user_specified_name @@ -314,6 +313,8 @@ volumes: baz: "1" foo: bar some-volume: {} +secrets: {} +configs: {} ` actual, err := yaml.Marshal(cfg) diff --git a/components/cli/cli/compose/schema/bindata.go b/components/cli/cli/compose/schema/bindata.go index d332096d9f..192859b744 100644 --- a/components/cli/cli/compose/schema/bindata.go +++ b/components/cli/cli/compose/schema/bindata.go @@ -427,41 +427,41 @@ eXTO87V/0u2+HN315DMgcTfoOwl7F1X4+r5JHZ6zN9+GTlz96VeHm/L/y+b/AQAA///+Z1URVUEAAA== size: 17007, modtime: 1518458244, compressed: ` -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 `, }, diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.6.json b/components/cli/cli/compose/schema/data/config_schema_v3.6.json index 8e718780bf..95a552b346 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.6.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.6.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "id": "config_schema_v3.5.json", + "id": "config_schema_v3.6.json", "type": "object", "required": ["version"], diff --git a/components/cli/cli/compose/types/types.go b/components/cli/cli/compose/types/types.go index 2299871cfb..107dc85784 100644 --- a/components/cli/cli/compose/types/types.go +++ b/components/cli/cli/compose/types/types.go @@ -70,34 +70,30 @@ func (cd ConfigDetails) LookupEnv(key string) (string, bool) { // Config is a full compose file configuration type Config struct { - Filename string - Services []ServiceConfig + Filename string `yaml:"-"` + Version string + Services Services Networks map[string]NetworkConfig Volumes map[string]VolumeConfig Secrets map[string]SecretConfig Configs map[string]ConfigObjConfig } -// MarshalYAML makes Config implement yaml.Marshaller -func (c *Config) MarshalYAML() (interface{}, error) { - m := map[string]interface{}{} +// Services is a list of ServiceConfig +type Services []ServiceConfig + +// MarshalYAML makes Services implement yaml.Marshaller +func (s Services) MarshalYAML() (interface{}, error) { services := map[string]ServiceConfig{} - for _, service := range c.Services { - s := service - s.Name = "" - services[service.Name] = s + for _, service := range s { + services[service.Name] = service } - m["services"] = services - m["networks"] = c.Networks - m["volumes"] = c.Volumes - m["secrets"] = c.Secrets - m["configs"] = c.Configs - return m, nil + return services, nil } // ServiceConfig is the configuration of one service type ServiceConfig struct { - Name string `yaml:",omitempty"` + Name string `yaml:"-"` Build BuildConfig `yaml:",omitempty"` CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty"` diff --git a/components/cli/docs/reference/commandline/service_create.md b/components/cli/docs/reference/commandline/service_create.md index 25d0c17c61..2eeae5d13e 100644 --- a/components/cli/docs/reference/commandline/service_create.md +++ b/components/cli/docs/reference/commandline/service_create.md @@ -736,7 +736,7 @@ etjpu59cykrptrgw0z0hk5snf After you create an overlay network in swarm mode, all manager nodes have access to the network. -When you create a service and pass the --network flag to attach the service to +When you create a service and pass the `--network` flag to attach the service to the overlay network: ```bash @@ -754,6 +754,9 @@ The swarm extends my-network to each node running the service. Containers on the same network can access each other using [service discovery](https://docs.docker.com/engine/swarm/networking/#use-swarm-mode-service-discovery). +Long form syntax of `--network` allows to specify list of aliases and driver options: +`--network name=my-network,alias=web1,driver-opt=field1=value1` + ### Publish service ports externally to the swarm (-p, --publish) You can publish service ports to make them available externally to the swarm diff --git a/components/cli/docs/reference/commandline/service_update.md b/components/cli/docs/reference/commandline/service_update.md index 41ca539119..5c43f02099 100644 --- a/components/cli/docs/reference/commandline/service_update.md +++ b/components/cli/docs/reference/commandline/service_update.md @@ -181,7 +181,7 @@ myservice Use the `--publish-add` or `--publish-rm` flags to add or remove a published port for a service. You can use the short or long syntax discussed in the -[docker service create](service_create/#attach-a-service-to-an-existing-network-network) +[docker service create](service_create/#publish-service-ports-externally-to-the-swarm) reference. The following example adds a published service port to an existing service. @@ -192,6 +192,22 @@ $ docker service update \ myservice ``` +### Add or remove network + +Use the `--network-add` or `--network-rm` flags to add or remove a network for +a service. You can use the short or long syntax discussed in the +[docker service create](service_create/#attach-a-service-to-an-existing-network-network) +reference. + +The following example adds a new alias name to an existing service already connected to network my-network: + +```bash +$ docker service update \ + --network-rm my-network \ + --network-add name=my-network,alias=web1 \ + myservice +``` + ### Roll back to the previous version of a service Use the `--rollback` option to roll back to the previous version of the service. diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index 38c1b15584..d0ac18a054 100755 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -5,7 +5,7 @@ github.com/coreos/etcd v3.2.1 github.com/cpuguy83/go-md2man v1.0.8 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c -github.com/docker/docker 079ed017b61eb819b8184b90013ce89465d3aaba +github.com/docker/docker 0ede01237c9ab871f1b8db0364427407f3e46541 github.com/docker/docker-credential-helpers 3c90bd29a46b943b2a9842987b58fb91a7c1819b # the docker/go package contains a customized version of canonical/json # and is used by Notary. The package is periodically rebased on current Go versions. 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 97a92f8b78..beb251a989 100644 --- a/components/cli/vendor/github.com/docker/docker/api/common.go +++ b/components/cli/vendor/github.com/docker/docker/api/common.go @@ -3,7 +3,7 @@ package api // import "github.com/docker/docker/api" // Common constants for daemon and client. const ( // DefaultVersion of Current REST API - DefaultVersion string = "1.36" + DefaultVersion string = "1.37" // 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/swarm/config.go b/components/cli/vendor/github.com/docker/docker/api/types/swarm/config.go index c1fdf3b3e4..a1555cf43e 100644 --- a/components/cli/vendor/github.com/docker/docker/api/types/swarm/config.go +++ b/components/cli/vendor/github.com/docker/docker/api/types/swarm/config.go @@ -13,6 +13,10 @@ type Config struct { type ConfigSpec struct { Annotations Data []byte `json:",omitempty"` + + // Templating controls whether and how to evaluate the config payload as + // a template. If it is not set, no templating is used. + Templating *Driver `json:",omitempty"` } // ConfigReferenceFileTarget is a file target in a config reference diff --git a/components/cli/vendor/github.com/docker/docker/api/types/swarm/secret.go b/components/cli/vendor/github.com/docker/docker/api/types/swarm/secret.go index cfba1141d8..d5213ec981 100644 --- a/components/cli/vendor/github.com/docker/docker/api/types/swarm/secret.go +++ b/components/cli/vendor/github.com/docker/docker/api/types/swarm/secret.go @@ -14,6 +14,10 @@ type SecretSpec struct { Annotations Data []byte `json:",omitempty"` Driver *Driver `json:",omitempty"` // name of the secrets driver used to fetch the secret's value from an external secret store + + // Templating controls whether and how to evaluate the secret payload as + // a template. If it is not set, no templating is used. + Templating *Driver `json:",omitempty"` } // SecretReferenceFileTarget is a file target in a secret reference 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 6ce0cdba1f..e129bb20f3 100644 --- a/components/cli/vendor/github.com/docker/docker/client/client.go +++ b/components/cli/vendor/github.com/docker/docker/client/client.go @@ -42,8 +42,8 @@ For example, to list running containers (the equivalent of "docker ps"): package client // import "github.com/docker/docker/client" import ( - "errors" "fmt" + "net" "net/http" "net/url" "os" @@ -56,6 +56,7 @@ import ( "github.com/docker/docker/api/types/versions" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" + "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -103,18 +104,21 @@ func CheckRedirect(req *http.Request, via []*http.Request) error { } // NewEnvClient initializes a new API client based on environment variables. -// Use DOCKER_HOST to set the url to the docker server. -// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. -// Use DOCKER_CERT_PATH to load the TLS certificates from. -// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. -// deprecated: use NewClientWithOpts(FromEnv) +// See FromEnv for a list of support environment variables. +// +// Deprecated: use NewClientWithOpts(FromEnv) func NewEnvClient() (*Client, error) { return NewClientWithOpts(FromEnv) } -// FromEnv enhance the default client with values from environment variables +// FromEnv configures the client with values from environment variables. +// +// Supported environment variables: +// DOCKER_HOST to set the url to the docker server. +// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. +// DOCKER_CERT_PATH to load the TLS certificates from. +// DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. func FromEnv(c *Client) error { - var httpClient *http.Client if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" { options := tlsconfig.Options{ CAFile: filepath.Join(dockerCertPath, "ca.pem"), @@ -127,30 +131,58 @@ func FromEnv(c *Client) error { return err } - httpClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsc, - }, + c.client = &http.Client{ + Transport: &http.Transport{TLSClientConfig: tlsc}, CheckRedirect: CheckRedirect, } - WithHTTPClient(httpClient)(c) } - host := os.Getenv("DOCKER_HOST") - if host != "" { - // WithHost will create an API client if it doesn't exist + if host := os.Getenv("DOCKER_HOST"); host != "" { if err := WithHost(host)(c); err != nil { return err } } - version := os.Getenv("DOCKER_API_VERSION") - if version != "" { + + if version := os.Getenv("DOCKER_API_VERSION"); version != "" { c.version = version c.manualOverride = true } return nil } +// WithTLSClientConfig applies a tls config to the client transport. +func WithTLSClientConfig(cacertPath, certPath, keyPath string) func(*Client) error { + return func(c *Client) error { + opts := tlsconfig.Options{ + CAFile: cacertPath, + CertFile: certPath, + KeyFile: keyPath, + ExclusiveRootPools: true, + } + config, err := tlsconfig.Client(opts) + if err != nil { + return errors.Wrap(err, "failed to create tls config") + } + if transport, ok := c.client.Transport.(*http.Transport); ok { + transport.TLSClientConfig = config + return nil + } + return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport) + } +} + +// WithDialer applies the dialer.DialContext to the client transport. This can be +// used to set the Timeout and KeepAlive settings of the client. +func WithDialer(dialer *net.Dialer) func(*Client) error { + return func(c *Client) error { + if transport, ok := c.client.Transport.(*http.Transport); ok { + transport.DialContext = dialer.DialContext + return nil + } + return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport) + } +} + // WithVersion overrides the client version with the specified one func WithVersion(version string) func(*Client) error { return func(c *Client) error { @@ -159,8 +191,7 @@ func WithVersion(version string) func(*Client) error { } } -// WithHost overrides the client host with the specified one, creating a new -// http client if one doesn't exist +// WithHost overrides the client host with the specified one. func WithHost(host string) func(*Client) error { return func(c *Client) error { hostURL, err := ParseHostURL(host) @@ -171,17 +202,10 @@ func WithHost(host string) func(*Client) error { c.proto = hostURL.Scheme c.addr = hostURL.Host c.basePath = hostURL.Path - if c.client == nil { - client, err := defaultHTTPClient(host) - if err != nil { - return err - } - return WithHTTPClient(client)(c) - } if transport, ok := c.client.Transport.(*http.Transport); ok { return sockets.ConfigureTransport(transport, c.proto, c.addr) } - return fmt.Errorf("cannot apply host to http transport") + return errors.Errorf("cannot apply host to transport: %T", c.client.Transport) } } @@ -266,7 +290,7 @@ func defaultHTTPClient(host string) (*http.Client, error) { // It won't send any version information if the version number is empty. It is // highly recommended that you set a version or your client may break if the // server is upgraded. -// deprecated: use NewClientWithOpts +// Deprecated: use NewClientWithOpts func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders)) } @@ -332,17 +356,6 @@ func (cli *Client) DaemonHost() string { return cli.host } -// ParseHost parses a url string, validates the strings is a host url, and returns -// the parsed host as: protocol, address, and base path -// Deprecated: use ParseHostURL -func ParseHost(host string) (string, string, string, error) { - hostURL, err := ParseHostURL(host) - if err != nil { - return "", "", "", err - } - return hostURL.Scheme, hostURL.Host, hostURL.Path, nil -} - // ParseHostURL parses a url string, validates the string is a host url, and // returns the parsed URL func ParseHostURL(host string) (*url.URL, error) { @@ -378,6 +391,7 @@ func (cli *Client) CustomHTTPHeaders() map[string]string { } // SetCustomHTTPHeaders that will be set on every HTTP request made by the client. +// Deprecated: use WithHTTPHeaders when creating the client. func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) { cli.customHTTPHeaders = headers } 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 e928e647a7..8517546abd 100644 --- a/components/cli/vendor/github.com/docker/docker/client/interface.go +++ b/components/cli/vendor/github.com/docker/docker/client/interface.go @@ -37,6 +37,7 @@ type CommonAPIClient interface { NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersionPing(types.Ping) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) + Close() error } // ContainerAPIClient defines API client methods for the containers diff --git a/components/cli/vendor/github.com/docker/docker/client/request.go b/components/cli/vendor/github.com/docker/docker/client/request.go index 986b512dda..302f599f5c 100644 --- a/components/cli/vendor/github.com/docker/docker/client/request.go +++ b/components/cli/vendor/github.com/docker/docker/client/request.go @@ -123,10 +123,7 @@ func (cli *Client) sendRequest(ctx context.Context, method, path string, query u if err != nil { return resp, err } - if err := cli.checkResponseErr(resp); err != nil { - return resp, err - } - return resp, nil + return resp, cli.checkResponseErr(resp) } func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResponse, error) {