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.
This commit is contained in:
Miloslav Trmač 2016-05-02 15:09:11 +02:00
parent a4aedae063
commit e169c311d3

167
docker.go
View File

@ -51,16 +51,9 @@ func (e errFetchManifest) Error() string {
} }
type dockerImage struct { type dockerImage struct {
ref reference.Named src *dockerImageSource
tag string digest string
digest string rawManifest []byte
registry string
username string
password string
WWWAuthenticate string
scheme string
rawManifest []byte
transport *http.Transport
} }
func (i *dockerImage) RawManifest(version string) ([]byte, error) { func (i *dockerImage) RawManifest(version string) ([]byte, error) {
@ -85,7 +78,7 @@ func (i *dockerImage) Manifest() (types.ImageManifest, error) {
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -93,8 +86,9 @@ func (i *dockerImage) Manifest() (types.ImageManifest, error) {
} }
func (i *dockerImage) getTags() ([]string, error) { func (i *dockerImage) getTags() ([]string, error) {
url := fmt.Sprintf(tagsURL, i.scheme, i.registry, i.ref.RemoteName()) // FIXME? Breaking the abstraction.
res, err := i.makeRequest("GET", url, i.WWWAuthenticate != "", nil) 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 { if err != nil {
return nil, err return nil, err
} }
@ -189,7 +183,61 @@ func sanitize(s string) string {
return strings.Replace(s, "/", "-", -1) 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) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -199,13 +247,13 @@ func (i *dockerImage) makeRequest(method, url string, auth bool, headers map[str
req.Header.Add(n, h) req.Header.Add(n, h)
} }
if auth { if auth {
if err := i.setupRequestAuth(req); err != nil { if err := s.setupRequestAuth(req); err != nil {
return nil, err return nil, err
} }
} }
client := &http.Client{} client := &http.Client{}
if i.transport != nil { if s.transport != nil {
client.Transport = i.transport client.Transport = s.transport
} }
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
@ -214,19 +262,19 @@ func (i *dockerImage) makeRequest(method, url string, auth bool, headers map[str
return res, nil return res, nil
} }
func (i *dockerImage) setupRequestAuth(req *http.Request) error { func (s *dockerImageSource) setupRequestAuth(req *http.Request) error {
tokens := strings.SplitN(strings.TrimSpace(i.WWWAuthenticate), " ", 2) tokens := strings.SplitN(strings.TrimSpace(s.WWWAuthenticate), " ", 2)
if len(tokens) != 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] { switch tokens[0] {
case "Basic": case "Basic":
req.SetBasicAuth(i.username, i.password) req.SetBasicAuth(s.username, s.password)
return nil return nil
case "Bearer": case "Bearer":
client := &http.Client{} client := &http.Client{}
if i.transport != nil { if s.transport != nil {
client.Transport = i.transport client.Transport = s.transport
} }
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
@ -263,7 +311,7 @@ func (i *dockerImage) 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 := i.getBearerToken(realm, service, scope) token, err := s.getBearerToken(realm, service, scope)
if err != nil { if err != nil {
return err return err
} }
@ -274,7 +322,7 @@ func (i *dockerImage) 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 (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) authReq, err := http.NewRequest("GET", realm, nil)
if err != nil { if err != nil {
return "", err return "", err
@ -285,8 +333,8 @@ func (i *dockerImage) getBearerToken(realm, service, scope string) (string, erro
getParams.Add("scope", scope) getParams.Add("scope", scope)
} }
authReq.URL.RawQuery = getParams.Encode() authReq.URL.RawQuery = getParams.Encode()
if i.username != "" && i.password != "" { if s.username != "" && s.password != "" {
authReq.SetBasicAuth(i.username, i.password) authReq.SetBasicAuth(s.username, s.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}}
@ -328,29 +376,12 @@ func (i *dockerImage) retrieveRawManifest() error {
if i.rawManifest != nil { if i.rawManifest != nil {
return nil return nil
} }
pr, err := i.ping() manblob, unverifiedCanonicalDigest, err := i.src.GetManifest()
if err != nil { if err != nil {
return err 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.rawManifest = manblob
i.digest = res.Header.Get("Docker-Content-Digest") i.digest = unverifiedCanonicalDigest
return nil return nil
} }
@ -398,31 +429,25 @@ func (i *dockerImage) Layers(layers ...string) error {
if !strings.HasPrefix(l, "sha256:") { if !strings.HasPrefix(l, "sha256:") {
l = "sha256:" + l l = "sha256:" + l
} }
url := fmt.Sprintf(blobsURL, i.scheme, i.registry, i.ref.RemoteName(), l) if err := i.getLayer(l, tmpDir); err != nil {
if err := i.getLayer(l, url, tmpDir); err != nil {
return err return err
} }
} }
return nil return nil
} }
func (i *dockerImage) getLayer(l, url, tmpDir string) error { func (i *dockerImage) getLayer(digest, tmpDir string) error {
logrus.Infof("Downloading %s", url) stream, err := i.src.GetLayer(digest)
res, err := i.makeRequest("GET", url, i.WWWAuthenticate != "", nil)
if err != nil { if err != nil {
return err return err
} }
defer res.Body.Close() defer stream.Close()
if res.StatusCode != http.StatusOK { layerPath := path.Join(tmpDir, strings.Replace(digest, "sha256:", "", -1)+".tar")
// 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")
layerFile, err := os.Create(layerPath) layerFile, err := os.Create(layerPath)
if err != nil { if err != nil {
return err return err
} }
if _, err := io.Copy(layerFile, res.Body); err != nil { if _, err := io.Copy(layerFile, stream); err != nil {
return err return err
} }
if err := layerFile.Sync(); err != nil { if err := layerFile.Sync(); err != nil {
@ -431,7 +456,8 @@ func (i *dockerImage) getLayer(l, url, tmpDir string) error {
return nil 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) ref, err := reference.ParseNamed(img)
if err != nil { if err != nil {
return nil, err return nil, err
@ -472,7 +498,7 @@ func parseDockerImage(img, certPath string, tlsVerify bool) (types.Image, error)
TLSClientConfig: tlsc, TLSClientConfig: tlsc,
} }
} }
return &dockerImage{ return &dockerImageSource{
ref: ref, ref: ref,
tag: tag, tag: tag,
registry: registry, registry: registry,
@ -482,6 +508,19 @@ func parseDockerImage(img, certPath string, tlsVerify bool) (types.Image, error)
}, nil }, 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 { func getDefaultConfigDir(confPath string) string {
return filepath.Join(homedir.Get(), confPath) return filepath.Join(homedir.Get(), confPath)
} }
@ -576,13 +615,13 @@ func (pr *pingResponse) needsAuth() bool {
return pr.WWWAuthenticate != "" return pr.WWWAuthenticate != ""
} }
func (i *dockerImage) ping() (*pingResponse, error) { func (s *dockerImageSource) ping() (*pingResponse, error) {
client := &http.Client{} client := &http.Client{}
if i.transport != nil { if s.transport != nil {
client.Transport = i.transport client.Transport = s.transport
} }
ping := func(scheme string) (*pingResponse, error) { 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) resp, err := client.Get(url)
if err != nil { if err != nil {
return nil, err return nil, err