Skip to content

Commit

Permalink
feat: added blob mounting support for oras Copy functions
Browse files Browse the repository at this point in the history
Adds MountFrom and OnMounted to CopyGraphOptions.
Allows for trying to mount from multiple repositories.

Signed-off-by: Kyle M. Tarplee <[email protected]>
  • Loading branch information
ktarplee committed Jan 5, 2024
1 parent d1becd5 commit 8d3f4c1
Show file tree
Hide file tree
Showing 4 changed files with 571 additions and 13 deletions.
82 changes: 81 additions & 1 deletion copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ type CopyGraphOptions struct {
// OnCopySkipped will be called when the sub-DAG rooted by the current node
// is skipped.
OnCopySkipped func(ctx context.Context, desc ocispec.Descriptor) error
// MountFrom returns the candidate repositories that desc may be mounted from.
// The OCI references will be tried in turn. If mounting fails on all of them,
// then it falls back to a copy.
MountFrom func(ctx context.Context, desc ocispec.Descriptor) ([]string, error)
// OnMounted will be invoked when desc is mounted.
OnMounted func(ctx context.Context, desc ocispec.Descriptor) error
// FindSuccessors finds the successors of the current node.
// fetcher provides cached access to the source storage, and is suitable
// for fetching non-leaf nodes like manifests. Since anything fetched from
Expand Down Expand Up @@ -259,12 +265,86 @@ func copyGraph(ctx context.Context, src content.ReadOnlyStorage, dst content.Sto
if exists {
return copyNode(ctx, proxy.Cache, dst, desc, opts)
}
return copyNode(ctx, src, dst, desc, opts)
return mountOrCopyNode(ctx, src, dst, desc, opts)
}

return syncutil.Go(ctx, limiter, fn, root)
}

// mountOrCopyNode tries to mount the node, if not falls back to copying.
func mountOrCopyNode(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, desc ocispec.Descriptor, opts CopyGraphOptions) error {
// Need MountFrom and it must be a blob
if opts.MountFrom == nil || descriptor.IsManifest(desc) {
return copyNode(ctx, src, dst, desc, opts)
}

mounter, ok := dst.(registry.Mounter)
if !ok {
// mounting is not supported by the destination
return copyNode(ctx, src, dst, desc, opts)
}

sourceRepositories, err := opts.MountFrom(ctx, desc)
if err != nil {
// Technically this error is not fatal, we can still attempt to copy the node
// But for consistency with the other callbacks we bail out.
return err
}

if len(sourceRepositories) == 0 {
return copyNode(ctx, src, dst, desc, opts)
}

skipContent := errors.New("skip content")
for i, sourceRepository := range sourceRepositories {
// try mounting this source repository
var mountFailed bool
getContent := func() (io.ReadCloser, error) {
// the invocation of getContent indicates that mounting has failed
mountFailed = true

if len(sourceRepositories)-1 == i {
// this is the last iteration so we need to actually get the content and do the copy

// call the original PreCopy function if it exists
if opts.PreCopy != nil {
if err := opts.PreCopy(ctx, desc); err != nil {
return nil, err
}

Check warning on line 313 in copy.go

View check run for this annotation

Codecov / codecov/patch

copy.go#L312-L313

Added lines #L312 - L313 were not covered by tests
}
return src.Fetch(ctx, desc)
}

// We want to return an error that we will test for from mounter.Mount()
return nil, skipContent
}

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

Check warning on line 325 in copy.go

View check run for this annotation

Codecov / codecov/patch

copy.go#L324-L325

Added lines #L324 - L325 were not covered by tests

if !mountFailed {
// mounted, success
if opts.OnMounted != nil {
if err := opts.OnMounted(ctx, desc); err != nil {
return err
}
}
return nil
}
}

// we copied it
if opts.PostCopy != nil {
if err := opts.PostCopy(ctx, desc); err != nil {
return err
}

Check warning on line 342 in copy.go

View check run for this annotation

Codecov / codecov/patch

copy.go#L341-L342

Added lines #L341 - L342 were not covered by tests
}

return nil
}

// doCopyNode copies a single content from the source CAS to the destination CAS.
func doCopyNode(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, desc ocispec.Descriptor) error {
rc, err := src.Fetch(ctx, desc)
Expand Down
Loading

0 comments on commit 8d3f4c1

Please sign in to comment.