Remove tags referencing deleted manifests.

When a manifest is deleted by digest, look up the referenced tags in the tag
store and remove all associations.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
This commit is contained in:
Richard Scothern 2016-01-05 11:22:40 -08:00
parent a580dae06f
commit 94347c8611
4 changed files with 181 additions and 7 deletions

View file

@ -1063,6 +1063,7 @@ func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
dgst := args.dgst
signedManifest := args.signedManifest
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
// ---------------
// Delete by digest
resp, err := httpDelete(manifestDigestURL)
@ -1118,6 +1119,77 @@ func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
checkErr(t, err, "delting unknown manifest by digest")
checkResponse(t, "fetching deleted manifest", resp, http.StatusNotFound)
// --------------------
// Uupload manifest by tag
tag := signedManifest.Tag
manifestTagURL, err := env.builder.BuildManifestURL(imageName, tag)
resp = putManifest(t, "putting signed manifest by tag", manifestTagURL, signedManifest)
checkResponse(t, "putting signed manifest by tag", resp, http.StatusCreated)
checkHeaders(t, resp, http.Header{
"Location": []string{manifestDigestURL},
"Docker-Content-Digest": []string{dgst.String()},
})
tagsURL, err := env.builder.BuildTagsURL(imageName)
if err != nil {
t.Fatalf("unexpected error building tags url: %v", err)
}
// Ensure that the tag is listed.
resp, err = http.Get(tagsURL)
if err != nil {
t.Fatalf("unexpected error getting unknown tags: %v", err)
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
var tagsResponse tagsAPIResponse
if err := dec.Decode(&tagsResponse); err != nil {
t.Fatalf("unexpected error decoding error response: %v", err)
}
if tagsResponse.Name != imageName {
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
}
if len(tagsResponse.Tags) != 1 {
t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
}
if tagsResponse.Tags[0] != tag {
t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
}
// ---------------
// Delete by digest
resp, err = httpDelete(manifestDigestURL)
checkErr(t, err, "deleting manifest by digest")
checkResponse(t, "deleting manifest with tag", resp, http.StatusAccepted)
checkHeaders(t, resp, http.Header{
"Content-Length": []string{"0"},
})
// Ensure that the tag is not listed.
resp, err = http.Get(tagsURL)
if err != nil {
t.Fatalf("unexpected error getting unknown tags: %v", err)
}
defer resp.Body.Close()
dec = json.NewDecoder(resp.Body)
if err := dec.Decode(&tagsResponse); err != nil {
t.Fatalf("unexpected error decoding error response: %v", err)
}
if tagsResponse.Name != imageName {
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
}
if len(tagsResponse.Tags) != 0 {
t.Fatalf("expected 0 tags in response: %v", tagsResponse.Tags)
}
}
type testEnv struct {

View file

@ -226,5 +226,19 @@ func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *h
}
}
tagService := imh.Repository.Tags(imh)
referencedTags, err := tagService.Lookup(imh, distribution.Descriptor{Digest: imh.Digest})
if err != nil {
imh.Errors = append(imh.Errors, err)
return
}
for _, tag := range referencedTags {
if err := tagService.Untag(imh, tag); err != nil {
imh.Errors = append(imh.Errors, err)
return
}
}
w.WriteHeader(http.StatusAccepted)
}

View file

@ -116,15 +116,19 @@ func (ts *tagStore) Get(ctx context.Context, tag string) (distribution.Descripto
return distribution.Descriptor{Digest: revision}, nil
}
// delete removes the tag from repository, including the history of all
// revisions that have the specified tag.
// Untag removes the tag association
func (ts *tagStore) Untag(ctx context.Context, tag string) error {
tagPath, err := pathFor(manifestTagPathSpec{
name: ts.repository.Name(),
tag: tag,
})
if err != nil {
switch err.(type) {
case storagedriver.PathNotFoundError:
return distribution.ErrTagUnknown{Tag: tag}
case nil:
break
default:
return err
}
@ -153,7 +157,35 @@ func (ts *tagStore) linkedBlobStore(ctx context.Context, tag string) *linkedBlob
// Lookup recovers a list of tags which refer to this digest. When a manifest is deleted by
// digest, tag entries which point to it need to be recovered to avoid dangling tags.
func (ts *tagStore) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
// An efficient implementation of this will require changes to the S3 driver.
return make([]string, 0), nil
func (ts *tagStore) Lookup(ctx context.Context, desc distribution.Descriptor) ([]string, error) {
allTags, err := ts.All(ctx)
switch err.(type) {
case distribution.ErrRepositoryUnknown:
// This tag store has been initialized but not yet populated
break
case nil:
break
default:
return nil, err
}
var tags []string
for _, tag := range allTags {
tagLinkPathSpec := manifestTagCurrentPathSpec{
name: ts.repository.Name(),
tag: tag,
}
tagLinkPath, err := pathFor(tagLinkPathSpec)
tagDigest, err := ts.blobStore.readlink(ctx, tagLinkPath)
if err != nil {
return nil, err
}
if tagDigest == desc.Digest {
tags = append(tags, tag)
}
}
return tags, nil
}

View file

@ -102,7 +102,7 @@ func TestTagStoreUnTag(t *testing.T) {
}
}
func TestTagAll(t *testing.T) {
func TestTagStoreAll(t *testing.T) {
env := testTagStore(t)
tagStore := env.ts
ctx := env.ctx
@ -148,3 +148,59 @@ func TestTagAll(t *testing.T) {
}
}
func TestTagLookup(t *testing.T) {
env := testTagStore(t)
tagStore := env.ts
ctx := env.ctx
descA := distribution.Descriptor{Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
desc0 := distribution.Descriptor{Digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"}
tags, err := tagStore.Lookup(ctx, descA)
if err != nil {
t.Fatal(err)
}
if len(tags) != 0 {
t.Fatalf("Lookup returned > 0 tags from empty store")
}
err = tagStore.Tag(ctx, "a", descA)
if err != nil {
t.Fatal(err)
}
err = tagStore.Tag(ctx, "b", descA)
if err != nil {
t.Fatal(err)
}
err = tagStore.Tag(ctx, "0", desc0)
if err != nil {
t.Fatal(err)
}
err = tagStore.Tag(ctx, "1", desc0)
if err != nil {
t.Fatal(err)
}
tags, err = tagStore.Lookup(ctx, descA)
if err != nil {
t.Fatal(err)
}
if len(tags) != 2 {
t.Errorf("Lookup of descA returned %d tags, expected 2", len(tags))
}
tags, err = tagStore.Lookup(ctx, desc0)
if err != nil {
t.Fatal(err)
}
if len(tags) != 2 {
t.Errorf("Lookup of descB returned %d tags, expected 2", len(tags))
}
}