@@ -40,14 +40,20 @@ import (
4040 "oras.land/oras-go/v2/internal/registryutil"
4141 "oras.land/oras-go/v2/registry"
4242 "oras.land/oras-go/v2/registry/remote/auth"
43+ "oras.land/oras-go/v2/registry/remote/errcode"
4344 "oras.land/oras-go/v2/registry/remote/internal/errutil"
4445)
4546
46- // dockerContentDigestHeader - The Docker-Content-Digest header, if present on
47- // the response, returns the canonical digest of the uploaded blob.
48- // See https://docs.docker.com/registry/spec/api/#digest-header
49- // See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pull
50- const dockerContentDigestHeader = "Docker-Content-Digest"
47+ const (
48+ // dockerContentDigestHeader - The Docker-Content-Digest header, if present
49+ // on the response, returns the canonical digest of the uploaded blob.
50+ // See https://docs.docker.com/registry/spec/api/#digest-header
51+ // See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pull
52+ dockerContentDigestHeader = "Docker-Content-Digest"
53+ // zeroDigest represents a digest that consists of zeros. zeroDigest is used
54+ // for pinging Referrers API.
55+ zeroDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
56+ )
5157
5258// referrersState represents the state of Referrers API.
5359type referrersState = int32
@@ -121,7 +127,11 @@ type Repository struct {
121127 referrersState referrersState
122128
123129 // referrersTagLocks maps a referrers tag to a lock.
124- referrersTagLocks sync.Map // map[string]sync.Mutex
130+ referrersTagLocks sync.Map // map[string]*sync.Mutex
131+
132+ // referrersPingLock locks the pingReferrersAPI() method and allows only
133+ // one go-routine to send the request.
134+ referrersPingLock sync.Mutex
125135}
126136
127137// NewRepository creates a client to the remote repository identified by a
@@ -399,13 +409,18 @@ func (r *Repository) Referrers(ctx context.Context, desc ocispec.Descriptor, art
399409
400410 // The referrers state is unknown.
401411 if err != nil {
402- if errors .Is (err , errdef .ErrNotFound ) {
403- // A 404 returned by Referrers API indicates that Referrers API is
404- // not supported. Fallback to referrers tag schema.
405- r .SetReferrersCapability (false )
406- return r .referrersByTagSchema (ctx , desc , artifactType , fn )
412+ var errResp * errcode.ErrorResponse
413+ if ! errors .As (err , & errResp ) || errResp .StatusCode != http .StatusNotFound {
414+ return err
407415 }
408- return err
416+ if errutil .IsErrorCode (errResp , errcode .ErrorCodeNameUnknown ) {
417+ // The repository is not found, no fallback.
418+ return err
419+ }
420+ // A 404 returned by Referrers API indicates that Referrers API is
421+ // not supported. Fallback to referrers tag schema.
422+ r .SetReferrersCapability (false )
423+ return r .referrersByTagSchema (ctx , desc , artifactType , fn )
409424 }
410425
411426 r .SetReferrersCapability (true )
@@ -455,9 +470,6 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string
455470 }
456471 defer resp .Body .Close ()
457472
458- if resp .StatusCode == http .StatusNotFound {
459- return "" , fmt .Errorf ("%s %q: %w" , resp .Request .Method , resp .Request .URL , errdef .ErrNotFound )
460- }
461473 if resp .StatusCode != http .StatusOK {
462474 return "" , errutil .ParseErrorResponse (resp )
463475 }
@@ -973,11 +985,11 @@ func (s *manifestStore) indexReferrersForDelete(ctx context.Context, desc ocispe
973985 }
974986
975987 subject := * manifest .Subject
976- yes , err := s .repo .isReferrersAPIAvailable (ctx , subject )
988+ ok , err := s .repo .pingReferrers (ctx )
977989 if err != nil {
978990 return err
979991 }
980- if yes {
992+ if ok {
981993 // referrers API is available, no client-side indexing needed
982994 return nil
983995 }
@@ -1238,11 +1250,11 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec.
12381250 return nil
12391251 }
12401252
1241- yes , err := s .repo .isReferrersAPIAvailable (ctx , subject )
1253+ ok , err := s .repo .pingReferrers (ctx )
12421254 if err != nil {
12431255 return err
12441256 }
1245- if yes {
1257+ if ok {
12461258 // referrers API is available, no client-side indexing needed
12471259 return nil
12481260 }
@@ -1288,8 +1300,8 @@ func (s *manifestStore) updateReferrersIndexForPush(ctx context.Context, desc, s
12881300 return s .repo .delete (ctx , oldIndexDesc , true )
12891301}
12901302
1291- // isReferrersAPIAvailable returns true if the Referrers API is available for r.
1292- func (r * Repository ) isReferrersAPIAvailable (ctx context.Context , desc ocispec. Descriptor ) (bool , error ) {
1303+ // pingReferrers returns true if the Referrers API is available for r.
1304+ func (r * Repository ) pingReferrers (ctx context.Context ) (bool , error ) {
12931305 switch r .loadReferrersState () {
12941306 case referrersStateSupported :
12951307 return true , nil
@@ -1298,8 +1310,19 @@ func (r *Repository) isReferrersAPIAvailable(ctx context.Context, desc ocispec.D
12981310 }
12991311
13001312 // referrers state is unknown
1313+ // limit the rate of pinging referrers API
1314+ r .referrersPingLock .Lock ()
1315+ defer r .referrersPingLock .Unlock ()
1316+
1317+ switch r .loadReferrersState () {
1318+ case referrersStateSupported :
1319+ return true , nil
1320+ case referrersStateUnsupported :
1321+ return false , nil
1322+ }
1323+
13011324 ref := r .Reference
1302- ref .Reference = desc . Digest . String ()
1325+ ref .Reference = zeroDigest
13031326 ctx = registryutil .WithScopeHint (ctx , ref , auth .ActionPull )
13041327
13051328 url := buildReferrersURL (r .PlainHTTP , ref , "" )
@@ -1318,6 +1341,10 @@ func (r *Repository) isReferrersAPIAvailable(ctx context.Context, desc ocispec.D
13181341 r .SetReferrersCapability (true )
13191342 return true , nil
13201343 case http .StatusNotFound :
1344+ if err := errutil .ParseErrorResponse (resp ); errutil .IsErrorCode (err , errcode .ErrorCodeNameUnknown ) {
1345+ // repository not found
1346+ return false , err
1347+ }
13211348 r .SetReferrersCapability (false )
13221349 return false , nil
13231350 default :
0 commit comments