Skip to content

Commit

Permalink
feat: add option to skip referrers GC (#515)
Browse files Browse the repository at this point in the history
Resolves #510

---------

Signed-off-by: Billy Zha <jinzha1@microsoft.com>
  • Loading branch information
qweeah authored May 31, 2023
1 parent 0e20275 commit 7dd0378
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 2 deletions.
3 changes: 2 additions & 1 deletion registry/remote/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ func TestRegistry_Repository(t *testing.T) {
t.Fatalf("NewRegistry() error = %v", err)
}
reg.PlainHTTP = true
reg.SkipReferrersGC = true
reg.RepositoryListPageSize = 50
reg.TagListPageSize = 100
reg.ReferrerListPageSize = 10
Expand Down Expand Up @@ -265,7 +266,7 @@ func TestRegistry_Repositories_WithLastParam(t *testing.T) {
}
}

//indexOf returns the index of an element within a slice
// indexOf returns the index of an element within a slice
func indexOf(element string, data []string) int {
for ind, val := range data {
if element == val {
Expand Down
14 changes: 13 additions & 1 deletion registry/remote/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ type Repository struct {
// If less than or equal to zero, a default (currently 4MiB) is used.
MaxMetadataBytes int64

// SkipReferrersGC specifies whether to delete the dangling referrers
// index when referrers tag schema is utilized.
// - If false, the old referrers index will be deleted after the new one
// is successfully uploaded.
// - If true, the old referrers index is kept.
// By default, it is disabled (set to false). See also:
// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#referrers-tag-schema
// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-manifests-with-subject
// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests
SkipReferrersGC bool

// NOTE: Must keep fields in sync with newRepositoryWithOptions function.

// referrersState represents that if the repository supports Referrers API.
Expand Down Expand Up @@ -145,6 +156,7 @@ func newRepositoryWithOptions(ref registry.Reference, opts *RepositoryOptions) (
Client: opts.Client,
Reference: ref,
PlainHTTP: opts.PlainHTTP,
SkipReferrersGC: opts.SkipReferrersGC,
ManifestMediaTypes: slices.Clone(opts.ManifestMediaTypes),
TagListPageSize: opts.TagListPageSize,
ReferrerListPageSize: opts.ReferrerListPageSize,
Expand Down Expand Up @@ -1316,7 +1328,7 @@ 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 skipDelete bool
skipDelete := s.repo.SkipReferrersGC
var oldIndexDesc ocispec.Descriptor
var referrers []ocispec.Descriptor
prepare := func() error {
Expand Down
74 changes: 74 additions & 0 deletions registry/remote/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3424,6 +3424,80 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) {
t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
}

// test push image manifest with subject without cleaning dangling referrers
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(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)
case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.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)
}
}))
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)
}
manifestDeleted = false
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_2) {
t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
}
if manifestDeleted {
t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, false)
}
if state := repo.loadReferrersState(); state != referrersStateUnsupported {
t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
}

// test push image manifest with subject again, referrers list should not be changed
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
Expand Down

0 comments on commit 7dd0378

Please sign in to comment.