Split dockerClient from dockerImageSource

The dockerClient encapsulates makeRequest and authentication setup, and
will be shared between the pull and push code.

This is only a restructuring, does not change behavior.

The dockerImage->dockerImageSource->dockerClient inclusion chain is
somewhat ugly, hopefully eventually we will move the remaining
dockerImage functionality either to dockerutils or to the top level, and
then eliminate it.
This commit is contained in:
Miloslav Trmač 2016-04-25 18:00:36 +02:00
parent 2790d9a1c3
commit 0587501ff0

131
docker.go
View File

@ -87,7 +87,7 @@ func (i *dockerImage) Manifest() (types.ImageManifest, error) {
func (i *dockerImage) getTags() ([]string, error) { func (i *dockerImage) getTags() ([]string, error) {
// FIXME? Breaking the abstraction. // FIXME? Breaking the abstraction.
url := fmt.Sprintf(tagsURL, i.src.ref.RemoteName()) url := fmt.Sprintf(tagsURL, i.src.ref.RemoteName())
res, err := i.src.makeRequest("GET", url, nil) res, err := i.src.c.makeRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -183,21 +183,16 @@ func sanitize(s string) string {
} }
type dockerImageSource struct { type dockerImageSource struct {
ref reference.Named ref reference.Named
tag string tag string
registry string c *dockerClient
username string
password string
wwwAuthenticate string // Cache of a value set by ping() if scheme is not empty
scheme string // Cache of a value returned by a successful ping() if not empty
transport *http.Transport
} }
func (s *dockerImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) { func (s *dockerImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) {
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag) url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1 // TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing // TODO(runcom) NO, switch on the resulter manifest like Docker is doing
res, err := s.makeRequest("GET", url, nil) res, err := s.c.makeRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -215,7 +210,7 @@ func (s *dockerImageSource) GetManifest() (manifest []byte, unverifiedCanonicalD
func (s *dockerImageSource) GetLayer(digest string) (io.ReadCloser, error) { func (s *dockerImageSource) GetLayer(digest string) (io.ReadCloser, error) {
url := fmt.Sprintf(blobsURL, s.ref.RemoteName(), digest) url := fmt.Sprintf(blobsURL, s.ref.RemoteName(), digest)
logrus.Infof("Downloading %s", url) logrus.Infof("Downloading %s", url)
res, err := s.makeRequest("GET", url, nil) res, err := s.c.makeRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -230,17 +225,27 @@ func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
return [][]byte{}, nil return [][]byte{}, nil
} }
func (s *dockerImageSource) makeRequest(method, url string, headers map[string]string) (*http.Response, error) { // dockerClient is configuration for dealing with a single Docker registry.
if s.scheme == "" { type dockerClient struct {
pr, err := s.ping() registry string
username string
password string
wwwAuthenticate string // Cache of a value set by ping() if scheme is not empty
scheme string // Cache of a value returned by a successful ping() if not empty
transport *http.Transport
}
func (c *dockerClient) makeRequest(method, url string, headers map[string]string) (*http.Response, error) {
if c.scheme == "" {
pr, err := c.ping()
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.wwwAuthenticate = pr.WWWAuthenticate c.wwwAuthenticate = pr.WWWAuthenticate
s.scheme = pr.scheme c.scheme = pr.scheme
} }
url = fmt.Sprintf(baseURL, s.scheme, s.registry) + url url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url
req, err := http.NewRequest(method, url, nil) req, err := http.NewRequest(method, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -249,14 +254,14 @@ func (s *dockerImageSource) makeRequest(method, url string, headers map[string]s
for n, h := range headers { for n, h := range headers {
req.Header.Add(n, h) req.Header.Add(n, h)
} }
if s.wwwAuthenticate != "" { if c.wwwAuthenticate != "" {
if err := s.setupRequestAuth(req); err != nil { if err := c.setupRequestAuth(req); err != nil {
return nil, err return nil, err
} }
} }
client := &http.Client{} client := &http.Client{}
if s.transport != nil { if c.transport != nil {
client.Transport = s.transport client.Transport = c.transport
} }
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
@ -265,19 +270,19 @@ func (s *dockerImageSource) makeRequest(method, url string, headers map[string]s
return res, nil return res, nil
} }
func (s *dockerImageSource) setupRequestAuth(req *http.Request) error { func (c *dockerClient) setupRequestAuth(req *http.Request) error {
tokens := strings.SplitN(strings.TrimSpace(s.wwwAuthenticate), " ", 2) tokens := strings.SplitN(strings.TrimSpace(c.wwwAuthenticate), " ", 2)
if len(tokens) != 2 { if len(tokens) != 2 {
return fmt.Errorf("expected 2 tokens in WWW-Authenticate: %d, %s", len(tokens), s.wwwAuthenticate) return fmt.Errorf("expected 2 tokens in WWW-Authenticate: %d, %s", len(tokens), c.wwwAuthenticate)
} }
switch tokens[0] { switch tokens[0] {
case "Basic": case "Basic":
req.SetBasicAuth(s.username, s.password) req.SetBasicAuth(c.username, c.password)
return nil return nil
case "Bearer": case "Bearer":
client := &http.Client{} client := &http.Client{}
if s.transport != nil { if c.transport != nil {
client.Transport = s.transport client.Transport = c.transport
} }
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
@ -314,7 +319,7 @@ func (s *dockerImageSource) setupRequestAuth(req *http.Request) error {
if scope == "" { if scope == "" {
return fmt.Errorf("missing scope in bearer auth challenge") return fmt.Errorf("missing scope in bearer auth challenge")
} }
token, err := s.getBearerToken(realm, service, scope) token, err := c.getBearerToken(realm, service, scope)
if err != nil { if err != nil {
return err return err
} }
@ -325,7 +330,7 @@ func (s *dockerImageSource) setupRequestAuth(req *http.Request) error {
// support docker bearer with authconfig's Auth string? see docker2aci // support docker bearer with authconfig's Auth string? see docker2aci
} }
func (s *dockerImageSource) getBearerToken(realm, service, scope string) (string, error) { func (c *dockerClient) getBearerToken(realm, service, scope string) (string, error) {
authReq, err := http.NewRequest("GET", realm, nil) authReq, err := http.NewRequest("GET", realm, nil)
if err != nil { if err != nil {
return "", err return "", err
@ -336,8 +341,8 @@ func (s *dockerImageSource) getBearerToken(realm, service, scope string) (string
getParams.Add("scope", scope) getParams.Add("scope", scope)
} }
authReq.URL.RawQuery = getParams.Encode() authReq.URL.RawQuery = getParams.Encode()
if s.username != "" && s.password != "" { if c.username != "" && c.password != "" {
authReq.SetBasicAuth(s.username, s.password) authReq.SetBasicAuth(c.username, c.password)
} }
// insecure for now to contact the external token service // insecure for now to contact the external token service
tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
@ -449,30 +454,15 @@ func (i *dockerImage) getLayer(dest types.ImageDestination, digest string) error
return dest.PutLayer(digest, stream) return dest.PutLayer(digest, stream)
} }
// newDockerImageSource is the same as NewDockerImageSource, only it returns the more specific *dockerImageSource type. // newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSource, error) { func newDockerClient(refHostname, certPath string, tlsVerify bool) (*dockerClient, error) {
ref, err := reference.ParseNamed(img)
if err != nil {
return nil, err
}
if reference.IsNameOnly(ref) {
ref = reference.WithDefaultTag(ref)
}
var tag string
switch x := ref.(type) {
case reference.Canonical:
tag = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
}
var registry string var registry string
hostname := ref.Hostname() if refHostname == dockerHostname {
if hostname == dockerHostname {
registry = dockerRegistry registry = dockerRegistry
} else { } else {
registry = hostname registry = refHostname
} }
username, password, err := getAuth(ref.Hostname()) username, password, err := getAuth(refHostname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -492,9 +482,7 @@ func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSou
TLSClientConfig: tlsc, TLSClientConfig: tlsc,
} }
} }
return &dockerImageSource{ return &dockerClient{
ref: ref,
tag: tag,
registry: registry, registry: registry,
username: username, username: username,
password: password, password: password,
@ -502,6 +490,33 @@ func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSou
}, nil }, nil
} }
// newDockerImageSource is the same as NewDockerImageSource, only it returns the more specific *dockerImageSource type.
func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSource, error) {
ref, err := reference.ParseNamed(img)
if err != nil {
return nil, err
}
if reference.IsNameOnly(ref) {
ref = reference.WithDefaultTag(ref)
}
var tag string
switch x := ref.(type) {
case reference.Canonical:
tag = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
}
c, err := newDockerClient(ref.Hostname(), certPath, tlsVerify)
if err != nil {
return nil, err
}
return &dockerImageSource{
ref: ref,
tag: tag,
c: c,
}, nil
}
// NewDockerImageSource creates a new ImageSource for the specified image and connection specification. // NewDockerImageSource creates a new ImageSource for the specified image and connection specification.
func NewDockerImageSource(img, certPath string, tlsVerify bool) (types.ImageSource, error) { func NewDockerImageSource(img, certPath string, tlsVerify bool) (types.ImageSource, error) {
return newDockerImageSource(img, certPath, tlsVerify) return newDockerImageSource(img, certPath, tlsVerify)
@ -605,13 +620,13 @@ type pingResponse struct {
errors []apiErr errors []apiErr
} }
func (s *dockerImageSource) ping() (*pingResponse, error) { func (c *dockerClient) ping() (*pingResponse, error) {
client := &http.Client{} client := &http.Client{}
if s.transport != nil { if c.transport != nil {
client.Transport = s.transport client.Transport = c.transport
} }
ping := func(scheme string) (*pingResponse, error) { ping := func(scheme string) (*pingResponse, error) {
url := fmt.Sprintf(baseURL, scheme, s.registry) url := fmt.Sprintf(baseURL, scheme, c.registry)
resp, err := client.Get(url) resp, err := client.Get(url)
if err != nil { if err != nil {
return nil, err return nil, err