Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option to skip referrers GC #515

Merged
merged 17 commits into from
May 31, 2023
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