diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 0164219b7f..56f0d0d57b 100644 --- a/cli/command/container/run.go +++ b/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/cli/command/image/build.go b/cli/command/image/build.go index 1a6cb951bc..2077914717 100644 --- a/cli/command/image/build.go +++ b/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/cli/config/configfile/file.go b/cli/config/configfile/file.go index 7214325d87..78024acf54 100644 --- a/cli/config/configfile/file.go +++ b/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/cli/config/configfile/file_test.go b/cli/config/configfile/file_test.go index 435797f681..8c84347719 100644 --- a/cli/config/configfile/file_test.go +++ b/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) +}