From d6073d69b41c703dd09273c07b03f7f7d2032789 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Fri, 8 Sep 2023 16:04:05 +0800 Subject: [PATCH 1/9] test pushing manifest with skipGC enabled Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository_test.go | 215 +++++++++++++++++++++++------ 1 file changed, 171 insertions(+), 44 deletions(-) diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index fdd5e9ce..3fa35e2e 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -3589,7 +3589,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { artifactDesc.ArtifactType = artifact.ArtifactType artifactDesc.Annotations = artifact.Annotations - // test pushing artifact with subject + // test pushing artifact with subject, a referrer list should be created index_1 := ocispec.Index{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value. does not pertain to OCI or docker version @@ -3774,7 +3774,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } - // test pushing image manifest with subject without cleaning dangling referrers + // test pushing image manifest with subject again, referrers list should not be changed ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String(): @@ -3792,7 +3792,91 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { 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_1) + w.Write(indexJSON_2) + 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) + } + + ctx = context.Background() + repo, err = NewRepository(uri.Host + "/test") + if err != nil { + t.Fatalf("NewRepository() error = %v", err) + } + repo.PlainHTTP = true + if state := repo.loadReferrersState(); state != referrersStateUnknown { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) + } + err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON)) + if err != nil { + t.Fatalf("Manifests.Push() error = %v", err) + } + if !bytes.Equal(gotManifest, manifestJSON) { + t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON)) + } + // referrers list should not be changed + if !bytes.Equal(gotReferrerIndex, indexJSON_2) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2)) + } + if state := repo.loadReferrersState(); state != referrersStateUnsupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) + } + + // push image index with subject, referrer list should be updated + indexManifest := ocispec.Index{ + MediaType: ocispec.MediaTypeImageIndex, + Subject: &subjectDesc, + ArtifactType: "test/index", + Annotations: map[string]string{"foo": "bar"}, + } + indexManifestJSON, err := json.Marshal(indexManifest) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) + indexManifestDesc.ArtifactType = indexManifest.ArtifactType + indexManifestDesc.Annotations = indexManifest.Annotations + 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{ + artifactDesc, + manifestDesc, + 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 + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String(): + if contentType := r.Header.Get("Content-Type"); contentType != indexManifestDesc.MediaType { + w.WriteHeader(http.StatusBadRequest) + break + } + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(r.Body); err != nil { + t.Errorf("fail to read: %v", err) + } + gotManifest = buf.Bytes() + w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String()) + w.WriteHeader(http.StatusCreated) + 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_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) @@ -3803,9 +3887,9 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("fail to read: %v", err) } gotReferrerIndex = buf.Bytes() - w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String()) + 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_1.Digest.String(): + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String(): manifestDeleted = true // no "Docker-Content-Digest" header for manifest deletion w.WriteHeader(http.StatusAccepted) @@ -3826,30 +3910,66 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Fatalf("NewRepository() error = %v", err) } repo.PlainHTTP = true - repo.SkipReferrersGC = true if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } - manifestDeleted = false - err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON)) + err = repo.Push(ctx, indexManifestDesc, bytes.NewReader(indexManifestJSON)) if err != nil { t.Fatalf("Manifests.Push() error = %v", err) } - if !bytes.Equal(gotManifest, manifestJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON)) + if !bytes.Equal(gotManifest, indexManifestJSON) { + t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexManifestJSON)) } - if !bytes.Equal(gotReferrerIndex, indexJSON_2) { - t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2)) + if !bytes.Equal(gotReferrerIndex, indexJSON_3) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3)) } - if manifestDeleted { - t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, false) + if !manifestDeleted { + t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true) } if state := repo.loadReferrersState(); state != referrersStateUnsupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } +} - // test pushing image manifest with subject again, referrers list should not be changed - ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +func Test_ManifestStore_Push_ReferrersAPIUnavailable_SkipReferrersGC(t *testing.T) { + // generate test content + subject := []byte(`{"layers":[]}`) + subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject) + referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1) + manifest := ocispec.Manifest{ + MediaType: ocispec.MediaTypeImageManifest, + Config: ocispec.Descriptor{ + MediaType: "testconfig", + }, + Subject: &subjectDesc, + Annotations: map[string]string{"foo": "bar"}, + } + manifestJSON, err := json.Marshal(manifest) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) + manifestDesc.ArtifactType = manifest.Config.MediaType + manifestDesc.Annotations = manifest.Annotations + index_1 := ocispec.Index{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageIndex, + Manifests: []ocispec.Descriptor{ + manifestDesc, + }, + } + + // test pushing image manifest with subject, a referrers list should be created + indexJSON_1, err := json.Marshal(index_1) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1) + var gotManifest []byte + var gotReferrerIndex []byte + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String(): if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType { @@ -3866,24 +3986,38 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { 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_2) + w.WriteHeader(http.StatusNotFound) + 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_1.Digest.String()) + w.WriteHeader(http.StatusCreated) default: t.Errorf("unexpected access: %s %s", r.Method, r.URL) w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() - uri, err = url.Parse(ts.URL) + uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("invalid test http server: %v", err) } - ctx = context.Background() - repo, err = NewRepository(uri.Host + "/test") + ctx := context.Background() + repo, err := NewRepository(uri.Host + "/test") if err != nil { t.Fatalf("NewRepository() error = %v", err) } repo.PlainHTTP = true + repo.SkipReferrersGC = true + if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } @@ -3894,15 +4028,15 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { if !bytes.Equal(gotManifest, manifestJSON) { t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON)) } - // referrers list should not be changed - if !bytes.Equal(gotReferrerIndex, indexJSON_2) { - t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2)) + if !bytes.Equal(gotReferrerIndex, indexJSON_1) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1)) } if state := repo.loadReferrersState(); state != referrersStateUnsupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } - // push image index with subject, referrer list should be updated + // push image index with subject, referrer list should be updated, the old + // one should not be deleted indexManifest := ocispec.Index{ MediaType: ocispec.MediaTypeImageIndex, Subject: &subjectDesc, @@ -3916,23 +4050,21 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) indexManifestDesc.ArtifactType = indexManifest.ArtifactType indexManifestDesc.Annotations = indexManifest.Annotations - index_3 := ocispec.Index{ + index_2 := ocispec.Index{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value. does not pertain to OCI or docker version }, MediaType: ocispec.MediaTypeImageIndex, Manifests: []ocispec.Descriptor{ - artifactDesc, manifestDesc, indexManifestDesc, }, } - indexJSON_3, err := json.Marshal(index_3) + indexJSON_2, err := json.Marshal(index_2) if err != nil { t.Fatalf("failed to marshal manifest: %v", err) } - indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3) - manifestDeleted = false + indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2) ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String(): @@ -3950,7 +4082,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { 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_2) + w.Write(indexJSON_1) 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) @@ -3961,12 +4093,8 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("fail to read: %v", err) } gotReferrerIndex = buf.Bytes() - w.Header().Set("Docker-Content-Digest", indexDesc_3.Digest.String()) + w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String(): - manifestDeleted = 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) @@ -3984,6 +4112,8 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Fatalf("NewRepository() error = %v", err) } repo.PlainHTTP = true + repo.SkipReferrersGC = true + if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } @@ -3994,11 +4124,8 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { if !bytes.Equal(gotManifest, indexManifestJSON) { t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexManifestJSON)) } - if !bytes.Equal(gotReferrerIndex, indexJSON_3) { - t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3)) - } - if !manifestDeleted { - t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true) + if !bytes.Equal(gotReferrerIndex, indexJSON_2) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2)) } if state := repo.loadReferrersState(); state != referrersStateUnsupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) @@ -4317,7 +4444,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { } indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) - // test deleting artifact with subject + // test deleting artifact with subject, referrers list should be updated index_1 := ocispec.Index{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value. does not pertain to OCI or docker version @@ -4422,7 +4549,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { store := repo.Manifests() ctx := context.Background() - // test deleting artifact with subject + // test deleting artifact with subject, referrers list should be updated if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } @@ -4443,7 +4570,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } - // test deleting manifest with subject + // test deleting manifest with subject, referrers list should be updated manifestDeleted = false indexDeleted = false ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -4517,7 +4644,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } - // test deleting index with a subject + // test deleting index with a subject, referrers list should be updated manifestDeleted = false indexDeleted = false ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { From d91a617e45f5915f44c5c46c6884ddea271614c8 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Fri, 8 Sep 2023 16:23:33 +0800 Subject: [PATCH 2/9] test deleting manifest with SkipGC - should fail now Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository_test.go | 216 ++++++++++++++++++++++++++++- 1 file changed, 214 insertions(+), 2 deletions(-) diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index 3fa35e2e..ed931556 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -4444,7 +4444,6 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { } indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON) - // test deleting artifact with subject, referrers list should be updated index_1 := ocispec.Index{ Versioned: specs.Versioned{ SchemaVersion: 2, // historical value. does not pertain to OCI or docker version @@ -4491,6 +4490,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { } indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3) + // test deleting artifact with subject, referrers list should be updated manifestDeleted := false indexDeleted := false var gotReferrerIndex []byte @@ -4549,7 +4549,6 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { store := repo.Manifests() ctx := context.Background() - // test deleting artifact with subject, referrers list should be updated if state := repo.loadReferrersState(); state != referrersStateUnknown { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) } @@ -4707,6 +4706,219 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) { } } +func Test_ManifestStore_Delete_ReferrersAPIUnavailable_SkipReferrersGC(t *testing.T) { + // generate test content + subject := []byte(`{"layers":[]}`) + subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject) + referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1) + + manifest := ocispec.Manifest{ + MediaType: ocispec.MediaTypeImageManifest, + Subject: &subjectDesc, + } + manifestJSON, err := json.Marshal(manifest) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) + + 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) + + index_1 := ocispec.Index{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageIndex, + Manifests: []ocispec.Descriptor{ + manifestDesc, + indexManifestDesc, + }, + } + indexJSON_1, err := json.Marshal(index_1) + if err != nil { + t.Fatalf("failed to marshal manifest: %v", err) + } + index_2 := 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_2, err := json.Marshal(index_2) + if err != nil { + 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{}, + } + 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) + + // test deleting image manifest with subject, referrers list should be updated, + // the old one should not be deleted + manifestDeleted := false + var gotReferrerIndex []byte + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.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/"+manifestDesc.Digest.String(): + if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) { + t.Errorf("manifest not convertable: %s", accept) + w.WriteHeader(http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", manifestDesc.MediaType) + w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) + if _, err := w.Write(manifestJSON); 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_1) + 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_2.Digest.String()) + w.WriteHeader(http.StatusCreated) + 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 + repo.SkipReferrersGC = 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, manifestDesc) + if err != nil { + t.Fatalf("Manifests.Delete() error = %v", err) + } + if !manifestDeleted { + t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true) + } + if !bytes.Equal(gotReferrerIndex, indexJSON_2) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2)) + } + if state := repo.loadReferrersState(); state != referrersStateUnsupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) + } + + // test deleting index with a subject, referrers list should be updated, + // the old one should not be deleted + manifestDeleted = 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_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) + 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 + repo.SkipReferrersGC = 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 !bytes.Equal(gotReferrerIndex, indexJSON_3) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3)) + } + if state := repo.loadReferrersState(); state != referrersStateUnsupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) + } +} + func Test_ManifestStore_Delete_ReferrersAPIUnavailable_InconsistentIndex(t *testing.T) { // generate test content subject := []byte(`{"layers":[]}`) From f6c64df67e2717543e7d4a6e29a72a43ef0d6113 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Fri, 8 Sep 2023 16:59:09 +0800 Subject: [PATCH 3/9] improve readability Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository.go | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index 0f8c6acd..4cd2bc2b 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -1425,17 +1425,17 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec. func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispec.Descriptor, change referrerChange) (err error) { referrersTag := buildReferrersTag(subject) - skipDelete := s.repo.SkipReferrersGC + var noOldIndex bool var oldIndexDesc ocispec.Descriptor - var referrers []ocispec.Descriptor + var oldReferrers []ocispec.Descriptor prepare := func() error { // 1. pull the original referrers list using the referrers tag schema var err error - oldIndexDesc, referrers, err = s.repo.referrersFromIndex(ctx, referrersTag) + oldIndexDesc, oldReferrers, err = s.repo.referrersFromIndex(ctx, referrersTag) if err != nil { if errors.Is(err, errdef.ErrNotFound) { // no old index found, skip delete - skipDelete = true + noOldIndex = true return nil } return err @@ -1444,7 +1444,7 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe } update := func(referrerChanges []referrerChange) error { // 2. apply the referrer changes on the referrers list - updatedReferrers, err := applyReferrerChanges(referrers, referrerChanges) + updatedReferrers, err := applyReferrerChanges(oldReferrers, referrerChanges) if err != nil { if err == errNoReferrerUpdate { return nil @@ -1453,7 +1453,11 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe } // 3. push the updated referrers list using referrers tag schema - if len(updatedReferrers) > 0 { + if len(updatedReferrers) > 0 || s.repo.SkipReferrersGC { + // push a new index when: + // 1. updatedReferrers is not empty + // 2. OR referrers GC is skipped, in this case the old index + // won't get deleted and an empty index should be pushed newIndexDesc, newIndex, err := generateIndex(updatedReferrers) if err != nil { return fmt.Errorf("failed to generate referrers index for referrers tag %s: %w", referrersTag, err) @@ -1463,14 +1467,15 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe } } - // 4. delete the dangling original referrers index - if !skipDelete { - if err := s.repo.delete(ctx, oldIndexDesc, true); err != nil { - return &ReferrersError{ - Op: opDeleteReferrersIndex, - Err: fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", oldIndexDesc.Digest.String(), referrersTag, err), - Subject: subject, - } + // 4. delete the dangling original referrers index, if applicable + if s.repo.SkipReferrersGC || noOldIndex { + return nil + } + if err := s.repo.delete(ctx, oldIndexDesc, true); err != nil { + return &ReferrersError{ + Op: opDeleteReferrersIndex, + Err: fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", oldIndexDesc.Digest.String(), referrersTag, err), + Subject: subject, } } return nil From 9272c93dbc30c49ff9d95af721fd043db9bd1f04 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Fri, 8 Sep 2023 17:03:35 +0800 Subject: [PATCH 4/9] update comment Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index ed931556..b9a842f7 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -4847,7 +4847,7 @@ func Test_ManifestStore_Delete_ReferrersAPIUnavailable_SkipReferrersGC(t *testin } // test deleting index with a subject, referrers list should be updated, - // the old one should not be deleted + // the old one should not be deleted, an empty one should be pushed manifestDeleted = false ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { From d51c6f646e304eb35de74202c41606101c9451b8 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Fri, 8 Sep 2023 17:25:35 +0800 Subject: [PATCH 5/9] improve comment Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index 4cd2bc2b..b06b9a3b 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -1454,10 +1454,11 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe // 3. push the updated referrers list using referrers tag schema if len(updatedReferrers) > 0 || s.repo.SkipReferrersGC { - // push a new index when: - // 1. updatedReferrers is not empty - // 2. OR referrers GC is skipped, in this case the old index - // won't get deleted and an empty index should be pushed + // push a new index in either case: + // 1. the referrers list has been updated with a non-zero size + // 2. OR the updated referrers list is empty but referrers GC + // is skipped, in this case an empty index should still be pushed + // as the old index won't get deleted newIndexDesc, newIndex, err := generateIndex(updatedReferrers) if err != nil { return fmt.Errorf("failed to generate referrers index for referrers tag %s: %w", referrersTag, err) From b7f2f878c6bd6afc0bdf20a4fa7ca0d7b782b3e7 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Mon, 11 Sep 2023 15:54:04 +0800 Subject: [PATCH 6/9] make more sense Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index b06b9a3b..ab13604d 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -1425,21 +1425,20 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec. func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispec.Descriptor, change referrerChange) (err error) { referrersTag := buildReferrersTag(subject) - var noOldIndex bool - var oldIndexDesc ocispec.Descriptor + var oldIndexDesc *ocispec.Descriptor var oldReferrers []ocispec.Descriptor prepare := func() error { // 1. pull the original referrers list using the referrers tag schema - var err error - oldIndexDesc, oldReferrers, err = s.repo.referrersFromIndex(ctx, referrersTag) + indexDesc, referrers, err := s.repo.referrersFromIndex(ctx, referrersTag) if err != nil { if errors.Is(err, errdef.ErrNotFound) { - // no old index found, skip delete - noOldIndex = true + // valid case: the old index does not exist return nil } return err } + oldIndexDesc = &indexDesc + oldReferrers = referrers return nil } update := func(referrerChanges []referrerChange) error { @@ -1469,13 +1468,13 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe } // 4. delete the dangling original referrers index, if applicable - if s.repo.SkipReferrersGC || noOldIndex { + if s.repo.SkipReferrersGC || oldIndexDesc == nil { return nil } - if err := s.repo.delete(ctx, oldIndexDesc, true); err != nil { + if err := s.repo.delete(ctx, *oldIndexDesc, true); err != nil { return &ReferrersError{ Op: opDeleteReferrersIndex, - Err: fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", oldIndexDesc.Digest.String(), referrersTag, err), + Err: fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", (*oldIndexDesc).Digest.String(), referrersTag, err), Subject: subject, } } From d11f8c9b3398ea3e4704cc50ab9182324fc5deb0 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Mon, 11 Sep 2023 16:00:15 +0800 Subject: [PATCH 7/9] minor rewording Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index ab13604d..62aa9c78 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -1432,7 +1432,7 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe indexDesc, referrers, err := s.repo.referrersFromIndex(ctx, referrersTag) if err != nil { if errors.Is(err, errdef.ErrNotFound) { - // valid case: the old index does not exist + // valid case: no old referrers index return nil } return err From 26ccbc3dea5bddf2023c7b7ba2a2ceab1a0a6764 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Mon, 11 Sep 2023 16:43:33 +0800 Subject: [PATCH 8/9] add test case Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository_test.go | 180 +++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 8 deletions(-) diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index b9a842f7..3e3ee61a 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -3671,6 +3671,92 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } + // test pushing artifact with subject when an old empty referrer list exists, + // the referrer list should be updated + emptyIndex := ocispec.Index{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageIndex, + } + emptyIndexJSON, err := json.Marshal(emptyIndex) + if err != nil { + t.Error("failed to marshal index", err) + } + emptyIndexDesc := content.NewDescriptorFromBytes(emptyIndex.MediaType, emptyIndexJSON) + var indexDeleted bool + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String(): + if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType { + w.WriteHeader(http.StatusBadRequest) + break + } + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(r.Body); err != nil { + t.Errorf("fail to read: %v", err) + } + gotManifest = buf.Bytes() + w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String()) + w.WriteHeader(http.StatusCreated) + 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(emptyIndexJSON) + 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_1.Digest.String()) + w.WriteHeader(http.StatusCreated) + case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+emptyIndexDesc.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) + } + + ctx = context.Background() + repo, err = NewRepository(uri.Host + "/test") + if err != nil { + t.Fatalf("NewRepository() error = %v", err) + } + repo.PlainHTTP = true + + if state := repo.loadReferrersState(); state != referrersStateUnknown { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) + } + err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON)) + if err != nil { + t.Fatalf("Manifests.Push() error = %v", err) + } + if !bytes.Equal(gotManifest, artifactJSON) { + t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON)) + } + if !bytes.Equal(gotReferrerIndex, indexJSON_1) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1)) + } + if !indexDeleted { + t.Errorf("indexDeleted = %v, want %v", indexDeleted, true) + } + if state := repo.loadReferrersState(); state != referrersStateUnsupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) + } + // test pushing image manifest with subject, referrer list should be updated manifest := ocispec.Manifest{ MediaType: ocispec.MediaTypeImageManifest, @@ -3702,7 +3788,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2) - var manifestDeleted bool + indexDeleted = false ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String(): @@ -3734,7 +3820,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String()) w.WriteHeader(http.StatusCreated) case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String(): - manifestDeleted = true + indexDeleted = true // no "Docker-Content-Digest" header for manifest deletion w.WriteHeader(http.StatusAccepted) default: @@ -3767,8 +3853,8 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { if !bytes.Equal(gotReferrerIndex, indexJSON_2) { t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2)) } - if !manifestDeleted { - t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true) + if !indexDeleted { + t.Errorf("indexDeleted = %v, want %v", indexDeleted, true) } if state := repo.loadReferrersState(); state != referrersStateUnsupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) @@ -3858,7 +3944,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { t.Fatalf("failed to marshal manifest: %v", err) } indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3) - manifestDeleted = false + indexDeleted = false ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String(): @@ -3890,7 +3976,7 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { 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(): - manifestDeleted = true + indexDeleted = true // no "Docker-Content-Digest" header for manifest deletion w.WriteHeader(http.StatusAccepted) default: @@ -3923,8 +4009,8 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { if !bytes.Equal(gotReferrerIndex, indexJSON_3) { t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3)) } - if !manifestDeleted { - t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true) + if !indexDeleted { + t.Errorf("indexDeleted = %v, want %v", indexDeleted, true) } if state := repo.loadReferrersState(); state != referrersStateUnsupported { t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) @@ -4035,6 +4121,84 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable_SkipReferrersGC(t *testing. t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) } + // test pushing image manifest with subject when an old empty referrer list exists, + // the referrer list should be updated + emptyIndex := ocispec.Index{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + MediaType: ocispec.MediaTypeImageIndex, + } + emptyIndexJSON, err := json.Marshal(emptyIndex) + if err != nil { + t.Error("failed to marshal index", err) + } + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String(): + if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType { + w.WriteHeader(http.StatusBadRequest) + break + } + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(r.Body); err != nil { + t.Errorf("fail to read: %v", err) + } + gotManifest = buf.Bytes() + w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) + w.WriteHeader(http.StatusCreated) + 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(emptyIndexJSON) + 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_1.Digest.String()) + w.WriteHeader(http.StatusCreated) + 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) + } + + ctx = context.Background() + repo, err = NewRepository(uri.Host + "/test") + if err != nil { + t.Fatalf("NewRepository() error = %v", err) + } + repo.PlainHTTP = true + repo.SkipReferrersGC = true + + if state := repo.loadReferrersState(); state != referrersStateUnknown { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) + } + err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON)) + if err != nil { + t.Fatalf("Manifests.Push() error = %v", err) + } + if !bytes.Equal(gotManifest, manifestJSON) { + t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON)) + } + if !bytes.Equal(gotReferrerIndex, indexJSON_1) { + t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1)) + } + if state := repo.loadReferrersState(); state != referrersStateUnsupported { + t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported) + } + // push image index with subject, referrer list should be updated, the old // one should not be deleted indexManifest := ocispec.Index{ From 17b6713f80754904f8a778205cd33ff12c239ef6 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Mon, 11 Sep 2023 16:48:20 +0800 Subject: [PATCH 9/9] minor Signed-off-by: Lixia (Sylvia) Lei --- registry/remote/repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index 62aa9c78..337b4115 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -1474,7 +1474,7 @@ func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispe if err := s.repo.delete(ctx, *oldIndexDesc, true); err != nil { return &ReferrersError{ Op: opDeleteReferrersIndex, - Err: fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", (*oldIndexDesc).Digest.String(), referrersTag, err), + Err: fmt.Errorf("failed to delete dangling referrers index %s for referrers tag %s: %w", oldIndexDesc.Digest.String(), referrersTag, err), Subject: subject, } }