Skip to content

Commit

Permalink
feat: Referrers support for content.ReadOnlyGraphStorage (#659)
Browse files Browse the repository at this point in the history
This PR provides `Referrers` support for the
`content.ReadOnlyGraphStorage` interface, which includes `oci.Store`.

Resolves #392
Signed-off-by: Xiaoxuan Wang <wangxiaoxuan119@gmail.com>
  • Loading branch information
wangxiaoxuan273 authored Dec 27, 2023
1 parent 5073458 commit 1d9ad6c
Show file tree
Hide file tree
Showing 2 changed files with 495 additions and 0 deletions.
90 changes: 90 additions & 0 deletions registry/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ package registry

import (
"context"
"encoding/json"
"fmt"
"io"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/errdef"
"oras.land/oras-go/v2/internal/descriptor"
"oras.land/oras-go/v2/internal/spec"
)

// Repository is an ORAS target and an union of the blob and the manifest CASs.
Expand Down Expand Up @@ -134,3 +139,88 @@ func Tags(ctx context.Context, repo TagLister) ([]string, error) {
}
return res, nil
}

// Referrers lists the descriptors of image or artifact manifests directly
// referencing the given manifest descriptor.
//
// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers
func Referrers(ctx context.Context, store content.ReadOnlyGraphStorage, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) {
if !descriptor.IsManifest(desc) {
return nil, fmt.Errorf("the descriptor %v is not a manifest: %w", desc, errdef.ErrUnsupported)
}

var results []ocispec.Descriptor

// use the Referrer API if it is available
if rf, ok := store.(ReferrerLister); ok {
if err := rf.Referrers(ctx, desc, artifactType, func(referrers []ocispec.Descriptor) error {
results = append(results, referrers...)
return nil
}); err != nil {
return nil, err
}
return results, nil
}

predecessors, err := store.Predecessors(ctx, desc)
if err != nil {
return nil, err
}
for _, node := range predecessors {
switch node.MediaType {
case ocispec.MediaTypeImageManifest:
fetched, err := content.FetchAll(ctx, store, node)
if err != nil {
return nil, err
}
var manifest ocispec.Manifest
if err := json.Unmarshal(fetched, &manifest); err != nil {
return nil, err
}
if manifest.Subject == nil || !content.Equal(*manifest.Subject, desc) {
continue
}
node.ArtifactType = manifest.ArtifactType
if node.ArtifactType == "" {
node.ArtifactType = manifest.Config.MediaType
}
node.Annotations = manifest.Annotations
case ocispec.MediaTypeImageIndex:
fetched, err := content.FetchAll(ctx, store, node)
if err != nil {
return nil, err
}
var index ocispec.Index
if err := json.Unmarshal(fetched, &index); err != nil {
return nil, err
}
if index.Subject == nil || !content.Equal(*index.Subject, desc) {
continue
}
node.ArtifactType = index.ArtifactType
node.Annotations = index.Annotations
case spec.MediaTypeArtifactManifest:
fetched, err := content.FetchAll(ctx, store, node)
if err != nil {
return nil, err
}
var artifact spec.Artifact
if err := json.Unmarshal(fetched, &artifact); err != nil {
return nil, err
}
if artifact.Subject == nil || !content.Equal(*artifact.Subject, desc) {
continue
}
node.ArtifactType = artifact.ArtifactType
node.Annotations = artifact.Annotations
default:
continue
}
if artifactType == "" || artifactType == node.ArtifactType {
// the field artifactType in referrers descriptor is allowed to be empty
// https://github.com/opencontainers/distribution-spec/issues/458
results = append(results, node)
}
}
return results, nil
}
Loading

0 comments on commit 1d9ad6c

Please sign in to comment.