Fix uploading layer blobs to Docker registry

Implement a client to the chunked API, instead of the nonexistent
one-shot API (per
2a4deee441
).

Adds a FIXME to DELETE the pending upload on failure; the uploads are
supposed to time out so this is not immediately critical.

Fixes #64 .
This commit is contained in:
Miloslav Trmač 2016-06-06 22:58:55 +02:00
parent e66541f7d0
commit fc761ed74f
2 changed files with 33 additions and 6 deletions

View File

@ -29,7 +29,7 @@ const (
tagsURL = "%s/tags/list"
manifestURL = "%s/manifests/%s"
blobsURL = "%s/blobs/%s"
blobUploadURL = "%s/blobs/uploads/?digest=%s"
blobUploadURL = "%s/blobs/uploads/"
)
// dockerClient is configuration for dealing with a single Docker registry.
@ -78,6 +78,8 @@ func newDockerClient(refHostname, certPath string, tlsVerify bool) (*dockerClien
}, nil
}
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// url is NOT an absolute URL, but a path relative to the /v2/ top-level API path. The host name and schema is taken from the client or autodetected.
func (c *dockerClient) makeRequest(method, url string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
if c.scheme == "" {
pr, err := c.ping()
@ -89,6 +91,12 @@ func (c *dockerClient) makeRequest(method, url string, headers map[string][]stri
}
url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url
return c.makeRequestToResolvedURL(method, url, headers, stream)
}
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// makeRequest should generally be preferred.
func (c *dockerClient) makeRequestToResolvedURL(method, url string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, url, stream)
if err != nil {
return nil, err

View File

@ -86,19 +86,38 @@ func (d *dockerImageDestination) PutLayer(digest string, stream io.Reader) error
logrus.Debugf("... failed, status %d", res.StatusCode)
// FIXME? Chunked upload, progress reporting, etc.
uploadURL := fmt.Sprintf(blobUploadURL, d.ref.RemoteName(), digest)
uploadURL := fmt.Sprintf(blobUploadURL, d.ref.RemoteName())
logrus.Debugf("Uploading %s", uploadURL)
// FIXME: Set Content-Length?
res, err = d.c.makeRequest("POST", uploadURL, map[string][]string{"Content-Type": {"application/octet-stream"}}, stream)
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, status %d", res.StatusCode)
return fmt.Errorf("Error uploading to %s, status %d", uploadURL, res.StatusCode)
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
}