From fc761ed74f2a28d684bcb4c908a070e697c19aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 6 Jun 2016 22:58:55 +0200 Subject: [PATCH] Fix uploading layer blobs to Docker registry Implement a client to the chunked API, instead of the nonexistent one-shot API (per https://github.com/docker/distribution/commit/2a4deee4413a4027b3024113a2c880e217097754 ). 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 . --- docker/docker_client.go | 10 +++++++++- docker/docker_image_dest.go | 29 ++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docker/docker_client.go b/docker/docker_client.go index 180feb4b..ca52ad10 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -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 diff --git a/docker/docker_image_dest.go b/docker/docker_image_dest.go index cb53d071..b354cddf 100644 --- a/docker/docker_image_dest.go +++ b/docker/docker_image_dest.go @@ -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 }