From 125f4ff7d7fbb5298e4ddf18db177d6583571cfa Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 8 Jun 2016 17:02:29 -0700 Subject: [PATCH] Add option to get content digest from manifest get The client may need the content digest to delete a manifest using the digest used by the registry. Signed-off-by: Derek McGowan (github: dmcgowan) --- registry/client/repository.go | 23 +++++++++++++++++++++++ registry/client/repository_test.go | 29 ++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/registry/client/repository.go b/registry/client/repository.go index 8cc5f7f9..323ab508 100644 --- a/registry/client/repository.go +++ b/registry/client/repository.go @@ -394,11 +394,26 @@ func (o etagOption) Apply(ms distribution.ManifestService) error { return fmt.Errorf("etag options is a client-only option") } +// ReturnContentDigest allows a client to set a the content digest on +// a successful request from the 'Docker-Content-Digest' header. This +// returned digest is represents the digest which the registry uses +// to refer to the content and can be used to delete the content. +func ReturnContentDigest(dgst *digest.Digest) distribution.ManifestServiceOption { + return contentDigestOption{dgst} +} + +type contentDigestOption struct{ digest *digest.Digest } + +func (o contentDigestOption) Apply(ms distribution.ManifestService) error { + return nil +} + func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) { var ( digestOrTag string ref reference.Named err error + contentDgst *digest.Digest ) for _, option := range options { @@ -408,6 +423,8 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis if err != nil { return nil, err } + } else if opt, ok := option.(contentDigestOption); ok { + contentDgst = opt.digest } else { err := option.Apply(ms) if err != nil { @@ -450,6 +467,12 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis if resp.StatusCode == http.StatusNotModified { return nil, distribution.ErrManifestNotModified } else if SuccessStatus(resp.StatusCode) { + if contentDgst != nil { + dgst, err := digest.ParseDigest(resp.Header.Get("Docker-Content-Digest")) + if err == nil { + *contentDgst = dgst + } + } mt := resp.Header.Get("Content-Type") body, err := ioutil.ReadAll(resp.Body) diff --git a/registry/client/repository_test.go b/registry/client/repository_test.go index 2faeb276..19b6ca2c 100644 --- a/registry/client/repository_test.go +++ b/registry/client/repository_test.go @@ -605,6 +605,14 @@ func addTestManifestWithEtag(repo reference.Named, reference string, content []b *m = append(*m, testutil.RequestResponseMapping{Request: getReqWithEtag, Response: getRespWithEtag}) } +func contentDigestString(mediatype string, content []byte) string { + if mediatype == schema1.MediaTypeSignedManifest { + m, _, _ := distribution.UnmarshalManifest(mediatype, content) + content = m.(*schema1.SignedManifest).Canonical + } + return digest.Canonical.FromBytes(content).String() +} + func addTestManifest(repo reference.Named, reference string, mediatype string, content []byte, m *testutil.RequestResponseMap) { *m = append(*m, testutil.RequestResponseMapping{ Request: testutil.Request{ @@ -615,9 +623,10 @@ func addTestManifest(repo reference.Named, reference string, mediatype string, c StatusCode: http.StatusOK, Body: content, Headers: http.Header(map[string][]string{ - "Content-Length": {fmt.Sprint(len(content))}, - "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, - "Content-Type": {mediatype}, + "Content-Length": {fmt.Sprint(len(content))}, + "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, + "Content-Type": {mediatype}, + "Docker-Content-Digest": {contentDigestString(mediatype, content)}, }), }, }) @@ -629,9 +638,10 @@ func addTestManifest(repo reference.Named, reference string, mediatype string, c Response: testutil.Response{ StatusCode: http.StatusOK, Headers: http.Header(map[string][]string{ - "Content-Length": {fmt.Sprint(len(content))}, - "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, - "Content-Type": {mediatype}, + "Content-Length": {fmt.Sprint(len(content))}, + "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, + "Content-Type": {mediatype}, + "Docker-Content-Digest": {digest.Canonical.FromBytes(content).String()}, }), }, }) @@ -710,7 +720,8 @@ func TestV1ManifestFetch(t *testing.T) { t.Fatal(err) } - manifest, err = ms.Get(ctx, dgst, distribution.WithTag("latest")) + var contentDigest digest.Digest + manifest, err = ms.Get(ctx, dgst, distribution.WithTag("latest"), ReturnContentDigest(&contentDigest)) if err != nil { t.Fatal(err) } @@ -723,6 +734,10 @@ func TestV1ManifestFetch(t *testing.T) { t.Fatal(err) } + if contentDigest != dgst { + t.Fatalf("Unexpected returned content digest %v, expected %v", contentDigest, dgst) + } + manifest, err = ms.Get(ctx, dgst, distribution.WithTag("badcontenttype")) if err != nil { t.Fatal(err)