@@ -17,10 +17,15 @@ package registry
17
17
18
18
import (
19
19
"context"
20
+ "encoding/json"
21
+ "fmt"
20
22
"io"
21
23
22
24
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
23
25
"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"
24
29
)
25
30
26
31
// 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) {
134
139
}
135
140
return res , nil
136
141
}
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