-
Notifications
You must be signed in to change notification settings - Fork 100
/
Copy pathcopy.go
150 lines (130 loc) · 4.15 KB
/
copy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oras
import (
"context"
"errors"
"fmt"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/errdef"
"oras.land/oras-go/v2/internal/cas"
"oras.land/oras-go/v2/internal/graph"
"oras.land/oras-go/v2/internal/status"
)
// 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
// destination reference is left blank.
// Returns the descriptor of the root node on successful copy.
func Copy(ctx context.Context, src Target, srcRef string, dst Target, dstRef string) (ocispec.Descriptor, error) {
if src == nil {
return ocispec.Descriptor{}, errors.New("nil source target")
}
if dst == nil {
return ocispec.Descriptor{}, errors.New("nil destination target")
}
if dstRef == "" {
dstRef = srcRef
}
root, err := src.Resolve(ctx, srcRef)
if err != nil {
return ocispec.Descriptor{}, err
}
if err := CopyGraph(ctx, src, dst, root); err != nil {
return ocispec.Descriptor{}, err
}
if err := dst.Tag(ctx, root, dstRef); err != nil {
return ocispec.Descriptor{}, err
}
return root, nil
}
// CopyGraph copies a rooted directed acyclic graph (DAG) from the source CAS to
// the destination CAS.
func CopyGraph(ctx context.Context, src, dst content.Storage, root ocispec.Descriptor) error {
// use caching proxy on non-leaf nodes
proxy := cas.NewProxy(src, cas.NewMemory())
// track content status
tracker := status.NewTracker()
// prepare pre-handler
preHandler := graph.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
// skip the descriptor if other go routine is working on it
done, committed := tracker.TryCommit(desc)
if !committed {
return nil, graph.ErrSkipDesc
}
// skip if a rooted sub-DAG exists
exists, err := dst.Exists(ctx, desc)
if err != nil {
return nil, err
}
if exists {
// mark the content as done
close(done)
return nil, graph.ErrSkipDesc
}
// find down edges while non-leaf nodes will be fetched and cached
return content.DownEdges(ctx, proxy, desc)
})
// prepare post-handler
postHandler := graph.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) (_ []ocispec.Descriptor, err error) {
defer func() {
if err == nil {
// mark the content as done on success
done, _ := tracker.TryCommit(desc)
close(done)
}
}()
// leaf nodes does not exist in the cache.
// copy them directly.
exists, err := proxy.Cache.Exists(ctx, desc)
if err != nil {
return nil, err
}
if !exists {
return nil, copyNode(ctx, src, dst, desc)
}
// for non-leaf nodes, wait for its down edges to complete
downEdges, err := content.DownEdges(ctx, proxy, desc)
if err != nil {
return nil, err
}
for _, node := range downEdges {
done, committed := tracker.TryCommit(node)
if committed {
return nil, fmt.Errorf("%s: %s: down edge not committed", desc.Digest, node.Digest)
}
select {
case <-done:
case <-ctx.Done():
return nil, ctx.Err()
}
}
return nil, copyNode(ctx, proxy.Cache, dst, desc)
})
// traverse the graph
return graph.Dispatch(ctx, preHandler, postHandler, nil, root)
}
// copyNode copies a single content from the source CAS to the destination CAS.
func copyNode(ctx context.Context, src, dst content.Storage, desc ocispec.Descriptor) error {
rc, err := src.Fetch(ctx, desc)
if err != nil {
return err
}
defer rc.Close()
err = dst.Push(ctx, desc, rc)
if err != nil && !errors.Is(err, errdef.ErrAlreadyExists) {
return err
}
return nil
}