1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package tarball
16
17 import (
18 "archive/tar"
19 "bytes"
20 "encoding/json"
21 "errors"
22 "fmt"
23 "io"
24 "os"
25 "path"
26 "path/filepath"
27 "sync"
28
29 comp "github.com/google/go-containerregistry/internal/compression"
30 "github.com/google/go-containerregistry/pkg/compression"
31 "github.com/google/go-containerregistry/pkg/name"
32 v1 "github.com/google/go-containerregistry/pkg/v1"
33 "github.com/google/go-containerregistry/pkg/v1/partial"
34 "github.com/google/go-containerregistry/pkg/v1/types"
35 )
36
37 type image struct {
38 opener Opener
39 manifest *Manifest
40 config []byte
41 imgDescriptor *Descriptor
42
43 tag *name.Tag
44 }
45
46 type uncompressedImage struct {
47 *image
48 }
49
50 type compressedImage struct {
51 *image
52 manifestLock sync.Mutex
53 manifest *v1.Manifest
54 }
55
56 var _ partial.UncompressedImageCore = (*uncompressedImage)(nil)
57 var _ partial.CompressedImageCore = (*compressedImage)(nil)
58
59
60 type Opener func() (io.ReadCloser, error)
61
62 func pathOpener(path string) Opener {
63 return func() (io.ReadCloser, error) {
64 return os.Open(path)
65 }
66 }
67
68
69 func ImageFromPath(path string, tag *name.Tag) (v1.Image, error) {
70 return Image(pathOpener(path), tag)
71 }
72
73
74 func LoadManifest(opener Opener) (Manifest, error) {
75 m, err := extractFileFromTar(opener, "manifest.json")
76 if err != nil {
77 return nil, err
78 }
79 defer m.Close()
80
81 var manifest Manifest
82
83 if err := json.NewDecoder(m).Decode(&manifest); err != nil {
84 return nil, err
85 }
86 return manifest, nil
87 }
88
89
90 func Image(opener Opener, tag *name.Tag) (v1.Image, error) {
91 img := &image{
92 opener: opener,
93 tag: tag,
94 }
95 if err := img.loadTarDescriptorAndConfig(); err != nil {
96 return nil, err
97 }
98
99
100 if len(img.imgDescriptor.Layers) > 0 {
101 compressed, err := img.areLayersCompressed()
102 if err != nil {
103 return nil, err
104 }
105 if compressed {
106 c := compressedImage{
107 image: img,
108 }
109 return partial.CompressedToImage(&c)
110 }
111 }
112
113 uc := uncompressedImage{
114 image: img,
115 }
116 return partial.UncompressedToImage(&uc)
117 }
118
119 func (i *image) MediaType() (types.MediaType, error) {
120 return types.DockerManifestSchema2, nil
121 }
122
123
124 type Descriptor struct {
125 Config string
126 RepoTags []string
127 Layers []string
128
129
130 LayerSources map[v1.Hash]v1.Descriptor `json:",omitempty"`
131 }
132
133
134 type Manifest []Descriptor
135
136 func (m Manifest) findDescriptor(tag *name.Tag) (*Descriptor, error) {
137 if tag == nil {
138 if len(m) != 1 {
139 return nil, errors.New("tarball must contain only a single image to be used with tarball.Image")
140 }
141 return &(m)[0], nil
142 }
143 for _, img := range m {
144 for _, tagStr := range img.RepoTags {
145 repoTag, err := name.NewTag(tagStr)
146 if err != nil {
147 return nil, err
148 }
149
150
151 if repoTag.Name() == tag.Name() {
152 return &img, nil
153 }
154 }
155 }
156 return nil, fmt.Errorf("tag %s not found in tarball", tag)
157 }
158
159 func (i *image) areLayersCompressed() (bool, error) {
160 if len(i.imgDescriptor.Layers) == 0 {
161 return false, errors.New("0 layers found in image")
162 }
163 layer := i.imgDescriptor.Layers[0]
164 blob, err := extractFileFromTar(i.opener, layer)
165 if err != nil {
166 return false, err
167 }
168 defer blob.Close()
169
170 cp, _, err := comp.PeekCompression(blob)
171 if err != nil {
172 return false, err
173 }
174
175 return cp != compression.None, nil
176 }
177
178 func (i *image) loadTarDescriptorAndConfig() error {
179 m, err := extractFileFromTar(i.opener, "manifest.json")
180 if err != nil {
181 return err
182 }
183 defer m.Close()
184
185 if err := json.NewDecoder(m).Decode(&i.manifest); err != nil {
186 return err
187 }
188
189 if i.manifest == nil {
190 return errors.New("no valid manifest.json in tarball")
191 }
192
193 i.imgDescriptor, err = i.manifest.findDescriptor(i.tag)
194 if err != nil {
195 return err
196 }
197
198 cfg, err := extractFileFromTar(i.opener, i.imgDescriptor.Config)
199 if err != nil {
200 return err
201 }
202 defer cfg.Close()
203
204 i.config, err = io.ReadAll(cfg)
205 if err != nil {
206 return err
207 }
208 return nil
209 }
210
211 func (i *image) RawConfigFile() ([]byte, error) {
212 return i.config, nil
213 }
214
215
216 type tarFile struct {
217 io.Reader
218 io.Closer
219 }
220
221 func extractFileFromTar(opener Opener, filePath string) (io.ReadCloser, error) {
222 f, err := opener()
223 if err != nil {
224 return nil, err
225 }
226 needClose := true
227 defer func() {
228 if needClose {
229 f.Close()
230 }
231 }()
232
233 tf := tar.NewReader(f)
234 for {
235 hdr, err := tf.Next()
236 if errors.Is(err, io.EOF) {
237 break
238 }
239 if err != nil {
240 return nil, err
241 }
242 if hdr.Name == filePath {
243 if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink {
244 currentDir := filepath.Dir(filePath)
245 return extractFileFromTar(opener, path.Join(currentDir, path.Clean(hdr.Linkname)))
246 }
247 needClose = false
248 return tarFile{
249 Reader: tf,
250 Closer: f,
251 }, nil
252 }
253 }
254 return nil, fmt.Errorf("file %s not found in tar", filePath)
255 }
256
257
258 type uncompressedLayerFromTarball struct {
259 diffID v1.Hash
260 mediaType types.MediaType
261 opener Opener
262 filePath string
263 }
264
265
266
267
268 type foreignUncompressedLayer struct {
269 uncompressedLayerFromTarball
270 desc v1.Descriptor
271 }
272
273 func (fl *foreignUncompressedLayer) Descriptor() (*v1.Descriptor, error) {
274 return &fl.desc, nil
275 }
276
277
278 func (ulft *uncompressedLayerFromTarball) DiffID() (v1.Hash, error) {
279 return ulft.diffID, nil
280 }
281
282
283 func (ulft *uncompressedLayerFromTarball) Uncompressed() (io.ReadCloser, error) {
284 return extractFileFromTar(ulft.opener, ulft.filePath)
285 }
286
287 func (ulft *uncompressedLayerFromTarball) MediaType() (types.MediaType, error) {
288 return ulft.mediaType, nil
289 }
290
291 func (i *uncompressedImage) LayerByDiffID(h v1.Hash) (partial.UncompressedLayer, error) {
292 cfg, err := partial.ConfigFile(i)
293 if err != nil {
294 return nil, err
295 }
296 for idx, diffID := range cfg.RootFS.DiffIDs {
297 if diffID == h {
298
299
300
301 mt := types.DockerLayer
302 bd, ok := i.imgDescriptor.LayerSources[h]
303 if ok {
304
305
306
307 docker25workaround := bd.MediaType == types.DockerUncompressedLayer || bd.MediaType == types.OCIUncompressedLayer
308
309 if !docker25workaround {
310
311 return &foreignUncompressedLayer{
312 uncompressedLayerFromTarball: uncompressedLayerFromTarball{
313 diffID: diffID,
314 mediaType: bd.MediaType,
315 opener: i.opener,
316 filePath: i.imgDescriptor.Layers[idx],
317 },
318 desc: bd,
319 }, nil
320 }
321
322
323 }
324
325 return &uncompressedLayerFromTarball{
326 diffID: diffID,
327 mediaType: mt,
328 opener: i.opener,
329 filePath: i.imgDescriptor.Layers[idx],
330 }, nil
331 }
332 }
333 return nil, fmt.Errorf("diff id %q not found", h)
334 }
335
336 func (c *compressedImage) Manifest() (*v1.Manifest, error) {
337 c.manifestLock.Lock()
338 defer c.manifestLock.Unlock()
339 if c.manifest != nil {
340 return c.manifest, nil
341 }
342
343 b, err := c.RawConfigFile()
344 if err != nil {
345 return nil, err
346 }
347
348 cfgHash, cfgSize, err := v1.SHA256(bytes.NewReader(b))
349 if err != nil {
350 return nil, err
351 }
352
353 c.manifest = &v1.Manifest{
354 SchemaVersion: 2,
355 MediaType: types.DockerManifestSchema2,
356 Config: v1.Descriptor{
357 MediaType: types.DockerConfigJSON,
358 Size: cfgSize,
359 Digest: cfgHash,
360 },
361 }
362
363 for i, p := range c.imgDescriptor.Layers {
364 cfg, err := partial.ConfigFile(c)
365 if err != nil {
366 return nil, err
367 }
368 diffid := cfg.RootFS.DiffIDs[i]
369 if d, ok := c.imgDescriptor.LayerSources[diffid]; ok {
370
371
372 c.manifest.Layers = append(c.manifest.Layers, d)
373 } else {
374 l, err := extractFileFromTar(c.opener, p)
375 if err != nil {
376 return nil, err
377 }
378 defer l.Close()
379 sha, size, err := v1.SHA256(l)
380 if err != nil {
381 return nil, err
382 }
383 c.manifest.Layers = append(c.manifest.Layers, v1.Descriptor{
384 MediaType: types.DockerLayer,
385 Size: size,
386 Digest: sha,
387 })
388 }
389 }
390 return c.manifest, nil
391 }
392
393 func (c *compressedImage) RawManifest() ([]byte, error) {
394 return partial.RawManifest(c)
395 }
396
397
398 type compressedLayerFromTarball struct {
399 desc v1.Descriptor
400 opener Opener
401 filePath string
402 }
403
404
405 func (clft *compressedLayerFromTarball) Digest() (v1.Hash, error) {
406 return clft.desc.Digest, nil
407 }
408
409
410 func (clft *compressedLayerFromTarball) Compressed() (io.ReadCloser, error) {
411 return extractFileFromTar(clft.opener, clft.filePath)
412 }
413
414
415 func (clft *compressedLayerFromTarball) MediaType() (types.MediaType, error) {
416 return clft.desc.MediaType, nil
417 }
418
419
420 func (clft *compressedLayerFromTarball) Size() (int64, error) {
421 return clft.desc.Size, nil
422 }
423
424 func (c *compressedImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
425 m, err := c.Manifest()
426 if err != nil {
427 return nil, err
428 }
429 for i, l := range m.Layers {
430 if l.Digest == h {
431 fp := c.imgDescriptor.Layers[i]
432 return &compressedLayerFromTarball{
433 desc: l,
434 opener: c.opener,
435 filePath: fp,
436 }, nil
437 }
438 }
439 return nil, fmt.Errorf("blob %v not found", h)
440 }
441
View as plain text