dotcloud/docker

View on GitHub
plugin/registry.go

Summary

Maintainability
A
35 mins
Test Coverage
package plugin

import (
    "context"
    "crypto/tls"
    "net"
    "net/http"
    "time"

    "github.com/containerd/containerd/remotes"
    "github.com/containerd/containerd/remotes/docker"
    "github.com/containerd/log"
    "github.com/distribution/reference"
    "github.com/docker/docker/api/types/registry"
    "github.com/docker/docker/dockerversion"
    "github.com/pkg/errors"
)

// scope builds the correct auth scope for the registry client to authorize against
// By default the client currently only does a "repository:" scope with out a classifier, e.g. "(plugin)"
// Without this, the client will not be able to authorize the request
func scope(ref reference.Named, push bool) string {
    scope := "repository(plugin):" + reference.Path(reference.TrimNamed(ref)) + ":pull"
    if push {
        scope += ",push"
    }
    return scope
}

func (pm *Manager) newResolver(ctx context.Context, tracker docker.StatusTracker, auth *registry.AuthConfig, headers http.Header, httpFallback bool) (remotes.Resolver, error) {
    if headers == nil {
        headers = http.Header{}
    }
    headers.Add("User-Agent", dockerversion.DockerUserAgent(ctx))

    return docker.NewResolver(docker.ResolverOptions{
        Tracker: tracker,
        Headers: headers,
        Hosts:   pm.registryHostsFn(auth, httpFallback),
    }), nil
}

func registryHTTPClient(config *tls.Config) *http.Client {
    return &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyFromEnvironment,
            DialContext: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
            }).DialContext,
            TLSClientConfig:     config,
            TLSHandshakeTimeout: 10 * time.Second,
            IdleConnTimeout:     30 * time.Second,
        },
    }
}

func (pm *Manager) registryHostsFn(auth *registry.AuthConfig, httpFallback bool) docker.RegistryHosts {
    return func(hostname string) ([]docker.RegistryHost, error) {
        eps, err := pm.config.RegistryService.LookupPullEndpoints(hostname)
        if err != nil {
            return nil, errors.Wrapf(err, "error resolving repository for %s", hostname)
        }

        hosts := make([]docker.RegistryHost, 0, len(eps))

        for _, ep := range eps {
            // forced http fallback is used only for push since the containerd pusher only ever uses the first host we
            // pass to it.
            // So it is the callers responsibility to retry with this flag set.
            if httpFallback && ep.URL.Scheme != "http" {
                log.G(context.TODO()).WithField("registryHost", hostname).WithField("endpoint", ep).Debugf("Skipping non-http endpoint")
                continue
            }

            caps := docker.HostCapabilityPull | docker.HostCapabilityResolve
            if !ep.Mirror {
                caps = caps | docker.HostCapabilityPush
            }

            host, err := docker.DefaultHost(ep.URL.Host)
            if err != nil {
                return nil, err
            }

            client := registryHTTPClient(ep.TLSConfig)
            hosts = append(hosts, docker.RegistryHost{
                Host:         host,
                Scheme:       ep.URL.Scheme,
                Client:       client,
                Path:         "/v2",
                Capabilities: caps,
                Authorizer: docker.NewDockerAuthorizer(
                    docker.WithAuthClient(client),
                    docker.WithAuthCreds(func(_ string) (string, string, error) {
                        if auth.IdentityToken != "" {
                            return "", auth.IdentityToken, nil
                        }
                        return auth.Username, auth.Password, nil
                    }),
                ),
            })
        }
        log.G(context.TODO()).WithField("registryHost", hostname).WithField("hosts", hosts).Debug("Resolved registry hosts")

        return hosts, nil
    }
}