lib/containerruntime/dockerdaemon/cli.go (115 lines of code) (raw):

// Copyright (c) 2016-2019 Uber Technologies, Inc. // // 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. package dockerdaemon import ( "bytes" "context" "fmt" "io/ioutil" "net" "net/http" "net/url" "strings" "syscall" "time" "golang.org/x/net/context/ctxhttp" ) const _defaultTimeout = 32 * time.Second // DockerClient is a docker daemon client. type DockerClient interface { PullImage(ctx context.Context, repo, tag string) error } type dockerClient struct { version string scheme string addr string basePath string registry string client *http.Client } // NewDockerClient creates a new DockerClient. func NewDockerClient(config Config, registry string) (DockerClient, error) { config = config.applyDefaults() client, addr, basePath, err := parseHost(config.DockerHost) if err != nil { return nil, fmt.Errorf("parse docker host %q: %s", config.DockerHost, err) } return &dockerClient{ version: config.DockerClientVersion, scheme: config.DockerScheme, addr: addr, basePath: basePath, registry: registry, client: client, }, nil } // parseHost parses host URL and returns a HTTP client. // This is needed because url.Parse cannot correctly parse url of format // "unix:///...". func parseHost(host string) (*http.Client, string, string, error) { strs := strings.SplitN(host, "://", 2) if len(strs) == 1 { return nil, "", "", fmt.Errorf("unable to parse docker host `%s`", host) } var basePath string transport := new(http.Transport) protocol, addr := strs[0], strs[1] if protocol == "tcp" { parsed, err := url.Parse("tcp://" + addr) if err != nil { return nil, "", "", err } addr = parsed.Host basePath = parsed.Path } else if protocol == "unix" { if len(addr) > len(syscall.RawSockaddrUnix{}.Path) { return nil, "", "", fmt.Errorf("unix socket path %q is too long", addr) } transport.DisableCompression = true transport.Dial = func(_, _ string) (net.Conn, error) { return net.DialTimeout(protocol, addr, _defaultTimeout) } } else { return nil, "", "", fmt.Errorf("protocol %s not supported", protocol) } client := &http.Client{ Transport: transport, } return client, addr, basePath, nil } // ImagePull calls `docker pull` on an image from known registry. func (cli *dockerClient) PullImage(ctx context.Context, repo, tag string) error { query := url.Values{} fromImage := fmt.Sprintf("%s/%s", cli.registry, repo) query.Set("fromImage", fromImage) query.Set("tag", tag) headers := map[string][]string{"X-Registry-Auth": {""}} urlPath := "/images/create" // Construct request. It veries depending on client version. var apiPath string if cli.version != "" { v := strings.TrimPrefix(cli.version, "v") apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, urlPath) } else { apiPath = fmt.Sprintf("%s%s", cli.basePath, urlPath) } u := &url.URL{Path: apiPath} if len(query) > 0 { u.RawQuery = query.Encode() } req, err := http.NewRequest("POST", u.String(), bytes.NewReader([]byte{})) if err != nil { return fmt.Errorf("create request: %s", err) } req.Header = headers req.Host = "docker" req.URL.Host = cli.addr req.URL.Scheme = cli.scheme resp, err := ctxhttp.Do(ctx, cli.client, req) if err != nil { return fmt.Errorf("send post request: %s", err) } defer resp.Body.Close() if resp.StatusCode != 200 { errMsg, err := ioutil.ReadAll(resp.Body) if err != nil { return fmt.Errorf("read error resp: %s", err) } return fmt.Errorf("Error posting to %s: code %d, err: %s", urlPath, resp.StatusCode, errMsg) } // Docker daemon returns 200 early. Close resp.Body after reading all. if _, err := ioutil.ReadAll(resp.Body); err != nil { return fmt.Errorf("read resp body: %s", err) } return nil }