From 3800c47fd21fa2df9fbcc2ca9e55d7870711bad4 Mon Sep 17 00:00:00 2001 From: Damien Mathieu Date: Fri, 10 May 2019 12:07:50 +0200 Subject: [PATCH 1/2] Implement Repository ServeBlob Signed-off-by: Damien Mathieu --- registry/client/repository.go | 19 ++++++++++- registry/client/repository_test.go | 53 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/registry/client/repository.go b/registry/client/repository.go index aa442e654..8611f0fe1 100644 --- a/registry/client/repository.go +++ b/registry/client/repository.go @@ -667,7 +667,24 @@ func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.Rea } func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { - panic("not implemented") + desc, err := bs.statter.Stat(ctx, dgst) + if err != nil { + return err + } + + blob, err := bs.Open(ctx, dgst) + if err != nil { + return err + } + defer blob.Close() + + w.Header().Set("Content-Length", strconv.FormatInt(desc.Size, 10)) + w.Header().Set("Content-Type", desc.MediaType) + w.Header().Set("Docker-Content-Digest", dgst.String()) + w.Header().Set("Etag", dgst.String()) + + _, err = io.CopyN(w, blob, desc.Size) + return err } func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { diff --git a/registry/client/repository_test.go b/registry/client/repository_test.go index 746821b06..0622c6337 100644 --- a/registry/client/repository_test.go +++ b/registry/client/repository_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "log" "net/http" "net/http/httptest" @@ -57,6 +58,7 @@ func addTestFetch(repo string, dgst digest.Digest, content []byte, m *testutil.R Body: content, Headers: http.Header(map[string][]string{ "Content-Length": {fmt.Sprint(len(content))}, + "Content-Type": {"application/octet-stream"}, "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, }), }, @@ -71,6 +73,7 @@ func addTestFetch(repo string, dgst digest.Digest, content []byte, m *testutil.R StatusCode: http.StatusOK, Headers: http.Header(map[string][]string{ "Content-Length": {fmt.Sprint(len(content))}, + "Content-Type": {"application/octet-stream"}, "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, }), }, @@ -99,6 +102,56 @@ func addTestCatalog(route string, content []byte, link string, m *testutil.Reque }) } +func TestBlobServeBlob(t *testing.T) { + dgst, blob := newRandomBlob(1024) + var m testutil.RequestResponseMap + addTestFetch("test.example.com/repo1", dgst, blob, &m) + + e, c := testServer(m) + defer c() + + ctx := context.Background() + repo, _ := reference.WithName("test.example.com/repo1") + r, err := NewRepository(repo, e, nil) + if err != nil { + t.Fatal(err) + } + l := r.Blobs(ctx) + + resp := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/", nil) + + err = l.ServeBlob(ctx, resp, req, dgst) + if err != nil { + t.Errorf("Error serving blob: %s", err.Error()) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading response body: %s", err.Error()) + } + if string(body) != string(blob) { + t.Errorf("Unexpected response body. Got %q, expected %q", string(body), string(blob)) + } + + expectedHeaders := []struct { + Name string + Value string + }{ + {Name: "Content-Length", Value: "1024"}, + {Name: "Content-Type", Value: "application/octet-stream"}, + {Name: "Docker-Content-Digest", Value: dgst.String()}, + {Name: "Etag", Value: dgst.String()}, + } + + for _, h := range expectedHeaders { + if resp.Header().Get(h.Name) != h.Value { + t.Errorf("Unexpected %s. Got %s, expected %s", h.Name, resp.Header().Get(h.Name), h.Value) + } + } + +} + func TestBlobDelete(t *testing.T) { dgst, _ := newRandomBlob(1024) var m testutil.RequestResponseMap From c5d5f938e3b069dbf3487e64c9d567bf9910f23b Mon Sep 17 00:00:00 2001 From: Damien Mathieu Date: Fri, 10 May 2019 14:08:31 +0200 Subject: [PATCH 2/2] fast-stop ServeBlob if we're doing a HEAD request A registry pointing to ECR is having issues if we try loading the blob Signed-off-by: Damien Mathieu --- registry/client/repository.go | 14 +++++---- registry/client/repository_test.go | 48 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/registry/client/repository.go b/registry/client/repository.go index 8611f0fe1..7a1edf783 100644 --- a/registry/client/repository.go +++ b/registry/client/repository.go @@ -672,17 +672,21 @@ func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.R return err } + w.Header().Set("Content-Length", strconv.FormatInt(desc.Size, 10)) + w.Header().Set("Content-Type", desc.MediaType) + w.Header().Set("Docker-Content-Digest", dgst.String()) + w.Header().Set("Etag", dgst.String()) + + if r.Method == http.MethodHead { + return nil + } + blob, err := bs.Open(ctx, dgst) if err != nil { return err } defer blob.Close() - w.Header().Set("Content-Length", strconv.FormatInt(desc.Size, 10)) - w.Header().Set("Content-Type", desc.MediaType) - w.Header().Set("Docker-Content-Digest", dgst.String()) - w.Header().Set("Etag", dgst.String()) - _, err = io.CopyN(w, blob, desc.Size) return err } diff --git a/registry/client/repository_test.go b/registry/client/repository_test.go index 0622c6337..51dbb4d18 100644 --- a/registry/client/repository_test.go +++ b/registry/client/repository_test.go @@ -149,7 +149,55 @@ func TestBlobServeBlob(t *testing.T) { t.Errorf("Unexpected %s. Got %s, expected %s", h.Name, resp.Header().Get(h.Name), h.Value) } } +} +func TestBlobServeBlobHEAD(t *testing.T) { + dgst, blob := newRandomBlob(1024) + var m testutil.RequestResponseMap + addTestFetch("test.example.com/repo1", dgst, blob, &m) + + e, c := testServer(m) + defer c() + + ctx := context.Background() + repo, _ := reference.WithName("test.example.com/repo1") + r, err := NewRepository(repo, e, nil) + if err != nil { + t.Fatal(err) + } + l := r.Blobs(ctx) + + resp := httptest.NewRecorder() + req := httptest.NewRequest("HEAD", "/", nil) + + err = l.ServeBlob(ctx, resp, req, dgst) + if err != nil { + t.Errorf("Error serving blob: %s", err.Error()) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading response body: %s", err.Error()) + } + if string(body) != "" { + t.Errorf("Unexpected response body. Got %q, expected %q", string(body), "") + } + + expectedHeaders := []struct { + Name string + Value string + }{ + {Name: "Content-Length", Value: "1024"}, + {Name: "Content-Type", Value: "application/octet-stream"}, + {Name: "Docker-Content-Digest", Value: dgst.String()}, + {Name: "Etag", Value: dgst.String()}, + } + + for _, h := range expectedHeaders { + if resp.Header().Get(h.Name) != h.Value { + t.Errorf("Unexpected %s. Got %s, expected %s", h.Name, resp.Header().Get(h.Name), h.Value) + } + } } func TestBlobDelete(t *testing.T) {