From 6ec43e7d6241285bb0ec1bf131840cd3352c6ebb Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Thu, 27 Jul 2023 11:05:50 +0800 Subject: [PATCH] feat: update the implementation of deleting manifest for distribution-spec v1.1.0-rc3 (#558) Support deleting image indexes with a subject Resolves: #535 Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository.go | 12 +- registry/remote/repository_test.go | 220 +++++++++++++++++++++++------ 2 files changed, 187 insertions(+), 45 deletions(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index ccda8adf..c952edb3 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -1079,7 +1079,8 @@ func (s *manifestStore) Delete(ctx context.Context, target ocispec.Descriptor) e // deleteWithIndexing removes the manifest content identified by the descriptor, // and indexes referrers for the manifest when needed. func (s *manifestStore) deleteWithIndexing(ctx context.Context, target ocispec.Descriptor) error { - if target.MediaType == spec.MediaTypeArtifactManifest || target.MediaType == ocispec.MediaTypeImageManifest { + switch target.MediaType { + case spec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex: if state := s.repo.loadReferrersState(); state == referrersStateSupported { // referrers API is available, no client-side indexing needed return s.repo.delete(ctx, target, true) @@ -1100,9 +1101,12 @@ func (s *manifestStore) deleteWithIndexing(ctx context.Context, target ocispec.D return s.repo.delete(ctx, target, true) } -// indexReferrersForDelete indexes referrers for image or artifact manifest with -// the subject field on manifest delete. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests +// indexReferrersForDelete indexes referrers for manifests with a subject field +// on manifest delete. +// +// References: +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests func (s *manifestStore) indexReferrersForDelete(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error { var manifest struct { Subject *ocispec.Descriptor `json:"subject"` diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index 1c114cc8..bf0a2ad6 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -693,29 +693,36 @@ func TestRepository_Delete(t *testing.T) { Digest: digest.FromBytes(blob), Size: int64(len(blob)), } - blobDeleted := false index := []byte(`{"manifests":[]}`) indexDesc := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageIndex, Digest: digest.FromBytes(index), Size: int64(len(index)), } - indexDeleted := false + + var blobDeleted bool + var indexDeleted bool ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodDelete { - t.Errorf("unexpected access: %s %s", r.Method, r.URL) - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - switch r.URL.Path { - case "/v2/test/blobs/" + blobDesc.Digest.String(): + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/blobs/"+blobDesc.Digest.String(): blobDeleted = true w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String()) w.WriteHeader(http.StatusAccepted) - case "/v2/test/manifests/" + indexDesc.Digest.String(): + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String(): indexDeleted = true // no "Docker-Content-Digest" header for manifest deletion w.WriteHeader(http.StatusAccepted) + case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String(): + if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) { + t.Errorf("manifest not convertable: %s", accept) + w.WriteHeader(http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", indexDesc.MediaType) + w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String()) + if _, err := w.Write(index); err != nil { + t.Errorf("failed to write %q: %v", r.URL, err) + } default: t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) @@ -3675,7 +3682,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { } manifestJSON, err := json.Marshal(manifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) manifestDesc.ArtifactType = manifest.Config.MediaType @@ -3692,7 +3699,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { } indexJSON_2, err := json.Marshal(index_2) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2) var manifestDeleted bool @@ -3904,7 +3911,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { } indexManifestJSON, err := json.Marshal(indexManifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) indexManifestDesc.ArtifactType = indexManifest.ArtifactType @@ -3922,7 +3929,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { } indexJSON_3, err := json.Marshal(index_3) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3) manifestDeleted = false @@ -4109,7 +4116,7 @@ func Test_ManifestStore_Delete(t *testing.T) { store := repo.Manifests() ctx := context.Background() - // test delete manifest without subject + // test deleting manifest without subject err = store.Delete(ctx, manifestDesc) if err != nil { t.Fatalf("Manifests.Delete() error = %v", err) @@ -4118,7 +4125,7 @@ func Test_ManifestStore_Delete(t *testing.T) { t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true) } - // test delete content that does not exist + // test deleting content that does not exist content := []byte(`{"manifests":[]}`) contentDesc := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageIndex, @@ -4141,20 +4148,33 @@ func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) { } artifactJSON, err := json.Marshal(artifact) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) + manifest := ocispec.Manifest{ MediaType: ocispec.MediaTypeImageManifest, Subject: &subjectDesc, } manifestJSON, err := json.Marshal(manifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) - manifestDeleted := false - artifactDeleted := false + + index := ocispec.Index{ + MediaType: ocispec.MediaTypeImageIndex, + Subject: &subjectDesc, + } + indexJSON, err := json.Marshal(index) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + indexDesc := content.NewDescriptorFromBytes(index.MediaType, indexJSON) + + var manifestDeleted bool + var artifactDeleted bool + var indexDeleted bool ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete && r.Method != http.MethodGet { t.Errorf("unexpected access: %s %s", r.Method, r.URL) @@ -4169,6 +4189,10 @@ func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) { manifestDeleted = true // no "Docker-Content-Digest" header for manifest deletion w.WriteHeader(http.StatusAccepted) + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String(): + indexDeleted = true + // no "Docker-Content-Digest" header for manifest deletion + w.WriteHeader(http.StatusAccepted) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String(): if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) { t.Errorf("manifest not convertable: %s", accept) @@ -4207,7 +4231,7 @@ func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) { repo.PlainHTTP = true store := repo.Manifests() ctx := context.Background() - // test delete artifact with subject + // test deleting artifact with subject if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } @@ -4219,7 +4243,7 @@ func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) { t.Errorf("Manifests.Delete() = %v, want %v", artifactDeleted, true) } - // test delete manifest with subject + // test deleting manifest with subject if state := repo.loadReferrersState(); state != referrersStateSupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) } @@ -4231,7 +4255,19 @@ func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) { t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true) } - // test delete content that does not exist + // test deleting index with subject + if state := repo.loadReferrersState(); state != referrersStateSupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) + } + err = store.Delete(ctx, indexDesc) + if err != nil { + t.Fatalf("Manifests.Delete() error = %v", err) + } + if !indexDeleted { + t.Errorf("Manifests.Delete() = %v, want %v", indexDeleted, true) + } + + // test deleting content that does not exist content := []byte("whatever") contentDesc := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageManifest, @@ -4250,26 +4286,38 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { subject := []byte(`{"layers":[]}`) subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject) referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1) + artifact := spec.Artifact{ MediaType: spec.MediaTypeArtifactManifest, Subject: &subjectDesc, } artifactJSON, err := json.Marshal(artifact) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) + manifest := ocispec.Manifest{ MediaType: ocispec.MediaTypeImageManifest, Subject: &subjectDesc, } manifestJSON, err := json.Marshal(manifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) - // test delete artifact with subject + indexManifest := ocispec.Index{ + MediaType: ocispec.MediaTypeImageIndex, + Subject: &subjectDesc, + } + indexManifestJSON, err := json.Marshal(indexManifest) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) + + // test deleting artifact with subject index_1 := ocispec.Index{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value. does not pertain to OCI or docker version @@ -4278,11 +4326,12 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { Manifests: []ocispec.Descriptor{ artifactDesc, manifestDesc, + indexManifestDesc, }, } indexJSON_1, err := json.Marshal(index_1) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1) index_2 := ocispec.Index{ @@ -4292,13 +4341,28 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { MediaType: ocispec.MediaTypeImageIndex, Manifests: []ocispec.Descriptor{ manifestDesc, + indexManifestDesc, }, } indexJSON_2, err := json.Marshal(index_2) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2) + index_3 := ocispec.Index{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageIndex, + Manifests: []ocispec.Descriptor{ + indexManifestDesc, + }, + } + indexJSON_3, err := json.Marshal(index_3) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3) manifestDeleted := false indexDeleted := false @@ -4358,7 +4422,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { store := repo.Manifests() ctx := context.Background() - // test delete artifact with subject + // test deleting artifact with subject if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } @@ -4379,7 +4443,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } - // test delete manifest with subject + // test deleting manifest with subject manifestDeleted = false indexDeleted = false ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -4403,6 +4467,18 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_2) + case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: + if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex { + w.WriteHeader(http.StatusBadRequest) + break + } + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(r.Body); err != nil { + t.Errorf("fail to read: %v", err) + } + gotReferrerIndex = buf.Bytes() + w.Header().Set("Docker-Content-Digest", indexDesc_3.Digest.String()) + w.WriteHeader(http.StatusCreated) case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String(): indexDeleted = true // no "Docker-Content-Digest" header for manifest deletion @@ -4440,6 +4516,68 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { if state := repo.loadReferrersState(); state != referrersStateUnsupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } + + // test deleting index with a subject + manifestDeleted = false + indexDeleted = false + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String(): + manifestDeleted = true + // no "Docker-Content-Digest" header for manifest deletion + w.WriteHeader(http.StatusAccepted) + case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String(): + if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexManifestDesc.MediaType) { + t.Errorf("manifest not convertable: %s", accept) + w.WriteHeader(http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", indexManifestDesc.MediaType) + w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String()) + if _, err := w.Write(indexManifestJSON); err != nil { + t.Errorf("failed to write %q: %v", r.URL, err) + } + case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: + w.WriteHeader(http.StatusNotFound) + case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: + w.Write(indexJSON_3) + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_3.Digest.String(): + indexDeleted = true + // no "Docker-Content-Digest" header for manifest deletion + w.WriteHeader(http.StatusAccepted) + default: + t.Errorf("unexpected access: %s %s", r.Method, r.URL) + w.WriteHeader(http.StatusNotFound) + } + })) + defer ts.Close() + uri, err = url.Parse(ts.URL) + if err != nil { + t.Fatalf("invalid test http server: %v", err) + } + repo, err = NewRepository(uri.Host + "/test") + if err != nil { + t.Fatalf("NewRepository() error = %v", err) + } + repo.PlainHTTP = true + store = repo.Manifests() + ctx = context.Background() + if state := repo.loadReferrersState(); state != referrersStateUnknown { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) + } + err = store.Delete(ctx, indexManifestDesc) + if err != nil { + t.Fatalf("Manifests.Delete() error = %v", err) + } + if !manifestDeleted { + t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true) + } + if !indexDeleted { + t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true) + } + if state := repo.loadReferrersState(); state != referrersStateUnsupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) + } } func Test_ManifestStore_Delete_ReferrersAPIUnavailable_InconsistentIndex(t *testing.T) { @@ -4453,7 +4591,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable_InconsistentIndex(t *test } artifactJSON, err := json.Marshal(artifact) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) @@ -5017,7 +5155,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIAvailable(t *testing.T) { } artifactJSON, err := json.Marshal(artifact) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) artifactRef := "foo" @@ -5028,7 +5166,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIAvailable(t *testing.T) { } manifestJSON, err := json.Marshal(manifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) manifestRef := "bar" @@ -5172,7 +5310,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIAvailable_NoSubjectHeader(t *t } artifactJSON, err := json.Marshal(artifact) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) artifactRef := "foo" @@ -5183,7 +5321,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIAvailable_NoSubjectHeader(t *t } manifestJSON, err := json.Marshal(manifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) manifestRef := "bar" @@ -5327,7 +5465,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { } artifactJSON, err := json.Marshal(artifact) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) artifactDesc.ArtifactType = artifact.ArtifactType @@ -5346,7 +5484,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { } indexJSON_1, err := json.Marshal(index_1) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1) var gotManifest []byte @@ -5427,7 +5565,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { } manifestJSON, err := json.Marshal(manifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) manifestDesc.ArtifactType = manifest.Config.MediaType @@ -5446,7 +5584,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { } indexJSON_2, err := json.Marshal(index_2) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2) var manifestDeleted bool @@ -5584,7 +5722,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { } indexManifestJSON, err := json.Marshal(indexManifest) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) indexManifestDesc.ArtifactType = indexManifest.ArtifactType @@ -5603,7 +5741,7 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { } indexJSON_3, err := json.Marshal(index_3) if err != nil { - t.Errorf("failed to marshal manifest: %v", err) + t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3) manifestDeleted = false