Skip to content

Commit

Permalink
feat: added blob mounting support
Browse files Browse the repository at this point in the history
Implements a WithMount method on CopyGraphOptions

Also allows for getContent to return ErrUnsupported to fall back to default behavior.

Signed-off-by: Kyle M. Tarplee <[email protected]>
  • Loading branch information
ktarplee committed Oct 31, 2023
1 parent 7ca7f2c commit 75c7e96
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 0 deletions.
45 changes: 45 additions & 0 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
const defaultConcurrency int = 3 // This value is consistent with dockerd and containerd.

// ErrSkipDesc signal to stop copying a descriptor. When returned from PreCopy the blob must exist in the target.
// This can be used to signal that a blob has been "Mounted" into the target repository.
var ErrSkipDesc = errors.New("skip descriptor")

// DefaultCopyOptions provides the default CopyOptions.
Expand Down Expand Up @@ -111,6 +112,50 @@ type CopyGraphOptions struct {
FindSuccessors func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error)
}

// WithMount enabled cross repository blob mounting.
// sourceReference is the repository to use for mounting (the mount point).
// mounter is the destination for the mount (a well-known implementation of this is *registry.Repository representing the target).
// onMounted is called (if provided) when the blob is mounted.
// The original PreCopy hook is called only on copy, and there fore not when the blob is mounted.
func (opts *CopyGraphOptions) WithMount(sourceRepository string, mounter registry.Mounter, onMounted func(context.Context, ocispec.Descriptor)) {
preCopy := opts.PreCopy
opts.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
// Only care to mount blobs
if descriptor.IsManifest(desc) {
return preCopy(ctx, desc)
}

var mountFailed bool
getContent := func() (io.ReadCloser, error) {
// call the original PreCopy function if it exists
if preCopy != nil {
if err := preCopy(ctx, desc); err != nil {
return nil, err
}
}
// the invocation of getContent indicates that mounting has failed
mountFailed = true

// To avoid needing a content.Fetcher as an input argument we simply fall back to the default behavior
// as if getContent was nil
return nil, errdef.ErrUnsupported
}

// Mount or copy
if err := mounter.Mount(ctx, desc, sourceRepository, getContent); err != nil {
return err
}

if !mountFailed && onMounted != nil {
onMounted(ctx, desc)
}

// Mount succeeded or we copied it
// either way we return ErrSkipDesc to signal that the descriptor now exists
return ErrSkipDesc
}
}

// Copy copies a rooted directed acyclic graph (DAG) with the tagged root node
// in the source Target to the destination Target.
// The destination reference will be the same as the source reference if the
Expand Down
4 changes: 4 additions & 0 deletions registry/remote/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,10 @@ func (s *blobStore) Mount(ctx context.Context, desc ocispec.Descriptor, fromRepo
var r io.ReadCloser
if getContent != nil {
r, err = getContent()
if errors.Is(err, errdef.ErrUnsupported) {
// getContent can return a ErrUnsupported to fallback to the default copy operation
r, err = s.sibling(fromRepo).Fetch(ctx, desc)
}
} else {
r, err = s.sibling(fromRepo).Fetch(ctx, desc)
}
Expand Down

0 comments on commit 75c7e96

Please sign in to comment.