Skip to content

Commit 8d3f4c1

Browse files
committed
feat: added blob mounting support for oras Copy functions
Adds MountFrom and OnMounted to CopyGraphOptions. Allows for trying to mount from multiple repositories. Signed-off-by: Kyle M. Tarplee <[email protected]>
1 parent d1becd5 commit 8d3f4c1

File tree

4 files changed

+571
-13
lines changed

4 files changed

+571
-13
lines changed

copy.go

+81-1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ type CopyGraphOptions struct {
105105
// OnCopySkipped will be called when the sub-DAG rooted by the current node
106106
// is skipped.
107107
OnCopySkipped func(ctx context.Context, desc ocispec.Descriptor) error
108+
// MountFrom returns the candidate repositories that desc may be mounted from.
109+
// The OCI references will be tried in turn. If mounting fails on all of them,
110+
// then it falls back to a copy.
111+
MountFrom func(ctx context.Context, desc ocispec.Descriptor) ([]string, error)
112+
// OnMounted will be invoked when desc is mounted.
113+
OnMounted func(ctx context.Context, desc ocispec.Descriptor) error
108114
// FindSuccessors finds the successors of the current node.
109115
// fetcher provides cached access to the source storage, and is suitable
110116
// for fetching non-leaf nodes like manifests. Since anything fetched from
@@ -259,12 +265,86 @@ func copyGraph(ctx context.Context, src content.ReadOnlyStorage, dst content.Sto
259265
if exists {
260266
return copyNode(ctx, proxy.Cache, dst, desc, opts)
261267
}
262-
return copyNode(ctx, src, dst, desc, opts)
268+
return mountOrCopyNode(ctx, src, dst, desc, opts)
263269
}
264270

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

274+
// mountOrCopyNode tries to mount the node, if not falls back to copying.
275+
func mountOrCopyNode(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, desc ocispec.Descriptor, opts CopyGraphOptions) error {
276+
// Need MountFrom and it must be a blob
277+
if opts.MountFrom == nil || descriptor.IsManifest(desc) {
278+
return copyNode(ctx, src, dst, desc, opts)
279+
}
280+
281+
mounter, ok := dst.(registry.Mounter)
282+
if !ok {
283+
// mounting is not supported by the destination
284+
return copyNode(ctx, src, dst, desc, opts)
285+
}
286+
287+
sourceRepositories, err := opts.MountFrom(ctx, desc)
288+
if err != nil {
289+
// Technically this error is not fatal, we can still attempt to copy the node
290+
// But for consistency with the other callbacks we bail out.
291+
return err
292+
}
293+
294+
if len(sourceRepositories) == 0 {
295+
return copyNode(ctx, src, dst, desc, opts)
296+
}
297+
298+
skipContent := errors.New("skip content")
299+
for i, sourceRepository := range sourceRepositories {
300+
// try mounting this source repository
301+
var mountFailed bool
302+
getContent := func() (io.ReadCloser, error) {
303+
// the invocation of getContent indicates that mounting has failed
304+
mountFailed = true
305+
306+
if len(sourceRepositories)-1 == i {
307+
// this is the last iteration so we need to actually get the content and do the copy
308+
309+
// call the original PreCopy function if it exists
310+
if opts.PreCopy != nil {
311+
if err := opts.PreCopy(ctx, desc); err != nil {
312+
return nil, err
313+
}
314+
}
315+
return src.Fetch(ctx, desc)
316+
}
317+
318+
// We want to return an error that we will test for from mounter.Mount()
319+
return nil, skipContent
320+
}
321+
322+
// Mount or copy
323+
if err := mounter.Mount(ctx, desc, sourceRepository, getContent); err != nil && !errors.Is(err, skipContent) {
324+
return err
325+
}
326+
327+
if !mountFailed {
328+
// mounted, success
329+
if opts.OnMounted != nil {
330+
if err := opts.OnMounted(ctx, desc); err != nil {
331+
return err
332+
}
333+
}
334+
return nil
335+
}
336+
}
337+
338+
// we copied it
339+
if opts.PostCopy != nil {
340+
if err := opts.PostCopy(ctx, desc); err != nil {
341+
return err
342+
}
343+
}
344+
345+
return nil
346+
}
347+
268348
// doCopyNode copies a single content from the source CAS to the destination CAS.
269349
func doCopyNode(ctx context.Context, src content.ReadOnlyStorage, dst content.Storage, desc ocispec.Descriptor) error {
270350
rc, err := src.Fetch(ctx, desc)

0 commit comments

Comments
 (0)