From e169c311d34d027051b96cf811b4fd83bb088ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 2 May 2016 15:09:11 +0200 Subject: [PATCH] Add an ImageSource implementation to docker.go The ImageSource type does not provide all of the functionality of docker.go, but we will be able to reuse the ImageSource parts in an OpenShift client. This is only a restructuring, does not change behavior. --- docker.go | 167 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 64 deletions(-) diff --git a/docker.go b/docker.go index 334cbb64..6c43ac36 100644 --- a/docker.go +++ b/docker.go @@ -51,16 +51,9 @@ func (e errFetchManifest) Error() string { } type dockerImage struct { - ref reference.Named - tag string - digest string - registry string - username string - password string - WWWAuthenticate string - scheme string - rawManifest []byte - transport *http.Transport + src *dockerImageSource + digest string + rawManifest []byte } func (i *dockerImage) RawManifest(version string) ([]byte, error) { @@ -85,7 +78,7 @@ func (i *dockerImage) Manifest() (types.ImageManifest, error) { if err != nil { return nil, err } - imgManifest, err := makeImageManifest(i.ref.FullName(), ms1, i.digest, tags) + imgManifest, err := makeImageManifest(i.src.ref.FullName(), ms1, i.digest, tags) if err != nil { return nil, err } @@ -93,8 +86,9 @@ func (i *dockerImage) Manifest() (types.ImageManifest, error) { } func (i *dockerImage) getTags() ([]string, error) { - url := fmt.Sprintf(tagsURL, i.scheme, i.registry, i.ref.RemoteName()) - res, err := i.makeRequest("GET", url, i.WWWAuthenticate != "", nil) + // FIXME? Breaking the abstraction. + url := fmt.Sprintf(tagsURL, i.src.scheme, i.src.registry, i.src.ref.RemoteName()) + res, err := i.src.makeRequest("GET", url, i.src.WWWAuthenticate != "", nil) if err != nil { return nil, err } @@ -189,7 +183,61 @@ func sanitize(s string) string { return strings.Replace(s, "/", "-", -1) } -func (i *dockerImage) makeRequest(method, url string, auth bool, headers map[string]string) (*http.Response, error) { +type dockerImageSource struct { + ref reference.Named + tag string + registry string + username string + password string + WWWAuthenticate string // Obtained by s.ping() + scheme string // Obtained by s.ping() + transport *http.Transport +} + +func (s *dockerImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) { + pr, err := s.ping() + if err != nil { + return nil, "", err + } + s.WWWAuthenticate = pr.WWWAuthenticate + s.scheme = pr.scheme + url := fmt.Sprintf(manifestURL, s.scheme, s.registry, s.ref.RemoteName(), s.tag) + // 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 + res, err := s.makeRequest("GET", url, pr.needsAuth(), nil) + if err != nil { + return nil, "", err + } + defer res.Body.Close() + manblob, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, "", err + } + if res.StatusCode != http.StatusOK { + return nil, "", errFetchManifest{res.StatusCode, manblob} + } + return manblob, res.Header.Get("Docker-Content-Digest"), nil +} + +func (s *dockerImageSource) GetLayer(digest string) (io.ReadCloser, error) { + url := fmt.Sprintf(blobsURL, s.scheme, s.registry, s.ref.RemoteName(), digest) + logrus.Infof("Downloading %s", url) + res, err := s.makeRequest("GET", url, s.WWWAuthenticate != "", nil) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + // print url also + return nil, fmt.Errorf("Invalid status code returned when fetching blob %d", res.StatusCode) + } + return res.Body, nil +} + +func (s *dockerImageSource) GetSignatures() ([][]byte, error) { + return [][]byte{}, nil +} + +func (s *dockerImageSource) makeRequest(method, url string, auth bool, headers map[string]string) (*http.Response, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err @@ -199,13 +247,13 @@ func (i *dockerImage) makeRequest(method, url string, auth bool, headers map[str req.Header.Add(n, h) } if auth { - if err := i.setupRequestAuth(req); err != nil { + if err := s.setupRequestAuth(req); err != nil { return nil, err } } client := &http.Client{} - if i.transport != nil { - client.Transport = i.transport + if s.transport != nil { + client.Transport = s.transport } res, err := client.Do(req) if err != nil { @@ -214,19 +262,19 @@ func (i *dockerImage) makeRequest(method, url string, auth bool, headers map[str return res, nil } -func (i *dockerImage) setupRequestAuth(req *http.Request) error { - tokens := strings.SplitN(strings.TrimSpace(i.WWWAuthenticate), " ", 2) +func (s *dockerImageSource) setupRequestAuth(req *http.Request) error { + tokens := strings.SplitN(strings.TrimSpace(s.WWWAuthenticate), " ", 2) if len(tokens) != 2 { - return fmt.Errorf("expected 2 tokens in WWW-Authenticate: %d, %s", len(tokens), i.WWWAuthenticate) + return fmt.Errorf("expected 2 tokens in WWW-Authenticate: %d, %s", len(tokens), s.WWWAuthenticate) } switch tokens[0] { case "Basic": - req.SetBasicAuth(i.username, i.password) + req.SetBasicAuth(s.username, s.password) return nil case "Bearer": client := &http.Client{} - if i.transport != nil { - client.Transport = i.transport + if s.transport != nil { + client.Transport = s.transport } res, err := client.Do(req) if err != nil { @@ -263,7 +311,7 @@ func (i *dockerImage) setupRequestAuth(req *http.Request) error { if scope == "" { return fmt.Errorf("missing scope in bearer auth challenge") } - token, err := i.getBearerToken(realm, service, scope) + token, err := s.getBearerToken(realm, service, scope) if err != nil { return err } @@ -274,7 +322,7 @@ func (i *dockerImage) setupRequestAuth(req *http.Request) error { // support docker bearer with authconfig's Auth string? see docker2aci } -func (i *dockerImage) getBearerToken(realm, service, scope string) (string, error) { +func (s *dockerImageSource) getBearerToken(realm, service, scope string) (string, error) { authReq, err := http.NewRequest("GET", realm, nil) if err != nil { return "", err @@ -285,8 +333,8 @@ func (i *dockerImage) getBearerToken(realm, service, scope string) (string, erro getParams.Add("scope", scope) } authReq.URL.RawQuery = getParams.Encode() - if i.username != "" && i.password != "" { - authReq.SetBasicAuth(i.username, i.password) + if s.username != "" && s.password != "" { + authReq.SetBasicAuth(s.username, s.password) } // insecure for now to contact the external token service tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} @@ -328,29 +376,12 @@ func (i *dockerImage) retrieveRawManifest() error { if i.rawManifest != nil { return nil } - pr, err := i.ping() + manblob, unverifiedCanonicalDigest, err := i.src.GetManifest() if err != nil { return err } - i.WWWAuthenticate = pr.WWWAuthenticate - i.scheme = pr.scheme - url := fmt.Sprintf(manifestURL, i.scheme, i.registry, i.ref.RemoteName(), i.tag) - // 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 - res, err := i.makeRequest("GET", url, pr.needsAuth(), nil) - if err != nil { - return err - } - defer res.Body.Close() - manblob, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return errFetchManifest{res.StatusCode, manblob} - } i.rawManifest = manblob - i.digest = res.Header.Get("Docker-Content-Digest") + i.digest = unverifiedCanonicalDigest return nil } @@ -398,31 +429,25 @@ func (i *dockerImage) Layers(layers ...string) error { if !strings.HasPrefix(l, "sha256:") { l = "sha256:" + l } - url := fmt.Sprintf(blobsURL, i.scheme, i.registry, i.ref.RemoteName(), l) - if err := i.getLayer(l, url, tmpDir); err != nil { + if err := i.getLayer(l, tmpDir); err != nil { return err } } return nil } -func (i *dockerImage) getLayer(l, url, tmpDir string) error { - logrus.Infof("Downloading %s", url) - res, err := i.makeRequest("GET", url, i.WWWAuthenticate != "", nil) +func (i *dockerImage) getLayer(digest, tmpDir string) error { + stream, err := i.src.GetLayer(digest) if err != nil { return err } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - // print url also - return fmt.Errorf("Invalid status code returned when fetching blob %d", res.StatusCode) - } - layerPath := path.Join(tmpDir, strings.Replace(l, "sha256:", "", -1)+".tar") + defer stream.Close() + layerPath := path.Join(tmpDir, strings.Replace(digest, "sha256:", "", -1)+".tar") layerFile, err := os.Create(layerPath) if err != nil { return err } - if _, err := io.Copy(layerFile, res.Body); err != nil { + if _, err := io.Copy(layerFile, stream); err != nil { return err } if err := layerFile.Sync(); err != nil { @@ -431,7 +456,8 @@ func (i *dockerImage) getLayer(l, url, tmpDir string) error { return nil } -func parseDockerImage(img, certPath string, tlsVerify bool) (types.Image, error) { +// 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 @@ -472,7 +498,7 @@ func parseDockerImage(img, certPath string, tlsVerify bool) (types.Image, error) TLSClientConfig: tlsc, } } - return &dockerImage{ + return &dockerImageSource{ ref: ref, tag: tag, registry: registry, @@ -482,6 +508,19 @@ func parseDockerImage(img, certPath string, tlsVerify bool) (types.Image, error) }, nil } +// NewDockerImageSource creates a new ImageSource for the specified image and connection specification. +func NewDockerImageSource(img, certPath string, tlsVerify bool) (types.ImageSource, error) { + return newDockerImageSource(img, certPath, tlsVerify) +} + +func parseDockerImage(img, certPath string, tlsVerify bool) (types.Image, error) { + s, err := newDockerImageSource(img, certPath, tlsVerify) + if err != nil { + return nil, err + } + return &dockerImage{src: s}, nil +} + func getDefaultConfigDir(confPath string) string { return filepath.Join(homedir.Get(), confPath) } @@ -576,13 +615,13 @@ func (pr *pingResponse) needsAuth() bool { return pr.WWWAuthenticate != "" } -func (i *dockerImage) ping() (*pingResponse, error) { +func (s *dockerImageSource) ping() (*pingResponse, error) { client := &http.Client{} - if i.transport != nil { - client.Transport = i.transport + if s.transport != nil { + client.Transport = s.transport } ping := func(scheme string) (*pingResponse, error) { - url := fmt.Sprintf(baseURL, scheme, i.registry) + url := fmt.Sprintf(baseURL, scheme, s.registry) resp, err := client.Get(url) if err != nil { return nil, err