github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/registry/repository.go (about)

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package registry
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  
    24  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    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"
    29  )
    30  
    31  // Repository is an ORAS target and an union of the blob and the manifest CASs.
    32  //
    33  // As specified by https://docs.docker.com/registry/spec/api/, it is natural to
    34  // assume that content.Resolver interface only works for manifests. Tagging a
    35  // blob may be resulted in an `ErrUnsupported` error. However, this interface
    36  // does not restrict tagging blobs.
    37  //
    38  // Since a repository is an union of the blob and the manifest CASs, all
    39  // operations defined in the `BlobStore` are executed depending on the media
    40  // type of the given descriptor accordingly.
    41  //
    42  // Furthermore, this interface also provides the ability to enforce the
    43  // separation of the blob and the manifests CASs.
    44  type Repository interface {
    45  	content.Storage
    46  	content.Deleter
    47  	content.TagResolver
    48  	ReferenceFetcher
    49  	ReferencePusher
    50  	ReferrerLister
    51  	TagLister
    52  
    53  	// Blobs provides access to the blob CAS only, which contains config blobs,
    54  	// layers, and other generic blobs.
    55  	Blobs() BlobStore
    56  
    57  	// Manifests provides access to the manifest CAS only.
    58  	Manifests() ManifestStore
    59  }
    60  
    61  // BlobStore is a CAS with the ability to stat and delete its content.
    62  type BlobStore interface {
    63  	content.Storage
    64  	content.Deleter
    65  	content.Resolver
    66  	ReferenceFetcher
    67  }
    68  
    69  // ManifestStore is a CAS with the ability to stat and delete its content.
    70  // Besides, ManifestStore provides reference tagging.
    71  type ManifestStore interface {
    72  	BlobStore
    73  	content.Tagger
    74  	ReferencePusher
    75  }
    76  
    77  // ReferencePusher provides advanced push with the tag service.
    78  type ReferencePusher interface {
    79  	// PushReference pushes the manifest with a reference tag.
    80  	PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error
    81  }
    82  
    83  // ReferenceFetcher provides advanced fetch with the tag service.
    84  type ReferenceFetcher interface {
    85  	// FetchReference fetches the content identified by the reference.
    86  	FetchReference(ctx context.Context, reference string) (ocispec.Descriptor, io.ReadCloser, error)
    87  }
    88  
    89  // ReferrerLister provides the Referrers API.
    90  // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers
    91  type ReferrerLister interface {
    92  	Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error
    93  }
    94  
    95  // TagLister lists tags by the tag service.
    96  type TagLister interface {
    97  	// Tags lists the tags available in the repository.
    98  	// Since the returned tag list may be paginated by the underlying
    99  	// implementation, a function should be passed in to process the paginated
   100  	// tag list.
   101  	//
   102  	// `last` argument is the `last` parameter when invoking the tags API.
   103  	// If `last` is NOT empty, the entries in the response start after the
   104  	// tag specified by `last`. Otherwise, the response starts from the top
   105  	// of the Tags list.
   106  	//
   107  	// Note: When implemented by a remote registry, the tags API is called.
   108  	// However, not all registries supports pagination or conforms the
   109  	// specification.
   110  	//
   111  	// References:
   112  	//   - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#content-discovery
   113  	//   - https://docs.docker.com/registry/spec/api/#tags
   114  	// See also `Tags()` in this package.
   115  	Tags(ctx context.Context, last string, fn func(tags []string) error) error
   116  }
   117  
   118  // Mounter allows cross-repository blob mounts.
   119  // For backward compatibility reasons, this is not implemented by
   120  // BlobStore: use a type assertion to check availability.
   121  type Mounter interface {
   122  	// Mount makes the blob with the given descriptor in fromRepo
   123  	// available in the repository signified by the receiver.
   124  	Mount(ctx context.Context,
   125  		desc ocispec.Descriptor,
   126  		fromRepo string,
   127  		getContent func() (io.ReadCloser, error),
   128  	) error
   129  }
   130  
   131  // Tags lists the tags available in the repository.
   132  func Tags(ctx context.Context, repo TagLister) ([]string, error) {
   133  	var res []string
   134  	if err := repo.Tags(ctx, "", func(tags []string) error {
   135  		res = append(res, tags...)
   136  		return nil
   137  	}); err != nil {
   138  		return nil, err
   139  	}
   140  	return res, nil
   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/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  }