...

Source file src/cuelabs.dev/go/oci/ociregistry/interface.go

Documentation: cuelabs.dev/go/oci/ociregistry

     1  // Copyright 2023 CUE Labs AG
     2  //
     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  // Package ociregistry provides an abstraction that represents the
    16  // capabilities provided by an OCI registry.
    17  //
    18  // See the [OCI distribution specification] for more information on OCI registries.
    19  //
    20  // Packages within this module provide the capability to translate to and
    21  // from the HTTP protocol documented in that specification:
    22  // - [cuelabs.dev/go/oci/ociregistry/ociclient] provides an [Interface] value
    23  // that acts as an HTTP client.
    24  // - [cuelabs.dev/go/oci/ociregistry/ociserver] provides an HTTP server
    25  // that serves the distribution protocol by making calls to an arbitrary
    26  // [Interface] value.
    27  //
    28  // When used together in a stack, the above two packages can be used
    29  // to provide a simple proxy server.
    30  //
    31  // The [cuelabs.dev/go/oci/ociregistry/ocimem] package provides a trivial
    32  // in-memory implementation of the interface.
    33  //
    34  // Other packages provide some utilities that manipulate [Interface] values:
    35  // - [cuelabs.dev/go/oci/ociregistry/ocifilter] provides functionality for exposing
    36  // modified or restricted views onto a registry.
    37  // - [cuelabs.dev/go/oci/ociregistry/ociunify] can combine two registries into one
    38  // unified view across both.
    39  //
    40  // # Notes on [Interface]
    41  //
    42  // In general, the caller cannot assume that the implementation of a given [Interface] value
    43  // is present on the network. For example, [cuelabs.dev/go/oci/ociregistry/ocimem]
    44  // doesn't know about the network at all. But there are times when an implementation
    45  // might want to provide information about the location of blobs or manifests so
    46  // that a client can go direct if it wishes. That is, a proxy might not wish
    47  // to ship all the traffic for all blobs through itself, but instead redirect clients
    48  // to talk to some other location on the internet.
    49  //
    50  // When an [Interface] implementation wishes to provide that information, it
    51  // can do so by setting the `URLs` field on the descriptor that it returns for
    52  // a given blob or manifest. Although it is not mandatory for a caller to use
    53  // this, some callers (specifically the ociserver package) can use this information
    54  // to redirect clients appropriately.
    55  //
    56  // [OCI distribution specification]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md
    57  package ociregistry
    58  
    59  import (
    60  	"context"
    61  	"io"
    62  
    63  	"github.com/opencontainers/go-digest"
    64  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    65  )
    66  
    67  // Interface defines a generic interface to a single OCI registry.
    68  // It does not support cross-registry operations: all methods are
    69  // directed to the receiver only.
    70  type Interface interface {
    71  	Writer
    72  	Reader
    73  	Deleter
    74  	Lister
    75  	private()
    76  }
    77  
    78  type ReadWriter interface {
    79  	Reader
    80  	Writer
    81  }
    82  
    83  type (
    84  	Digest     = digest.Digest
    85  	Descriptor = ocispec.Descriptor
    86  	Manifest   = ocispec.Manifest
    87  )
    88  
    89  type Reader interface {
    90  	// GetBlob returns the content of the blob with the given digest.
    91  	// The context also controls the lifetime of the returned BlobReader.
    92  	// Errors:
    93  	// - ErrNameUnknown when the repository is not present.
    94  	// - ErrBlobUnknown when the blob is not present in the repository.
    95  	GetBlob(ctx context.Context, repo string, digest Digest) (BlobReader, error)
    96  
    97  	// GetBlobRange is like GetBlob but asks to get only the given range of bytes from the blob,
    98  	// starting at offset0, up to but not including offset1.
    99  	// If offset1 is negative or exceeds the actual size of the blob, GetBlobRange will
   100  	// return all the data starting from offset0.
   101  	// The context also controls the lifetime of the returned BlobReader.
   102  	GetBlobRange(ctx context.Context, repo string, digest Digest, offset0, offset1 int64) (BlobReader, error)
   103  
   104  	// GetManifest returns the contents of the manifest with the given digest.
   105  	// The context also controls the lifetime of the returned BlobReader.
   106  	// Errors:
   107  	// - ErrNameUnknown when the repository is not present.
   108  	// - ErrManifestUnknown when the blob is not present in the repository.
   109  	GetManifest(ctx context.Context, repo string, digest Digest) (BlobReader, error)
   110  
   111  	// GetTag returns the contents of the manifest with the given tag.
   112  	// The context also controls the lifetime of the returned BlobReader.
   113  	// Errors:
   114  	// - ErrNameUnknown when the repository is not present.
   115  	// - ErrManifestUnknown when the tag is not present in the repository.
   116  	GetTag(ctx context.Context, repo string, tagName string) (BlobReader, error)
   117  
   118  	// ResolveDigest returns the descriptor for a given blob.
   119  	// Only the MediaType, Digest and Size fields will be filled out.
   120  	// Errors:
   121  	// - ErrNameUnknown when the repository is not present.
   122  	// - ErrBlobUnknown when the blob is not present in the repository.
   123  	ResolveBlob(ctx context.Context, repo string, digest Digest) (Descriptor, error)
   124  
   125  	// ResolveManifest returns the descriptor for a given maniifest.
   126  	// Only the MediaType, Digest and Size fields will be filled out.
   127  	// Errors:
   128  	// - ErrNameUnknown when the repository is not present.
   129  	// - ErrManifestUnknown when the blob is not present in the repository.
   130  	ResolveManifest(ctx context.Context, repo string, digest Digest) (Descriptor, error)
   131  
   132  	// ResolveTag returns the descriptor for a given tag.
   133  	// Only the MediaType, Digest and Size fields will be filled out.
   134  	// Errors:
   135  	// - ErrNameUnknown when the repository is not present.
   136  	// - ErrManifestUnknown when the blob is not present in the repository.
   137  	ResolveTag(ctx context.Context, repo string, tagName string) (Descriptor, error)
   138  }
   139  
   140  // Writer defines registry actions that write to blobs, manifests and tags.
   141  type Writer interface {
   142  	// PushBlob pushes a blob described by desc to the given repository, reading content from r.
   143  	// Only the desc.Digest and desc.Size fields are used.
   144  	// It returns desc with Digest set to the canonical digest for the blob.
   145  	// Errors:
   146  	// - ErrNameUnknown when the repository is not present.
   147  	// - ErrNameInvalid when the repository name is not valid.
   148  	// - ErrDigestInvalid when desc.Digest does not match the content.
   149  	// - ErrSizeInvalid when desc.Size does not match the content length.
   150  	PushBlob(ctx context.Context, repo string, desc Descriptor, r io.Reader) (Descriptor, error)
   151  
   152  	// PushBlobChunked starts to push a blob to the given repository.
   153  	// The returned [BlobWriter] can be used to stream the upload and resume on temporary errors.
   154  	//
   155  	// The chunkSize parameter provides a hint for the chunk size to use
   156  	// when writing to the registry. If it's zero, a suitable default will be chosen.
   157  	// It might be larger if the underlying registry requires that.
   158  	//
   159  	// The context remains active as long as the BlobWriter is around: if it's
   160  	// cancelled, it should cause any blocked BlobWriter operations to terminate.
   161  	PushBlobChunked(ctx context.Context, repo string, chunkSize int) (BlobWriter, error)
   162  
   163  	// PushBlobChunkedResume resumes a previous push of a blob started with PushBlobChunked.
   164  	// The id should be the value returned from [BlobWriter.ID] from the previous push.
   165  	// and the offset should be the value returned from [BlobWriter.Size].
   166  	//
   167  	// The offset and chunkSize should similarly be obtained from the previous [BlobWriter]
   168  	// via the [BlobWriter.Size] and [BlobWriter.ChunkSize] methods.
   169  	// Alternatively, set offset to -1 to continue where the last write left off,
   170  	// and to only use chunkSize as a hint like in PushBlobChunked.
   171  	//
   172  	// The context remains active as long as the BlobWriter is around: if it's
   173  	// cancelled, it should cause any blocked BlobWriter operations to terminate.
   174  	PushBlobChunkedResume(ctx context.Context, repo, id string, offset int64, chunkSize int) (BlobWriter, error)
   175  
   176  	// MountBlob makes a blob with the given digest that's in fromRepo available
   177  	// in toRepo and returns its canonical descriptor.
   178  	//
   179  	// This avoids the need to pull content down from fromRepo only to push it to r.
   180  	//
   181  	// TODO the mount endpoint doesn't return the size of the content,
   182  	// so to return a correctly populated descriptor, a client will need to make
   183  	// an extra HTTP call to find that out. For now, we'll just say that
   184  	// the descriptor returned from MountBlob might have a zero Size.
   185  	//
   186  	// Errors:
   187  	//	ErrUnsupported (when the repository does not support mounts).
   188  	MountBlob(ctx context.Context, fromRepo, toRepo string, digest Digest) (Descriptor, error)
   189  
   190  	// PushManifest pushes a manifest with the given media type and contents.
   191  	// If tag is non-empty, the tag with that name will be pointed at the manifest.
   192  	//
   193  	// It returns a descriptor suitable for accessing the manfiest.
   194  	PushManifest(ctx context.Context, repo string, tag string, contents []byte, mediaType string) (Descriptor, error)
   195  }
   196  
   197  // Deleter defines registry actions that delete objects from the registry.
   198  type Deleter interface {
   199  	// DeleteBlob deletes the blob with the given digest in the given repository.
   200  	DeleteBlob(ctx context.Context, repo string, digest Digest) error
   201  
   202  	// DeleteManifest deletes the manifest with the given digest in the given repository.
   203  	DeleteManifest(ctx context.Context, repo string, digest Digest) error
   204  
   205  	// DeleteTag deletes the manifest with the given tag in the given repository.
   206  	// TODO does this delete the tag only, or the manifest too?
   207  	DeleteTag(ctx context.Context, repo string, name string) error
   208  }
   209  
   210  // Lister defines registry operations that enumerate objects within the registry.
   211  // TODO support resumption from a given point.
   212  type Lister interface {
   213  	// Repositories returns an iterator that can be used to iterate
   214  	// over all the repositories in the registry in lexical order.
   215  	// If startAfter is non-empty, the iteration starts lexically
   216  	// after, but not including, that repository.
   217  	Repositories(ctx context.Context, startAfter string) Seq[string]
   218  
   219  	// Tags returns an iterator that can be used to iterate over all
   220  	// the tags in the given repository in lexical order. If
   221  	// startAfter is non-empty, the tags start lexically after, but
   222  	// not including that tag.
   223  	Tags(ctx context.Context, repo string, startAfter string) Seq[string]
   224  
   225  	// Referrers returns an iterator that can be used to iterate over all
   226  	// the manifests that have the given digest as their Subject.
   227  	// If artifactType is non-zero, the results will be restricted to
   228  	// only manifests with that type.
   229  	// TODO is it possible to ask for multiple artifact types?
   230  	Referrers(ctx context.Context, repo string, digest Digest, artifactType string) Seq[Descriptor]
   231  }
   232  
   233  // BlobWriter provides a handle for uploading a blob to a registry.
   234  type BlobWriter interface {
   235  	// Write writes more data to the blob. When resuming, the
   236  	// caller must start writing data from Size bytes into the content.
   237  	io.Writer
   238  
   239  	// Closer closes the writer but does not abort. The blob write
   240  	// can later be resumed.
   241  	io.Closer
   242  
   243  	// Size returns the number of bytes written to this blob.
   244  	Size() int64
   245  
   246  	// ChunkSize returns the maximum number of bytes to upload at a single time.
   247  	// This number must meet the minimum given by the registry
   248  	// and should otherwise follow the hint given by the user.
   249  	ChunkSize() int
   250  
   251  	// ID returns the opaque identifier for this writer. The returned value
   252  	// can be passed to PushBlobChunked to resume the write.
   253  	// It is only valid before Write has been called or after Close has
   254  	// been called.
   255  	ID() string
   256  
   257  	// Commit completes the blob writer process. The content is verified
   258  	// against the provided digest, and a canonical descriptor for it is returned.
   259  	Commit(digest Digest) (Descriptor, error)
   260  
   261  	// Cancel ends the blob write without storing any data and frees any
   262  	// associated resources. Any data written thus far will be lost. Cancel
   263  	// implementations should allow multiple calls even after a commit that
   264  	// result in a no-op. This allows use of Cancel in a defer statement,
   265  	// increasing the assurance that it is correctly called.
   266  	Cancel() error
   267  }
   268  
   269  // BlobReader provides the contents of a given blob or manifest.
   270  type BlobReader interface {
   271  	io.ReadCloser
   272  	// Descriptor returns the descriptor for the blob.
   273  	Descriptor() Descriptor
   274  }
   275  

View as plain text