From 3f7c62f6f6ddca1cd2f875cf0924549d9c5a4827 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 28 Oct 2020 13:46:00 +0100 Subject: [PATCH] registry: remove v1 code not related to searching Signed-off-by: Sebastiaan van Stijn --- registry/errors.go | 8 - registry/registry_test.go | 204 +------------- registry/session.go | 563 +------------------------------------- 3 files changed, 5 insertions(+), 770 deletions(-) diff --git a/registry/errors.go b/registry/errors.go index 5bab02e5e2..4906303efc 100644 --- a/registry/errors.go +++ b/registry/errors.go @@ -7,14 +7,6 @@ import ( "github.com/docker/docker/errdefs" ) -type notFoundError string - -func (e notFoundError) Error() string { - return string(e) -} - -func (notFoundError) NotFound() {} - func translateV2AuthError(err error) error { switch e := err.(type) { case *url.Error: diff --git a/registry/registry_test.go b/registry/registry_test.go index d0659c17fc..417c9574bc 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -1,10 +1,8 @@ package registry // import "github.com/docker/docker/registry" import ( - "fmt" "net/http" "net/http/httputil" - "net/url" "os" "strings" "testing" @@ -17,15 +15,6 @@ import ( "gotest.tools/v3/skip" ) -var ( - token = []string{"fake-token"} -) - -const ( - imageID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d" - REPO = "foo42/bar" -) - func spawnTestRegistrySession(t *testing.T) *Session { authConfig := &types.AuthConfig{} endpoint, err := NewV1Endpoint(makeIndex("/v1/"), "", nil) @@ -50,7 +39,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { // Because we know that the client's transport is an `*authTransport` we simply cast it, // in order to set the internal cached token to the fake token, and thus send that fake token // upon every subsequent requests. - r.client.Transport.(*authTransport).token = token + r.client.Transport.(*authTransport).token = []string{"fake-token"} return r } @@ -148,153 +137,6 @@ func TestEndpoint(t *testing.T) { } } -func TestGetRemoteHistory(t *testing.T) { - r := spawnTestRegistrySession(t) - hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } - assertEqual(t, len(hist), 2, "Expected 2 images in history") - assertEqual(t, hist[0], imageID, "Expected "+imageID+"as first ancestry") - assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - "Unexpected second ancestry") -} - -func TestLookupRemoteImage(t *testing.T) { - r := spawnTestRegistrySession(t) - err := r.LookupRemoteImage(imageID, makeURL("/v1/")) - assertEqual(t, err, nil, "Expected error of remote lookup to nil") - if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil { - t.Fatal("Expected error of remote lookup to not nil") - } -} - -func TestGetRemoteImageJSON(t *testing.T) { - r := spawnTestRegistrySession(t) - json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } - assertEqual(t, size, int64(154), "Expected size 154") - if len(json) == 0 { - t.Fatal("Expected non-empty json") - } - - _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/")) - if err == nil { - t.Fatal("Expected image not found error") - } -} - -func TestGetRemoteImageLayer(t *testing.T) { - r := spawnTestRegistrySession(t) - data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0) - if err != nil { - t.Fatal(err) - } - if data == nil { - t.Fatal("Expected non-nil data result") - } - - _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0) - if err == nil { - t.Fatal("Expected image not found error") - } -} - -func TestGetRemoteTag(t *testing.T) { - r := spawnTestRegistrySession(t) - repoRef, err := reference.ParseNormalizedNamed(REPO) - if err != nil { - t.Fatal(err) - } - tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, repoRef, "test") - if err != nil { - t.Fatal(err) - } - assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID) - - bazRef, err := reference.ParseNormalizedNamed("foo42/baz") - if err != nil { - t.Fatal(err) - } - _, err = r.GetRemoteTag([]string{makeURL("/v1/")}, bazRef, "foo") - if err != ErrRepoNotFound { - t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo") - } -} - -func TestGetRemoteTags(t *testing.T) { - r := spawnTestRegistrySession(t) - repoRef, err := reference.ParseNormalizedNamed(REPO) - if err != nil { - t.Fatal(err) - } - tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, repoRef) - if err != nil { - t.Fatal(err) - } - assertEqual(t, len(tags), 2, "Expected two tags") - assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID) - assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID) - - bazRef, err := reference.ParseNormalizedNamed("foo42/baz") - if err != nil { - t.Fatal(err) - } - _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, bazRef) - if err != ErrRepoNotFound { - t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo") - } -} - -func TestGetRepositoryData(t *testing.T) { - r := spawnTestRegistrySession(t) - parsedURL, err := url.Parse(makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } - host := "http://" + parsedURL.Host + "/v1/" - repoRef, err := reference.ParseNormalizedNamed(REPO) - if err != nil { - t.Fatal(err) - } - data, err := r.GetRepositoryData(repoRef) - if err != nil { - t.Fatal(err) - } - assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList") - assertEqual(t, len(data.Endpoints), 2, - fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints))) - assertEqual(t, data.Endpoints[0], host, - fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0])) - assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/", - fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1])) - -} - -func TestPushImageJSONRegistry(t *testing.T) { - r := spawnTestRegistrySession(t) - imgData := &ImgData{ - ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", - } - - err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } -} - -func TestPushImageLayerRegistry(t *testing.T) { - r := spawnTestRegistrySession(t) - layer := strings.NewReader("") - _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{}) - if err != nil { - t.Fatal(err) - } -} - func TestParseRepositoryInfo(t *testing.T) { type staticRepositoryInfo struct { Index *registrytypes.IndexInfo @@ -701,50 +543,6 @@ func TestMirrorEndpointLookup(t *testing.T) { } } -func TestPushRegistryTag(t *testing.T) { - r := spawnTestRegistrySession(t) - repoRef, err := reference.ParseNormalizedNamed(REPO) - if err != nil { - t.Fatal(err) - } - err = r.PushRegistryTag(repoRef, imageID, "stable", makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } -} - -func TestPushImageJSONIndex(t *testing.T) { - r := spawnTestRegistrySession(t) - imgData := []*ImgData{ - { - ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", - }, - { - ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", - Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", - }, - } - repoRef, err := reference.ParseNormalizedNamed(REPO) - if err != nil { - t.Fatal(err) - } - repoData, err := r.PushImageJSONIndex(repoRef, imgData, false, nil) - if err != nil { - t.Fatal(err) - } - if repoData == nil { - t.Fatal("Expected RepositoryData object") - } - repoData, err = r.PushImageJSONIndex(repoRef, imgData, true, []string{r.indexEndpoint.String()}) - if err != nil { - t.Fatal(err) - } - if repoData == nil { - t.Fatal("Expected RepositoryData object") - } -} - func TestSearchRepositories(t *testing.T) { r := spawnTestRegistrySession(t) results, err := r.SearchRepositories("fakequery", 25) diff --git a/registry/session.go b/registry/session.go index 14825c5f9a..d34dc1e58a 100644 --- a/registry/session.go +++ b/registry/session.go @@ -1,43 +1,26 @@ package registry // import "github.com/docker/docker/registry" import ( - "bytes" - "crypto/sha256" - // this is required for some certificates _ "crypto/sha512" - "encoding/hex" "encoding/json" "fmt" - "io" - "io/ioutil" "net/http" "net/http/cookiejar" "net/url" - "strconv" "strings" "sync" - "github.com/docker/distribution/reference" - "github.com/docker/distribution/registry/api/errcode" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/registry/resumable" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -var ( - // ErrRepoNotFound is returned if the repository didn't exist on the - // remote side - ErrRepoNotFound notFoundError = "Repository not found" -) - // A Session is used to communicate with a V1 registry type Session struct { indexEndpoint *V1Endpoint @@ -214,527 +197,6 @@ func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1E return newSession(client, authConfig, endpoint), nil } -// ID returns this registry session's ID. -func (r *Session) ID() string { - return r.id -} - -// GetRemoteHistory retrieves the history of a given image from the registry. -// It returns a list of the parent's JSON files (including the requested image). -func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) { - res, err := r.client.Get(registry + "images/" + imgID + "/ancestry") - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - if res.StatusCode == http.StatusUnauthorized { - return nil, errcode.ErrorCodeUnauthorized.WithArgs() - } - return nil, newJSONError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) - } - - var history []string - if err := json.NewDecoder(res.Body).Decode(&history); err != nil { - return nil, fmt.Errorf("Error while reading the http response: %v", err) - } - - logrus.Debugf("Ancestry: %v", history) - return history, nil -} - -// LookupRemoteImage checks if an image exists in the registry -func (r *Session) LookupRemoteImage(imgID, registry string) error { - res, err := r.client.Get(registry + "images/" + imgID + "/json") - if err != nil { - return err - } - res.Body.Close() - if res.StatusCode != http.StatusOK { - return newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) - } - return nil -} - -// GetRemoteImageJSON retrieves an image's JSON metadata from the registry. -func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int64, error) { - res, err := r.client.Get(registry + "images/" + imgID + "/json") - if err != nil { - return nil, -1, fmt.Errorf("Failed to download json: %s", err) - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, -1, newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) - } - // if the size header is not present, then set it to '-1' - imageSize := int64(-1) - if hdr := res.Header.Get("X-Docker-Size"); hdr != "" { - imageSize, err = strconv.ParseInt(hdr, 10, 64) - if err != nil { - return nil, -1, err - } - } - - jsonString, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString) - } - return jsonString, imageSize, nil -} - -// GetRemoteImageLayer retrieves an image layer from the registry -func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) { - var ( - statusCode = 0 - res *http.Response - err error - imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID) - ) - - req, err := http.NewRequest(http.MethodGet, imageURL, nil) - if err != nil { - return nil, fmt.Errorf("Error while getting from the server: %v", err) - } - - res, err = r.client.Do(req) - if err != nil { - logrus.Debugf("Error contacting registry %s: %v", registry, err) - // the only case err != nil && res != nil is https://golang.org/src/net/http/client.go#L515 - if res != nil { - if res.Body != nil { - res.Body.Close() - } - statusCode = res.StatusCode - } - return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", - statusCode, imgID) - } - - if res.StatusCode != http.StatusOK { - res.Body.Close() - return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", - res.StatusCode, imgID) - } - - if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { - logrus.Debug("server supports resume") - return resumable.NewRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil - } - logrus.Debug("server doesn't support resume") - return res.Body, nil -} - -// GetRemoteTag retrieves the tag named in the askedTag argument from the given -// repository. It queries each of the registries supplied in the registries -// argument, and returns data from the first one that answers the query -// successfully. -func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Named, askedTag string) (string, error) { - repository := reference.Path(repositoryRef) - - if strings.Count(repository, "/") == 0 { - // This will be removed once the registry supports auto-resolution on - // the "library" namespace - repository = "library/" + repository - } - for _, host := range registries { - endpoint := fmt.Sprintf("%srepositories/%s/tags/%s", host, repository, askedTag) - res, err := r.client.Get(endpoint) - if err != nil { - return "", err - } - - logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) - defer res.Body.Close() - - if res.StatusCode == 404 { - return "", ErrRepoNotFound - } - if res.StatusCode != http.StatusOK { - continue - } - - var tagID string - if err := json.NewDecoder(res.Body).Decode(&tagID); err != nil { - return "", err - } - return tagID, nil - } - return "", fmt.Errorf("Could not reach any registry endpoint") -} - -// GetRemoteTags retrieves all tags from the given repository. It queries each -// of the registries supplied in the registries argument, and returns data from -// the first one that answers the query successfully. It returns a map with -// tag names as the keys and image IDs as the values. -func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) { - repository := reference.Path(repositoryRef) - - if strings.Count(repository, "/") == 0 { - // This will be removed once the registry supports auto-resolution on - // the "library" namespace - repository = "library/" + repository - } - for _, host := range registries { - endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) - res, err := r.client.Get(endpoint) - if err != nil { - return nil, err - } - - logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) - defer res.Body.Close() - - if res.StatusCode == 404 { - return nil, ErrRepoNotFound - } - if res.StatusCode != http.StatusOK { - continue - } - - result := make(map[string]string) - if err := json.NewDecoder(res.Body).Decode(&result); err != nil { - return nil, err - } - return result, nil - } - return nil, fmt.Errorf("Could not reach any registry endpoint") -} - -func buildEndpointsList(headers []string, indexEp string) ([]string, error) { - var endpoints []string - parsedURL, err := url.Parse(indexEp) - if err != nil { - return nil, err - } - var urlScheme = parsedURL.Scheme - // The registry's URL scheme has to match the Index' - for _, ep := range headers { - epList := strings.Split(ep, ",") - for _, epListElement := range epList { - endpoints = append( - endpoints, - fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement))) - } - } - return endpoints, nil -} - -// GetRepositoryData returns lists of images and endpoints for the repository -func (r *Session) GetRepositoryData(name reference.Named) (*RepositoryData, error) { - repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.String(), reference.Path(name)) - - logrus.Debugf("[registry] Calling GET %s", repositoryTarget) - - req, err := http.NewRequest(http.MethodGet, repositoryTarget, nil) - if err != nil { - return nil, err - } - // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests - req.Header.Set("X-Docker-Token", "true") - res, err := r.client.Do(req) - if err != nil { - // check if the error is because of i/o timeout - // and return a non-obtuse error message for users - // "Get https://index.docker.io/v1/repositories/library/busybox/images: i/o timeout" - // was a top search on the docker user forum - if isTimeout(err) { - return nil, fmt.Errorf("network timed out while trying to connect to %s. You may want to check your internet connection or if you are behind a proxy", repositoryTarget) - } - return nil, fmt.Errorf("Error while pulling image: %v", err) - } - defer res.Body.Close() - if res.StatusCode == http.StatusUnauthorized { - return nil, errcode.ErrorCodeUnauthorized.WithArgs() - } - // TODO: Right now we're ignoring checksums in the response body. - // In the future, we need to use them to check image validity. - if res.StatusCode == 404 { - return nil, newJSONError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) - } else if res.StatusCode != http.StatusOK { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - logrus.Debugf("Error reading response body: %s", err) - } - return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, reference.Path(name), errBody), res) - } - - var endpoints []string - if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String()) - if err != nil { - return nil, err - } - } else { - // Assume the endpoint is on the same host - endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", r.indexEndpoint.URL.Scheme, req.URL.Host)) - } - - remoteChecksums := []*ImgData{} - if err := json.NewDecoder(res.Body).Decode(&remoteChecksums); err != nil { - return nil, err - } - - // Forge a better object from the retrieved data - imgsData := make(map[string]*ImgData, len(remoteChecksums)) - for _, elem := range remoteChecksums { - imgsData[elem.ID] = elem - } - - return &RepositoryData{ - ImgList: imgsData, - Endpoints: endpoints, - }, nil -} - -// PushImageChecksumRegistry uploads checksums for an image -func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error { - u := registry + "images/" + imgData.ID + "/checksum" - - logrus.Debugf("[registry] Calling PUT %s", u) - - req, err := http.NewRequest(http.MethodPut, u, nil) - if err != nil { - return err - } - req.Header.Set("X-Docker-Checksum", imgData.Checksum) - req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload) - - res, err := r.client.Do(req) - if err != nil { - return fmt.Errorf("Failed to upload metadata: %v", err) - } - defer res.Body.Close() - if len(res.Cookies()) > 0 { - r.client.Jar.SetCookies(req.URL, res.Cookies()) - } - if res.StatusCode != http.StatusOK { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) - } - var jsonBody map[string]string - if err := json.Unmarshal(errBody, &jsonBody); err != nil { - errBody = []byte(err.Error()) - } else if jsonBody["error"] == "Image already exists" { - return ErrAlreadyExists - } - return fmt.Errorf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody) - } - return nil -} - -// PushImageJSONRegistry pushes JSON metadata for a local image to the registry -func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error { - - u := registry + "images/" + imgData.ID + "/json" - - logrus.Debugf("[registry] Calling PUT %s", u) - - req, err := http.NewRequest(http.MethodPut, u, bytes.NewReader(jsonRaw)) - if err != nil { - return err - } - req.Header.Add("Content-type", "application/json") - - res, err := r.client.Do(req) - if err != nil { - return fmt.Errorf("Failed to upload metadata: %s", err) - } - defer res.Body.Close() - if res.StatusCode == http.StatusUnauthorized && strings.HasPrefix(registry, "http://") { - return newJSONError("HTTP code 401, Docker will not send auth headers over HTTP.", res) - } - if res.StatusCode != http.StatusOK { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) - } - var jsonBody map[string]string - if err := json.Unmarshal(errBody, &jsonBody); err != nil { - errBody = []byte(err.Error()) - } else if jsonBody["error"] == "Image already exists" { - return ErrAlreadyExists - } - return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) - } - return nil -} - -// PushImageLayerRegistry sends the checksum of an image layer to the registry -func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) { - u := registry + "images/" + imgID + "/layer" - - logrus.Debugf("[registry] Calling PUT %s", u) - - tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0) - if err != nil { - return "", "", err - } - h := sha256.New() - h.Write(jsonRaw) - h.Write([]byte{'\n'}) - checksumLayer := io.TeeReader(tarsumLayer, h) - - req, err := http.NewRequest(http.MethodPut, u, checksumLayer) - if err != nil { - return "", "", err - } - req.Header.Add("Content-Type", "application/octet-stream") - req.ContentLength = -1 - req.TransferEncoding = []string{"chunked"} - res, err := r.client.Do(req) - if err != nil { - return "", "", fmt.Errorf("Failed to upload layer: %v", err) - } - if rc, ok := layer.(io.Closer); ok { - if err := rc.Close(); err != nil { - return "", "", err - } - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", "", newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) - } - return "", "", newJSONError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) - } - - checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) - return tarsumLayer.Sum(jsonRaw), checksumPayload, nil -} - -// PushRegistryTag pushes a tag on the registry. -// Remote has the format '/ -func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error { - // "jsonify" the string - revision = "\"" + revision + "\"" - path := fmt.Sprintf("repositories/%s/tags/%s", reference.Path(remote), tag) - - req, err := http.NewRequest(http.MethodPut, registry+path, strings.NewReader(revision)) - if err != nil { - return err - } - req.Header.Add("Content-type", "application/json") - req.ContentLength = int64(len(revision)) - res, err := r.client.Do(req) - if err != nil { - return err - } - res.Body.Close() - if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { - return newJSONError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, reference.Path(remote)), res) - } - return nil -} - -// PushImageJSONIndex uploads an image list to the repository -func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { - cleanImgList := []*ImgData{} - if validate { - for _, elem := range imgList { - if elem.Checksum != "" { - cleanImgList = append(cleanImgList, elem) - } - } - } else { - cleanImgList = imgList - } - - imgListJSON, err := json.Marshal(cleanImgList) - if err != nil { - return nil, err - } - var suffix string - if validate { - suffix = "images" - } - u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.String(), reference.Path(remote), suffix) - logrus.Debugf("[registry] PUT %s", u) - logrus.Debugf("Image list pushed to index:\n%s", imgListJSON) - headers := map[string][]string{ - "Content-type": {"application/json"}, - // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests - "X-Docker-Token": {"true"}, - } - if validate { - headers["X-Docker-Endpoints"] = regs - } - - // Redirect if necessary - var res *http.Response - for { - if res, err = r.putImageRequest(u, headers, imgListJSON); err != nil { - return nil, err - } - if !shouldRedirect(res) { - break - } - res.Body.Close() - u = res.Header.Get("Location") - logrus.Debugf("Redirected to %s", u) - } - defer res.Body.Close() - - if res.StatusCode == http.StatusUnauthorized { - return nil, errcode.ErrorCodeUnauthorized.WithArgs() - } - - var tokens, endpoints []string - if !validate { - if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - logrus.Debugf("Error reading response body: %s", err) - } - return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, reference.Path(remote), errBody), res) - } - tokens = res.Header["X-Docker-Token"] - logrus.Debugf("Auth token: %v", tokens) - - if res.Header.Get("X-Docker-Endpoints") == "" { - return nil, fmt.Errorf("Index response didn't contain any endpoints") - } - endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String()) - if err != nil { - return nil, err - } - } else { - if res.StatusCode != http.StatusNoContent { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - logrus.Debugf("Error reading response body: %s", err) - } - return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, reference.Path(remote), errBody), res) - } - } - - return &RepositoryData{ - Endpoints: endpoints, - }, nil -} - -func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) { - req, err := http.NewRequest(http.MethodPut, u, bytes.NewReader(body)) - if err != nil { - return nil, err - } - req.ContentLength = int64(len(body)) - for k, v := range headers { - req.Header[k] = v - } - response, err := r.client.Do(req) - if err != nil { - return nil, err - } - return response, nil -} - -func shouldRedirect(response *http.Response) bool { - return response.StatusCode >= 300 && response.StatusCode < 400 -} - // SearchRepositories performs a search against the remote repository func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) { if limit < 1 || limit > 100 { @@ -755,28 +217,11 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return nil, newJSONError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) + return nil, &jsonmessage.JSONError{ + Message: fmt.Sprintf("Unexpected status code %d", res.StatusCode), + Code: res.StatusCode, + } } result := new(registrytypes.SearchResults) return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results") } - -func isTimeout(err error) bool { - type timeout interface { - Timeout() bool - } - e := err - switch urlErr := err.(type) { - case *url.Error: - e = urlErr.Err - } - t, ok := e.(timeout) - return ok && t.Timeout() -} - -func newJSONError(msg string, res *http.Response) error { - return &jsonmessage.JSONError{ - Message: msg, - Code: res.StatusCode, - } -}