134 lines
4.1 KiB
Go
134 lines
4.1 KiB
Go
|
package docker
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/Sirupsen/logrus"
|
||
|
"github.com/containers/image/manifest"
|
||
|
"github.com/containers/image/types"
|
||
|
)
|
||
|
|
||
|
type dockerImageDestination struct {
|
||
|
ref dockerReference
|
||
|
c *dockerClient
|
||
|
}
|
||
|
|
||
|
// newImageDestination creates a new ImageDestination for the specified image reference and connection specification.
|
||
|
func newImageDestination(ref dockerReference, certPath string, tlsVerify bool) (types.ImageDestination, error) {
|
||
|
c, err := newDockerClient(ref.ref.Hostname(), certPath, tlsVerify)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &dockerImageDestination{
|
||
|
ref: ref,
|
||
|
c: c,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||
|
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
|
||
|
func (d *dockerImageDestination) Reference() types.ImageReference {
|
||
|
return d.ref
|
||
|
}
|
||
|
|
||
|
func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
|
||
|
return []string{
|
||
|
// TODO(runcom): we'll add OCI as part of another PR here
|
||
|
manifest.DockerV2Schema2MIMEType,
|
||
|
manifest.DockerV2Schema1SignedMIMEType,
|
||
|
manifest.DockerV2Schema1MIMEType,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *dockerImageDestination) PutManifest(m []byte) error {
|
||
|
// FIXME: This only allows upload by digest, not creating a tag. See the
|
||
|
// corresponding comment in openshift.NewImageDestination.
|
||
|
digest, err := manifest.Digest(m)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), digest)
|
||
|
|
||
|
headers := map[string][]string{}
|
||
|
mimeType := manifest.GuessMIMEType(m)
|
||
|
if mimeType != "" {
|
||
|
headers["Content-Type"] = []string{mimeType}
|
||
|
}
|
||
|
res, err := d.c.makeRequest("PUT", url, headers, bytes.NewReader(m))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer res.Body.Close()
|
||
|
if res.StatusCode != http.StatusCreated {
|
||
|
body, err := ioutil.ReadAll(res.Body)
|
||
|
if err == nil {
|
||
|
logrus.Debugf("Error body %s", string(body))
|
||
|
}
|
||
|
logrus.Debugf("Error uploading manifest, status %d, %#v", res.StatusCode, res)
|
||
|
return fmt.Errorf("Error uploading manifest to %s, status %d", url, res.StatusCode)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (d *dockerImageDestination) PutBlob(digest string, stream io.Reader) error {
|
||
|
checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), digest)
|
||
|
|
||
|
logrus.Debugf("Checking %s", checkURL)
|
||
|
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer res.Body.Close()
|
||
|
if res.StatusCode == http.StatusOK && res.Header.Get("Docker-Content-Digest") == digest {
|
||
|
logrus.Debugf("... already exists, not uploading")
|
||
|
return nil
|
||
|
}
|
||
|
logrus.Debugf("... failed, status %d", res.StatusCode)
|
||
|
|
||
|
// FIXME? Chunked upload, progress reporting, etc.
|
||
|
uploadURL := fmt.Sprintf(blobUploadURL, d.ref.ref.RemoteName())
|
||
|
logrus.Debugf("Uploading %s", uploadURL)
|
||
|
res, err = d.c.makeRequest("POST", uploadURL, nil, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer res.Body.Close()
|
||
|
if res.StatusCode != http.StatusAccepted {
|
||
|
logrus.Debugf("Error initiating layer upload, response %#v", *res)
|
||
|
return fmt.Errorf("Error initiating layer upload to %s, status %d", uploadURL, res.StatusCode)
|
||
|
}
|
||
|
uploadLocation, err := res.Location()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Error determining upload URL: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
// FIXME: DELETE uploadLocation on failure
|
||
|
|
||
|
locationQuery := uploadLocation.Query()
|
||
|
locationQuery.Set("digest", digest)
|
||
|
uploadLocation.RawQuery = locationQuery.Encode()
|
||
|
res, err = d.c.makeRequestToResolvedURL("PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, stream)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer res.Body.Close()
|
||
|
if res.StatusCode != http.StatusCreated {
|
||
|
logrus.Debugf("Error uploading layer, response %#v", *res)
|
||
|
return fmt.Errorf("Error uploading layer to %s, status %d", uploadLocation, res.StatusCode)
|
||
|
}
|
||
|
|
||
|
logrus.Debugf("Upload of layer %s complete", digest)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
||
|
if len(signatures) != 0 {
|
||
|
return fmt.Errorf("Pushing signatures to a Docker Registry is not supported")
|
||
|
}
|
||
|
return nil
|
||
|
}
|