Skip to content

Commit ed1a257

Browse files
authored
Extended Copy (#130)
Support Extended Copy (Reference: https://oras.land/client_libraries/#extended-copy) Signed-off-by: Lixia (Sylvia) Lei <lixia_lei@outlook.com>
1 parent c4a672a commit ed1a257

File tree

5 files changed

+487
-5
lines changed

5 files changed

+487
-5
lines changed

content/storage.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ type Storage interface {
4949
Exists(ctx context.Context, target ocispec.Descriptor) (bool, error)
5050
}
5151

52+
// GraphStorage represents a CAS that supports parent node finding.
53+
type GraphStorage interface {
54+
Storage
55+
UpEdgeFinder
56+
}
57+
5258
// Deleter removes content.
5359
// Deleter is an extension of Storage.
5460
type Deleter interface {

copy.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func CopyGraph(ctx context.Context, src, dst content.Storage, root ocispec.Descr
109109
return nil, err
110110
}
111111
if !exists {
112-
return nil, copyDescriptor(ctx, src, dst, desc)
112+
return nil, copyNode(ctx, src, dst, desc)
113113
}
114114

115115
// for non-leaf nodes, wait for its down edges to complete
@@ -128,16 +128,15 @@ func CopyGraph(ctx context.Context, src, dst content.Storage, root ocispec.Descr
128128
return nil, ctx.Err()
129129
}
130130
}
131-
return nil, copyDescriptor(ctx, proxy.Cache, dst, desc)
131+
return nil, copyNode(ctx, proxy.Cache, dst, desc)
132132
})
133133

134134
// traverse the graph
135135
return graph.Dispatch(ctx, preHandler, postHandler, nil, root)
136136
}
137137

138-
// copyDescriptor copies a single content from the source CAS to the destination
139-
// CAS.
140-
func copyDescriptor(ctx context.Context, src, dst content.Storage, desc ocispec.Descriptor) error {
138+
// copyNode copies a single content from the source CAS to the destination CAS.
139+
func copyNode(ctx context.Context, src, dst content.Storage, desc ocispec.Descriptor) error {
141140
rc, err := src.Fetch(ctx, desc)
142141
if err != nil {
143142
return err

extendedcopy.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 oras
17+
18+
import (
19+
"context"
20+
"errors"
21+
22+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
23+
"oras.land/oras-go/v2/content"
24+
"oras.land/oras-go/v2/internal/descriptor"
25+
)
26+
27+
// ExtendedCopy copies the directed acyclic graph (DAG) that are reachable from
28+
// the given tagged node from the source GraphTarget to the destination Target.
29+
// The destination reference will be the same as the source reference if the
30+
// destination reference is left blank.
31+
// Returns the descriptor of the tagged node on successful copy.
32+
func ExtendedCopy(ctx context.Context, src GraphTarget, srcRef string, dst Target, dstRef string) (ocispec.Descriptor, error) {
33+
if src == nil {
34+
return ocispec.Descriptor{}, errors.New("nil source graph target")
35+
}
36+
if dst == nil {
37+
return ocispec.Descriptor{}, errors.New("nil destination target")
38+
}
39+
if dstRef == "" {
40+
dstRef = srcRef
41+
}
42+
43+
node, err := src.Resolve(ctx, srcRef)
44+
if err != nil {
45+
return ocispec.Descriptor{}, err
46+
}
47+
48+
if err := ExtendedCopyGraph(ctx, src, dst, node); err != nil {
49+
return ocispec.Descriptor{}, err
50+
}
51+
52+
if err := dst.Tag(ctx, node, dstRef); err != nil {
53+
return ocispec.Descriptor{}, err
54+
}
55+
56+
return node, nil
57+
}
58+
59+
// ExtendedCopyGraph copies the directed acyclic graph (DAG) that are reachable
60+
// from the given node from the source GraphStorage to the destination Storage.
61+
func ExtendedCopyGraph(ctx context.Context, src content.GraphStorage, dst content.Storage, node ocispec.Descriptor) error {
62+
roots, err := findRoots(ctx, src, node)
63+
if err != nil {
64+
return err
65+
}
66+
67+
// copy the sub-DAGs rooted by the root nodes
68+
for _, root := range roots {
69+
if err := CopyGraph(ctx, src, dst, root); err != nil {
70+
return err
71+
}
72+
}
73+
74+
return nil
75+
}
76+
77+
// findRoots finds the root nodes reachable from the given node through a
78+
// depth-first search.
79+
func findRoots(ctx context.Context, finder content.UpEdgeFinder, node ocispec.Descriptor) (map[descriptor.Descriptor]ocispec.Descriptor, error) {
80+
roots := make(map[descriptor.Descriptor]ocispec.Descriptor)
81+
visited := make(map[descriptor.Descriptor]bool)
82+
var stack []ocispec.Descriptor
83+
84+
// push the initial node to the stack
85+
stack = append(stack, node)
86+
for len(stack) > 0 {
87+
// pop the current node from the stack
88+
top := len(stack) - 1
89+
current := stack[top]
90+
stack = stack[:top]
91+
92+
currentKey := descriptor.FromOCI(current)
93+
if visited[currentKey] {
94+
// skip the current node if it has been visited
95+
continue
96+
}
97+
visited[currentKey] = true
98+
99+
upEdges, err := finder.UpEdges(ctx, current)
100+
if err != nil {
101+
return nil, err
102+
}
103+
104+
// The current node has no parent node,
105+
// which means it is a root node of a sub-DAG.
106+
if len(upEdges) == 0 {
107+
if _, exists := roots[currentKey]; !exists {
108+
roots[currentKey] = current
109+
}
110+
continue
111+
}
112+
113+
// The current node has parent nodes, which means it is NOT a root node.
114+
// Push the parent nodes to the stack and keep finding from there.
115+
for _, upEdge := range upEdges {
116+
upEdgeKey := descriptor.FromOCI(upEdge)
117+
if !visited[upEdgeKey] {
118+
stack = append(stack, upEdge)
119+
}
120+
}
121+
}
122+
123+
return roots, nil
124+
}

0 commit comments

Comments
 (0)