1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package tarball
16
17 import (
18 "archive/tar"
19 "errors"
20 "fmt"
21 "io"
22 "os"
23 "path/filepath"
24 "strings"
25 "testing"
26
27 "github.com/google/go-containerregistry/internal/compare"
28 "github.com/google/go-containerregistry/pkg/name"
29 v1 "github.com/google/go-containerregistry/pkg/v1"
30 "github.com/google/go-containerregistry/pkg/v1/mutate"
31 "github.com/google/go-containerregistry/pkg/v1/partial"
32 "github.com/google/go-containerregistry/pkg/v1/random"
33 "github.com/google/go-containerregistry/pkg/v1/tarball"
34 "github.com/google/go-containerregistry/pkg/v1/types"
35 "github.com/google/go-containerregistry/pkg/v1/validate"
36 )
37
38 func TestWrite(t *testing.T) {
39
40 fp, err := os.CreateTemp("", "")
41 if err != nil {
42 t.Fatalf("Error creating temp file.")
43 }
44 t.Log(fp.Name())
45 defer fp.Close()
46 defer os.Remove(fp.Name())
47
48
49 randImage, err := random.Image(256, 8)
50 if err != nil {
51 t.Fatalf("Error creating random image: %v", err)
52 }
53 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
54 if err != nil {
55 t.Fatalf("Error creating test tag: %v", err)
56 }
57 o, err := os.Create(fp.Name())
58 if err != nil {
59 t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err)
60 }
61 defer o.Close()
62 if err := Write(tag, randImage, o); err != nil {
63 t.Fatalf("Unexpected error writing tarball: %v", err)
64 }
65
66
67
68 for _, it := range []*name.Tag{nil, &tag} {
69 tarImage, err := tarball.ImageFromPath(fp.Name(), it)
70 if err != nil {
71 t.Fatalf("Unexpected error reading tarball: %v", err)
72 }
73 if err := validate.Image(tarImage); err != nil {
74 t.Errorf("validate.Image: %v", err)
75 }
76 if err := compare.Images(randImage, tarImage); err != nil {
77 t.Errorf("compare.Images: %v", err)
78 }
79 }
80
81
82 fakeTag, err := name.NewTag("gcr.io/notthistag:latest", name.StrictValidation)
83 if err != nil {
84 t.Fatalf("Error generating tag: %v", err)
85 }
86 if _, err := tarball.ImageFromPath(fp.Name(), &fakeTag); err == nil {
87 t.Errorf("Expected error loading tag %v from image", fakeTag)
88 }
89 }
90
91 func TestMultiWriteSameImage(t *testing.T) {
92
93 fp, err := os.CreateTemp("", "")
94 if err != nil {
95 t.Fatalf("Error creating temp file.")
96 }
97 t.Log(fp.Name())
98 defer fp.Close()
99 defer os.Remove(fp.Name())
100
101
102 randImage, err := random.Image(256, 8)
103 if err != nil {
104 t.Fatalf("Error creating random image.")
105 }
106
107
108 tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
109 if err != nil {
110 t.Fatalf("Error creating test tag1.")
111 }
112 tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation)
113 if err != nil {
114 t.Fatalf("Error creating test tag2.")
115 }
116 dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation)
117 if err != nil {
118 t.Fatalf("Error creating test dig3.")
119 }
120 refToImage := make(map[name.Reference]v1.Image)
121 refToImage[tag1] = randImage
122 refToImage[tag2] = randImage
123 refToImage[dig3] = randImage
124
125 o, err := os.Create(fp.Name())
126 if err != nil {
127 t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err)
128 }
129 defer o.Close()
130
131
132 if err := MultiWrite(refToImage, o); err != nil {
133 t.Fatalf("Unexpected error writing tarball: %v", err)
134 }
135 for ref := range refToImage {
136 tag, ok := ref.(name.Tag)
137 if !ok {
138 continue
139 }
140
141 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
142 if err != nil {
143 t.Fatalf("Unexpected error reading tarball: %v", err)
144 }
145 if err := validate.Image(tarImage); err != nil {
146 t.Errorf("validate.Image: %v", err)
147 }
148 if err := compare.Images(randImage, tarImage); err != nil {
149 t.Errorf("compare.Images: %v", err)
150 }
151 }
152 }
153
154 func TestMultiWriteDifferentImages(t *testing.T) {
155
156 fp, err := os.CreateTemp("", "")
157 if err != nil {
158 t.Fatalf("Error creating temp file: %v", err)
159 }
160 t.Log(fp.Name())
161 defer fp.Close()
162 defer os.Remove(fp.Name())
163
164
165 randImage1, err := random.Image(256, 8)
166 if err != nil {
167 t.Fatalf("Error creating random image 1: %v", err)
168 }
169
170
171 randImage2, err := random.Image(256, 8)
172 if err != nil {
173 t.Fatalf("Error creating random image 2: %v", err)
174 }
175
176
177 randImage3, err := random.Image(256, 8)
178 if err != nil {
179 t.Fatalf("Error creating random image 3: %v", err)
180 }
181
182
183 tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
184 if err != nil {
185 t.Fatalf("Error creating test tag1: %v", err)
186 }
187 tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation)
188 if err != nil {
189 t.Fatalf("Error creating test tag2: %v", err)
190 }
191 dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation)
192 if err != nil {
193 t.Fatalf("Error creating test dig3: %v", err)
194 }
195 refToImage := make(map[name.Reference]v1.Image)
196 refToImage[tag1] = randImage1
197 refToImage[tag2] = randImage2
198 refToImage[dig3] = randImage3
199
200 o, err := os.Create(fp.Name())
201 if err != nil {
202 t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err)
203 }
204 defer o.Close()
205
206
207 if err := MultiWrite(refToImage, o); err != nil {
208 t.Fatalf("Unexpected error writing tarball: %v", err)
209 }
210 for ref, img := range refToImage {
211 tag, ok := ref.(name.Tag)
212 if !ok {
213 continue
214 }
215
216 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
217 if err != nil {
218 t.Fatalf("Unexpected error reading tarball: %v", err)
219 }
220 if err := validate.Image(tarImage); err != nil {
221 t.Errorf("validate.Image: %v", err)
222 }
223 if err := compare.Images(img, tarImage); err != nil {
224 t.Errorf("compare.Images: %v", err)
225 }
226 }
227 }
228
229 func TestWriteForeignLayers(t *testing.T) {
230
231 fp, err := os.CreateTemp("", "")
232 if err != nil {
233 t.Fatalf("Error creating temp file: %v", err)
234 }
235 t.Log(fp.Name())
236 defer fp.Close()
237 defer os.Remove(fp.Name())
238
239
240 randImage, err := random.Image(256, 1)
241 if err != nil {
242 t.Fatalf("Error creating random image: %v", err)
243 }
244 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
245 if err != nil {
246 t.Fatalf("Error creating test tag: %v", err)
247 }
248 randLayer, err := random.Layer(512, types.DockerForeignLayer)
249 if err != nil {
250 t.Fatalf("random.Layer: %v", err)
251 }
252 img, err := mutate.Append(randImage, mutate.Addendum{
253 Layer: randLayer,
254 URLs: []string{
255 "example.com",
256 },
257 })
258 if err != nil {
259 t.Fatalf("Unable to mutate image to add foreign layer: %v", err)
260 }
261 o, err := os.Create(fp.Name())
262 if err != nil {
263 t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err)
264 }
265 defer o.Close()
266 if err := Write(tag, img, o); err != nil {
267 t.Fatalf("Unexpected error writing tarball: %v", err)
268 }
269
270 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
271 if err != nil {
272 t.Fatalf("Unexpected error reading tarball: %v", err)
273 }
274
275 if err := validate.Image(tarImage); err != nil {
276 t.Fatalf("validate.Image(): %v", err)
277 }
278
279 m, err := tarImage.Manifest()
280 if err != nil {
281 t.Fatal(err)
282 }
283
284 if got, want := m.Layers[1].MediaType, types.DockerForeignLayer; got != want {
285 t.Errorf("Wrong MediaType: %s != %s", got, want)
286 }
287 if got, want := m.Layers[1].URLs[0], "example.com"; got != want {
288 t.Errorf("Wrong URLs: %s != %s", got, want)
289 }
290 }
291
292 func TestMultiWriteNoHistory(t *testing.T) {
293
294 img, err := random.Image(256, 8)
295 if err != nil {
296 t.Fatalf("Error creating random image: %v", err)
297 }
298 cfg, err := img.ConfigFile()
299 if err != nil {
300 t.Fatalf("Error getting image config: %v", err)
301 }
302
303 cfg.History = nil
304 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
305 if err != nil {
306 t.Fatalf("Error creating test tag: %v", err)
307 }
308
309 fp, err := os.CreateTemp("", "")
310 if err != nil {
311 t.Fatalf("Error creating temp file: %v", err)
312 }
313 t.Log(fp.Name())
314 defer fp.Close()
315 defer os.Remove(fp.Name())
316 if err := Write(tag, img, fp); err != nil {
317 t.Fatalf("Unexpected error writing tarball: %v", err)
318 }
319 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
320 if err != nil {
321 t.Fatalf("Unexpected error reading tarball: %v", err)
322 }
323 if err := validate.Image(tarImage); err != nil {
324 t.Fatalf("validate.Image(): %v", err)
325 }
326 }
327
328 func TestMultiWriteHistoryEmptyLayers(t *testing.T) {
329
330
331 h := []v1.History{
332 {EmptyLayer: true},
333 {EmptyLayer: false},
334 {EmptyLayer: true},
335 {EmptyLayer: false},
336 {EmptyLayer: true},
337 }
338
339
340 img, err := random.Image(256, int64(len(filterEmpty(h))))
341 if err != nil {
342 t.Fatalf("Error creating random image: %v", err)
343 }
344 cfg, err := img.ConfigFile()
345 if err != nil {
346 t.Fatalf("Error getting image config: %v", err)
347 }
348
349
350 cfg.History = h
351 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
352 if err != nil {
353 t.Fatalf("Error creating test tag: %v", err)
354 }
355
356 fp, err := os.CreateTemp("", "")
357 if err != nil {
358 t.Fatalf("Error creating temp file: %v", err)
359 }
360 t.Log(fp.Name())
361 defer fp.Close()
362 defer os.Remove(fp.Name())
363 if err := Write(tag, img, fp); err != nil {
364 t.Fatalf("Unexpected error writing tarball: %v", err)
365 }
366 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
367 if err != nil {
368 t.Fatalf("Unexpected error reading tarball: %v", err)
369 }
370 if err := validate.Image(tarImage); err != nil {
371 t.Fatalf("validate.Image(): %v", err)
372 }
373 }
374
375 func TestMultiWriteMismatchedHistory(t *testing.T) {
376
377 img, err := random.Image(256, 8)
378 if err != nil {
379 t.Fatalf("Error creating random image: %v", err)
380 }
381 cfg, err := img.ConfigFile()
382 if err != nil {
383 t.Fatalf("Error getting image config: %v", err)
384 }
385
386
387
388 cfg.History = make([]v1.History, 1)
389 img, err = mutate.ConfigFile(img, cfg)
390 if err != nil {
391 t.Fatalf("mutate.ConfigFile() = %v", err)
392 }
393
394 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
395 if err != nil {
396 t.Fatalf("Error creating test tag: %v", err)
397 }
398
399 fp, err := os.CreateTemp("", "")
400 if err != nil {
401 t.Fatalf("Error creating temp file: %v", err)
402 }
403 t.Log(fp.Name())
404 defer fp.Close()
405 defer os.Remove(fp.Name())
406 err = Write(tag, img, fp)
407 if err == nil {
408 t.Fatal("Unexpected success writing tarball, got nil, want error.")
409 }
410 want := "image config had layer history which did not match the number of layers"
411 if !strings.Contains(err.Error(), want) {
412 t.Errorf("Got unexpected error when writing image with mismatched history & layer, got %v, want substring %q", err, want)
413 }
414 }
415
416 type fastSizeLayer struct {
417 v1.Layer
418 size int64
419 called bool
420 }
421
422 func (l *fastSizeLayer) UncompressedSize() (int64, error) {
423 l.called = true
424 return l.size, nil
425 }
426
427 func TestUncompressedSize(t *testing.T) {
428
429 img, err := random.Image(256, 8)
430 if err != nil {
431 t.Fatalf("Error creating random image: %v", err)
432 }
433
434 rand, err := random.Layer(1000, types.DockerLayer)
435 if err != nil {
436 t.Fatal(err)
437 }
438
439 size, err := partial.UncompressedSize(rand)
440 if err != nil {
441 t.Fatal(err)
442 }
443
444 l := &fastSizeLayer{Layer: rand, size: size}
445
446 img, err = mutate.AppendLayers(img, l)
447 if err != nil {
448 t.Fatal(err)
449 }
450 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
451 if err != nil {
452 t.Fatalf("Error creating test tag: %v", err)
453 }
454
455 fp, err := os.CreateTemp("", "")
456 if err != nil {
457 t.Fatalf("Error creating temp file: %v", err)
458 }
459 t.Log(fp.Name())
460 defer fp.Close()
461 defer os.Remove(fp.Name())
462 if err := Write(tag, img, fp); err != nil {
463 t.Fatalf("Write(): %v", err)
464 }
465 if !l.called {
466 t.Errorf("expected UncompressedSize to be called, but it wasn't")
467 }
468 }
469
470
471
472 func TestWriteSharedLayers(t *testing.T) {
473
474 fp, err := os.CreateTemp("", "")
475 if err != nil {
476 t.Fatalf("Error creating temp file: %v", err)
477 }
478 t.Log(fp.Name())
479 defer fp.Close()
480 defer os.Remove(fp.Name())
481
482 const baseImageLayerCount = 8
483
484
485 baseImage, err := random.Image(256, baseImageLayerCount)
486 if err != nil {
487 t.Fatalf("Error creating base image: %v", err)
488 }
489
490
491 randLayer, err := random.Layer(256, types.DockerLayer)
492 if err != nil {
493 t.Fatalf("Error creating random layer %v", err)
494 }
495 extendedImage, err := mutate.Append(baseImage, mutate.Addendum{
496 Layer: randLayer,
497 })
498 if err != nil {
499 t.Fatalf("Error mutating base image %v", err)
500 }
501
502
503 tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
504 if err != nil {
505 t.Fatalf("Error creating test tag1: %v", err)
506 }
507 tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation)
508 if err != nil {
509 t.Fatalf("Error creating test tag2: %v", err)
510 }
511 refToImage := map[name.Reference]v1.Image{
512 tag1: baseImage,
513 tag2: extendedImage,
514 }
515
516 o, err := os.Create(fp.Name())
517 if err != nil {
518 t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err)
519 }
520 defer o.Close()
521
522
523 if err := MultiWrite(refToImage, o); err != nil {
524 t.Fatalf("Unexpected error writing tarball: %v", err)
525 }
526 for ref, img := range refToImage {
527 tag, ok := ref.(name.Tag)
528 if !ok {
529 continue
530 }
531
532 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
533 if err != nil {
534 t.Fatalf("Unexpected error reading tarball: %v", err)
535 }
536 if err := validate.Image(tarImage); err != nil {
537 t.Errorf("validate.Image: %v", err)
538 }
539 if err := compare.Images(img, tarImage); err != nil {
540 t.Errorf("compare.Images: %v", err)
541 }
542 }
543
544 wantIDs := make(map[string]struct{})
545 ids, err := v1LayerIDs(baseImage)
546 if err != nil {
547 t.Fatalf("Error getting base image IDs: %v", err)
548 }
549 for _, id := range ids {
550 wantIDs[id] = struct{}{}
551 }
552 ids, err = v1LayerIDs(extendedImage)
553 if err != nil {
554 t.Fatalf("Error getting extended image IDs: %v", err)
555 }
556 for _, id := range ids {
557 wantIDs[id] = struct{}{}
558 }
559
560
561 if len(wantIDs) != baseImageLayerCount+2 {
562 t.Errorf("Expected to have %d unique layer IDs but have %d", baseImageLayerCount+2, len(wantIDs))
563 }
564
565 const layerFileName = "layer.tar"
566 r := tar.NewReader(fp)
567 for {
568 hdr, err := r.Next()
569 if err != nil {
570 if errors.Is(err, io.EOF) {
571 break
572 }
573 t.Fatalf("Get tar header: %v", err)
574 }
575 if filepath.Base(hdr.Name) == layerFileName {
576 id := filepath.Dir(hdr.Name)
577 if _, ok := wantIDs[id]; ok {
578 delete(wantIDs, id)
579 } else {
580 t.Errorf("Found unwanted layer with ID %q", id)
581 }
582 }
583 }
584 if len(wantIDs) != 0 {
585 for id := range wantIDs {
586 t.Errorf("Expected to find layer with ID %q but it didn't exist", id)
587 }
588 }
589 }
590
591 func v1LayerIDs(img v1.Image) ([]string, error) {
592 layers, err := img.Layers()
593 if err != nil {
594 return nil, fmt.Errorf("get layers: %w", err)
595 }
596 ids := make([]string, len(layers))
597 parentID := ""
598 for i, layer := range layers {
599 var rawCfg []byte
600 if i == len(layers)-1 {
601 rawCfg, err = img.RawConfigFile()
602 if err != nil {
603 return nil, fmt.Errorf("get raw config file: %w", err)
604 }
605 }
606 id, err := v1LayerID(layer, parentID, rawCfg)
607 if err != nil {
608 return nil, fmt.Errorf("get v1 layer ID: %w", err)
609 }
610
611 ids[i] = id
612 parentID = id
613 }
614 return ids, nil
615 }
616
View as plain text