Skip to content

Commit 1d9ad6c

Browse files
feat: Referrers support for content.ReadOnlyGraphStorage (#659)
This PR provides `Referrers` support for the `content.ReadOnlyGraphStorage` interface, which includes `oci.Store`. Resolves #392 Signed-off-by: Xiaoxuan Wang <wangxiaoxuan119@gmail.com>
1 parent 5073458 commit 1d9ad6c

File tree

2 files changed

+495
-0
lines changed

2 files changed

+495
-0
lines changed

registry/repository.go

+90
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ package registry
1717

1818
import (
1919
"context"
20+
"encoding/json"
21+
"fmt"
2022
"io"
2123

2224
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2325
"oras.land/oras-go/v2/content"
26+
"oras.land/oras-go/v2/errdef"
27+
"oras.land/oras-go/v2/internal/descriptor"
28+
"oras.land/oras-go/v2/internal/spec"
2429
)
2530

2631
// Repository is an ORAS target and an union of the blob and the manifest CASs.
@@ -134,3 +139,88 @@ func Tags(ctx context.Context, repo TagLister) ([]string, error) {
134139
}
135140
return res, nil
136141
}
142+
143+
// Referrers lists the descriptors of image or artifact manifests directly
144+
// referencing the given manifest descriptor.
145+
//
146+
// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers
147+
func Referrers(ctx context.Context, store content.ReadOnlyGraphStorage, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) {
148+
if !descriptor.IsManifest(desc) {
149+
return nil, fmt.Errorf("the descriptor %v is not a manifest: %w", desc, errdef.ErrUnsupported)
150+
}
151+
152+
var results []ocispec.Descriptor
153+
154+
// use the Referrer API if it is available
155+
if rf, ok := store.(ReferrerLister); ok {
156+
if err := rf.Referrers(ctx, desc, artifactType, func(referrers []ocispec.Descriptor) error {
157+
results = append(results, referrers...)
158+
return nil
159+
}); err != nil {
160+
return nil, err
161+
}
162+
return results, nil
163+
}
164+
165+
predecessors, err := store.Predecessors(ctx, desc)
166+
if err != nil {
167+
return nil, err
168+
}
169+
for _, node := range predecessors {
170+
switch node.MediaType {
171+
case ocispec.MediaTypeImageManifest:
172+
fetched, err := content.FetchAll(ctx, store, node)
173+
if err != nil {
174+
return nil, err
175+
}
176+
var manifest ocispec.Manifest
177+
if err := json.Unmarshal(fetched, &manifest); err != nil {
178+
return nil, err
179+
}
180+
if manifest.Subject == nil || !content.Equal(*manifest.Subject, desc) {
181+
continue
182+
}
183+
node.ArtifactType = manifest.ArtifactType
184+
if node.ArtifactType == "" {
185+
node.ArtifactType = manifest.Config.MediaType
186+
}
187+
node.Annotations = manifest.Annotations
188+
case ocispec.MediaTypeImageIndex:
189+
fetched, err := content.FetchAll(ctx, store, node)
190+
if err != nil {
191+
return nil, err
192+
}
193+
var index ocispec.Index
194+
if err := json.Unmarshal(fetched, &index); err != nil {
195+
return nil, err
196+
}
197+
if index.Subject == nil || !content.Equal(*index.Subject, desc) {
198+
continue
199+
}
200+
node.ArtifactType = index.ArtifactType
201+
node.Annotations = index.Annotations
202+
case spec.MediaTypeArtifactManifest:
203+
fetched, err := content.FetchAll(ctx, store, node)
204+
if err != nil {
205+
return nil, err
206+
}
207+
var artifact spec.Artifact
208+
if err := json.Unmarshal(fetched, &artifact); err != nil {
209+
return nil, err
210+
}
211+
if artifact.Subject == nil || !content.Equal(*artifact.Subject, desc) {
212+
continue
213+
}
214+
node.ArtifactType = artifact.ArtifactType
215+
node.Annotations = artifact.Annotations
216+
default:
217+
continue
218+
}
219+
if artifactType == "" || artifactType == node.ArtifactType {
220+
// the field artifactType in referrers descriptor is allowed to be empty
221+
// https://github.com/opencontainers/distribution-spec/issues/458
222+
results = append(results, node)
223+
}
224+
}
225+
return results, nil
226+
}

0 commit comments

Comments
 (0)