@@ -40,14 +40,20 @@ import (
40
40
"oras.land/oras-go/v2/internal/registryutil"
41
41
"oras.land/oras-go/v2/registry"
42
42
"oras.land/oras-go/v2/registry/remote/auth"
43
+ "oras.land/oras-go/v2/registry/remote/errcode"
43
44
"oras.land/oras-go/v2/registry/remote/internal/errutil"
44
45
)
45
46
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
+ )
51
57
52
58
// referrersState represents the state of Referrers API.
53
59
type referrersState = int32
@@ -121,7 +127,11 @@ type Repository struct {
121
127
referrersState referrersState
122
128
123
129
// 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
125
135
}
126
136
127
137
// 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
399
409
400
410
// The referrers state is unknown.
401
411
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
407
415
}
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 )
409
424
}
410
425
411
426
r .SetReferrersCapability (true )
@@ -455,9 +470,6 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string
455
470
}
456
471
defer resp .Body .Close ()
457
472
458
- if resp .StatusCode == http .StatusNotFound {
459
- return "" , fmt .Errorf ("%s %q: %w" , resp .Request .Method , resp .Request .URL , errdef .ErrNotFound )
460
- }
461
473
if resp .StatusCode != http .StatusOK {
462
474
return "" , errutil .ParseErrorResponse (resp )
463
475
}
@@ -973,11 +985,11 @@ func (s *manifestStore) indexReferrersForDelete(ctx context.Context, desc ocispe
973
985
}
974
986
975
987
subject := * manifest .Subject
976
- yes , err := s .repo .isReferrersAPIAvailable (ctx , subject )
988
+ ok , err := s .repo .pingReferrers (ctx )
977
989
if err != nil {
978
990
return err
979
991
}
980
- if yes {
992
+ if ok {
981
993
// referrers API is available, no client-side indexing needed
982
994
return nil
983
995
}
@@ -1238,11 +1250,11 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec.
1238
1250
return nil
1239
1251
}
1240
1252
1241
- yes , err := s .repo .isReferrersAPIAvailable (ctx , subject )
1253
+ ok , err := s .repo .pingReferrers (ctx )
1242
1254
if err != nil {
1243
1255
return err
1244
1256
}
1245
- if yes {
1257
+ if ok {
1246
1258
// referrers API is available, no client-side indexing needed
1247
1259
return nil
1248
1260
}
@@ -1288,8 +1300,8 @@ func (s *manifestStore) updateReferrersIndexForPush(ctx context.Context, desc, s
1288
1300
return s .repo .delete (ctx , oldIndexDesc , true )
1289
1301
}
1290
1302
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 ) {
1293
1305
switch r .loadReferrersState () {
1294
1306
case referrersStateSupported :
1295
1307
return true , nil
@@ -1298,8 +1310,19 @@ func (r *Repository) isReferrersAPIAvailable(ctx context.Context, desc ocispec.D
1298
1310
}
1299
1311
1300
1312
// 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
+
1301
1324
ref := r .Reference
1302
- ref .Reference = desc . Digest . String ()
1325
+ ref .Reference = zeroDigest
1303
1326
ctx = registryutil .WithScopeHint (ctx , ref , auth .ActionPull )
1304
1327
1305
1328
url := buildReferrersURL (r .PlainHTTP , ref , "" )
@@ -1318,6 +1341,10 @@ func (r *Repository) isReferrersAPIAvailable(ctx context.Context, desc ocispec.D
1318
1341
r .SetReferrersCapability (true )
1319
1342
return true , nil
1320
1343
case http .StatusNotFound :
1344
+ if err := errutil .ParseErrorResponse (resp ); errutil .IsErrorCode (err , errcode .ErrorCodeNameUnknown ) {
1345
+ // repository not found
1346
+ return false , err
1347
+ }
1321
1348
r .SetReferrersCapability (false )
1322
1349
return false , nil
1323
1350
default :
0 commit comments