1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package crane
16
17 import (
18 "errors"
19 "fmt"
20 "net/http"
21
22 "github.com/google/go-containerregistry/pkg/logs"
23 "github.com/google/go-containerregistry/pkg/name"
24 "github.com/google/go-containerregistry/pkg/v1/remote"
25 "github.com/google/go-containerregistry/pkg/v1/remote/transport"
26 "golang.org/x/sync/errgroup"
27 )
28
29
30 func Copy(src, dst string, opt ...Option) error {
31 o := makeOptions(opt...)
32 srcRef, err := name.ParseReference(src, o.Name...)
33 if err != nil {
34 return fmt.Errorf("parsing reference %q: %w", src, err)
35 }
36
37 dstRef, err := name.ParseReference(dst, o.Name...)
38 if err != nil {
39 return fmt.Errorf("parsing reference for %q: %w", dst, err)
40 }
41
42 puller, err := remote.NewPuller(o.Remote...)
43 if err != nil {
44 return err
45 }
46
47 if tag, ok := dstRef.(name.Tag); ok {
48 if o.noclobber {
49 logs.Progress.Printf("Checking existing tag %v", tag)
50 head, err := puller.Head(o.ctx, tag)
51 var terr *transport.Error
52 if errors.As(err, &terr) {
53 if terr.StatusCode != http.StatusNotFound && terr.StatusCode != http.StatusForbidden {
54 return err
55 }
56 } else if err != nil {
57 return err
58 }
59
60 if head != nil {
61 return fmt.Errorf("refusing to clobber existing tag %s@%s", tag, head.Digest)
62 }
63 }
64 }
65
66 pusher, err := remote.NewPusher(o.Remote...)
67 if err != nil {
68 return err
69 }
70
71 logs.Progress.Printf("Copying from %v to %v", srcRef, dstRef)
72 desc, err := puller.Get(o.ctx, srcRef)
73 if err != nil {
74 return fmt.Errorf("fetching %q: %w", src, err)
75 }
76
77 if o.Platform == nil {
78 return pusher.Push(o.ctx, dstRef, desc)
79 }
80
81
82 img, err := desc.Image()
83 if err != nil {
84 return err
85 }
86 return pusher.Push(o.ctx, dstRef, img)
87 }
88
89
90 func CopyRepository(src, dst string, opt ...Option) error {
91 o := makeOptions(opt...)
92
93 srcRepo, err := name.NewRepository(src, o.Name...)
94 if err != nil {
95 return err
96 }
97
98 dstRepo, err := name.NewRepository(dst, o.Name...)
99 if err != nil {
100 return fmt.Errorf("parsing reference for %q: %w", dst, err)
101 }
102
103 puller, err := remote.NewPuller(o.Remote...)
104 if err != nil {
105 return err
106 }
107
108 ignoredTags := map[string]struct{}{}
109 if o.noclobber {
110
111 have, err := puller.List(o.ctx, dstRepo)
112 if err != nil {
113 var terr *transport.Error
114 if errors.As(err, &terr) {
115
116
117 if !(terr.StatusCode == http.StatusNotFound || terr.StatusCode == http.StatusForbidden) {
118 return err
119 }
120 } else {
121 return err
122 }
123 }
124 for _, tag := range have {
125 ignoredTags[tag] = struct{}{}
126 }
127 }
128
129 pusher, err := remote.NewPusher(o.Remote...)
130 if err != nil {
131 return err
132 }
133
134 lister, err := puller.Lister(o.ctx, srcRepo)
135 if err != nil {
136 return err
137 }
138
139 g, ctx := errgroup.WithContext(o.ctx)
140 g.SetLimit(o.jobs)
141
142 for lister.HasNext() {
143 tags, err := lister.Next(ctx)
144 if err != nil {
145 return err
146 }
147
148 for _, tag := range tags.Tags {
149 tag := tag
150
151 if o.noclobber {
152 if _, ok := ignoredTags[tag]; ok {
153 logs.Progress.Printf("Skipping %s due to no-clobber", tag)
154 continue
155 }
156 }
157
158 g.Go(func() error {
159 srcTag, err := name.ParseReference(src+":"+tag, o.Name...)
160 if err != nil {
161 return fmt.Errorf("failed to parse tag: %w", err)
162 }
163 dstTag, err := name.ParseReference(dst+":"+tag, o.Name...)
164 if err != nil {
165 return fmt.Errorf("failed to parse tag: %w", err)
166 }
167
168 logs.Progress.Printf("Fetching %s", srcTag)
169 desc, err := puller.Get(ctx, srcTag)
170 if err != nil {
171 return err
172 }
173
174 logs.Progress.Printf("Pushing %s", dstTag)
175 return pusher.Push(ctx, dstTag, desc)
176 })
177 }
178 }
179
180 return g.Wait()
181 }
182
View as plain text