1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package tarball_test
16
17 import (
18 "archive/tar"
19 "bytes"
20 "encoding/json"
21 "errors"
22 "fmt"
23 "io"
24 "os"
25 "strings"
26 "testing"
27
28 "github.com/google/go-containerregistry/internal/compare"
29 "github.com/google/go-containerregistry/pkg/name"
30 v1 "github.com/google/go-containerregistry/pkg/v1"
31 "github.com/google/go-containerregistry/pkg/v1/mutate"
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.")
52 }
53 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
54 if err != nil {
55 t.Fatalf("Error creating test tag.")
56 }
57 if err := tarball.WriteToFile(fp.Name(), tag, randImage); err != nil {
58 t.Fatalf("Unexpected error writing tarball: %v", err)
59 }
60
61
62
63 for _, it := range []*name.Tag{nil, &tag} {
64 tarImage, err := tarball.ImageFromPath(fp.Name(), it)
65 if err != nil {
66 t.Fatalf("Unexpected error reading tarball: %v", err)
67 }
68
69 if err := validate.Image(tarImage); err != nil {
70 t.Errorf("validate.Image: %v", err)
71 }
72
73 if err := compare.Images(randImage, tarImage); err != nil {
74 t.Errorf("compare.Images: %v", err)
75 }
76 }
77
78
79 fakeTag, err := name.NewTag("gcr.io/notthistag:latest", name.StrictValidation)
80 if err != nil {
81 t.Fatalf("Error generating tag: %v", err)
82 }
83 if _, err := tarball.ImageFromPath(fp.Name(), &fakeTag); err == nil {
84 t.Errorf("Expected error loading tag %v from image", fakeTag)
85 }
86 }
87
88 func TestMultiWriteSameImage(t *testing.T) {
89
90 fp, err := os.CreateTemp("", "")
91 if err != nil {
92 t.Fatalf("Error creating temp file.")
93 }
94 t.Log(fp.Name())
95 defer fp.Close()
96 defer os.Remove(fp.Name())
97
98
99 randImage, err := random.Image(256, 8)
100 if err != nil {
101 t.Fatalf("Error creating random image.")
102 }
103
104
105 tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
106 if err != nil {
107 t.Fatalf("Error creating test tag1.")
108 }
109 tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation)
110 if err != nil {
111 t.Fatalf("Error creating test tag2.")
112 }
113 dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation)
114 if err != nil {
115 t.Fatalf("Error creating test dig3.")
116 }
117 refToImage := make(map[name.Reference]v1.Image)
118 refToImage[tag1] = randImage
119 refToImage[tag2] = randImage
120 refToImage[dig3] = randImage
121
122
123 if err := tarball.MultiRefWriteToFile(fp.Name(), refToImage); err != nil {
124 t.Fatalf("Unexpected error writing tarball: %v", err)
125 }
126 for ref := range refToImage {
127 tag, ok := ref.(name.Tag)
128 if !ok {
129 continue
130 }
131
132 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
133 if err != nil {
134 t.Fatalf("Unexpected error reading tarball: %v", err)
135 }
136
137 if err := validate.Image(tarImage); err != nil {
138 t.Errorf("validate.Image: %v", err)
139 }
140
141 if err := compare.Images(randImage, tarImage); err != nil {
142 t.Errorf("compare.Images: %v", err)
143 }
144 }
145 }
146
147 func TestMultiWriteDifferentImages(t *testing.T) {
148
149 fp, err := os.CreateTemp("", "")
150 if err != nil {
151 t.Fatalf("Error creating temp file.")
152 }
153 t.Log(fp.Name())
154 defer fp.Close()
155 defer os.Remove(fp.Name())
156
157
158 randImage1, err := random.Image(256, 8)
159 if err != nil {
160 t.Fatalf("Error creating random image 1.")
161 }
162
163
164 randImage2, err := random.Image(256, 8)
165 if err != nil {
166 t.Fatalf("Error creating random image 2.")
167 }
168
169
170 randImage3, err := random.Image(256, 8)
171 if err != nil {
172 t.Fatalf("Error creating random image 3.")
173 }
174
175
176 tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
177 if err != nil {
178 t.Fatalf("Error creating test tag1.")
179 }
180 tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation)
181 if err != nil {
182 t.Fatalf("Error creating test tag2.")
183 }
184 dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation)
185 if err != nil {
186 t.Fatalf("Error creating test dig3.")
187 }
188 refToImage := make(map[name.Reference]v1.Image)
189 refToImage[tag1] = randImage1
190 refToImage[tag2] = randImage2
191 refToImage[dig3] = randImage3
192
193
194 if err := tarball.MultiRefWriteToFile(fp.Name(), refToImage); err != nil {
195 t.Fatalf("Unexpected error writing tarball: %v", err)
196 }
197 for ref, img := range refToImage {
198 tag, ok := ref.(name.Tag)
199 if !ok {
200 continue
201 }
202
203 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
204 if err != nil {
205 t.Fatalf("Unexpected error reading tarball: %v", err)
206 }
207
208 if err := validate.Image(tarImage); err != nil {
209 t.Errorf("validate.Image: %v", err)
210 }
211
212 if err := compare.Images(img, tarImage); err != nil {
213 t.Errorf("compare.Images: %v", err)
214 }
215 }
216 }
217
218 func TestWriteForeignLayers(t *testing.T) {
219
220 fp, err := os.CreateTemp("", "")
221 if err != nil {
222 t.Fatalf("Error creating temp file.")
223 }
224 t.Log(fp.Name())
225 defer fp.Close()
226 defer os.Remove(fp.Name())
227
228
229 randImage, err := random.Image(256, 1)
230 if err != nil {
231 t.Fatalf("Error creating random image.")
232 }
233 tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
234 if err != nil {
235 t.Fatalf("Error creating test tag.")
236 }
237 randLayer, err := random.Layer(512, types.DockerForeignLayer)
238 if err != nil {
239 t.Fatalf("random.Layer: %v", err)
240 }
241 img, err := mutate.Append(randImage, mutate.Addendum{
242 Layer: randLayer,
243 URLs: []string{
244 "example.com",
245 },
246 })
247 if err != nil {
248 t.Fatal(err)
249 }
250 if err := tarball.WriteToFile(fp.Name(), tag, img); err != nil {
251 t.Fatalf("Unexpected error writing tarball: %v", err)
252 }
253
254 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
255 if err != nil {
256 t.Fatalf("Unexpected error reading tarball: %v", err)
257 }
258
259 if err := validate.Image(tarImage); err != nil {
260 t.Fatalf("validate.Image(): %v", err)
261 }
262
263 m, err := tarImage.Manifest()
264 if err != nil {
265 t.Fatal(err)
266 }
267
268 if got, want := m.Layers[1].MediaType, types.DockerForeignLayer; got != want {
269 t.Errorf("Wrong MediaType: %s != %s", got, want)
270 }
271 if got, want := m.Layers[1].URLs[0], "example.com"; got != want {
272 t.Errorf("Wrong URLs: %s != %s", got, want)
273 }
274 }
275
276 func TestWriteSharedLayers(t *testing.T) {
277
278 fp, err := os.CreateTemp("", "")
279 if err != nil {
280 t.Fatalf("Error creating temp file.")
281 }
282 t.Log(fp.Name())
283 defer fp.Close()
284 defer os.Remove(fp.Name())
285
286
287 randImage, err := random.Image(256, 1)
288 if err != nil {
289 t.Fatalf("Error creating random image.")
290 }
291 tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation)
292 if err != nil {
293 t.Fatalf("Error creating test tag1.")
294 }
295 tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation)
296 if err != nil {
297 t.Fatalf("Error creating test tag2.")
298 }
299 randLayer, err := random.Layer(512, types.DockerLayer)
300 if err != nil {
301 t.Fatalf("random.Layer: %v", err)
302 }
303 mutatedImage, err := mutate.Append(randImage, mutate.Addendum{
304 Layer: randLayer,
305 })
306 if err != nil {
307 t.Fatal(err)
308 }
309 refToImage := make(map[name.Reference]v1.Image)
310 refToImage[tag1] = randImage
311 refToImage[tag2] = mutatedImage
312
313
314 if err := tarball.MultiRefWriteToFile(fp.Name(), refToImage); err != nil {
315 t.Fatalf("Unexpected error writing tarball: %v", err)
316 }
317 for ref := range refToImage {
318 tag, ok := ref.(name.Tag)
319 if !ok {
320 continue
321 }
322
323 tarImage, err := tarball.ImageFromPath(fp.Name(), &tag)
324 if err != nil {
325 t.Fatalf("Unexpected error reading tarball: %v", err)
326 }
327
328 if err := validate.Image(tarImage); err != nil {
329 t.Errorf("validate.Image: %v", err)
330 }
331
332 if err := compare.Images(refToImage[tag], tarImage); err != nil {
333 t.Errorf("compare.Images: %v", err)
334 }
335 }
336 _, err = fp.Seek(0, io.SeekStart)
337 if err != nil {
338 t.Fatalf("Seek to start of file: %v", err)
339 }
340 layers, err := randImage.Layers()
341 if err != nil {
342 t.Fatalf("Get image layers: %v", err)
343 }
344 layers = append(layers, randLayer)
345 wantDigests := make(map[string]struct{})
346 for _, layer := range layers {
347 d, err := layer.Digest()
348 if err != nil {
349 t.Fatalf("Get layer digest: %v", err)
350 }
351 wantDigests[d.Hex] = struct{}{}
352 }
353
354 const layerFileSuffix = ".tar.gz"
355 r := tar.NewReader(fp)
356 for {
357 hdr, err := r.Next()
358 if err != nil {
359 if errors.Is(err, io.EOF) {
360 break
361 }
362 t.Fatalf("Get tar header: %v", err)
363 }
364 if strings.HasSuffix(hdr.Name, layerFileSuffix) {
365 hex := hdr.Name[:len(hdr.Name)-len(layerFileSuffix)]
366 if _, ok := wantDigests[hex]; ok {
367 delete(wantDigests, hex)
368 } else {
369 t.Errorf("Found unwanted layer with digest %q", hex)
370 }
371 }
372 }
373 if len(wantDigests) != 0 {
374 for hex := range wantDigests {
375 t.Errorf("Expected to find layer with digest %q but it didn't exist", hex)
376 }
377 }
378 }
379
380 func TestComputeManifest(t *testing.T) {
381 var randomTag, mutatedTag = "ubuntu", "gcr.io/baz/bat:latest"
382
383
384 randomTagWritten := "ubuntu:latest"
385
386
387 randImage, err := random.Image(256, 1)
388 if err != nil {
389 t.Fatalf("Error creating random image.")
390 }
391 randConfig, err := randImage.ConfigName()
392 if err != nil {
393 t.Fatalf("error getting random image config: %v", err)
394 }
395 tag1, err := name.NewTag(randomTag)
396 if err != nil {
397 t.Fatalf("Error creating test tag1.")
398 }
399 tag2, err := name.NewTag(mutatedTag, name.StrictValidation)
400 if err != nil {
401 t.Fatalf("Error creating test tag2.")
402 }
403 randLayer, err := random.Layer(512, types.DockerLayer)
404 if err != nil {
405 t.Fatalf("random.Layer: %v", err)
406 }
407 mutatedImage, err := mutate.Append(randImage, mutate.Addendum{
408 Layer: randLayer,
409 })
410 if err != nil {
411 t.Fatal(err)
412 }
413 mutatedConfig, err := mutatedImage.ConfigName()
414 if err != nil {
415 t.Fatalf("error getting mutated image config: %v", err)
416 }
417 randomLayersHashes, err := getLayersHashes(randImage)
418 if err != nil {
419 t.Fatalf("error getting random image hashes: %v", err)
420 }
421 randomLayersFilenames := getLayersFilenames(randomLayersHashes)
422
423 mutatedLayersHashes, err := getLayersHashes(mutatedImage)
424 if err != nil {
425 t.Fatalf("error getting mutated image hashes: %v", err)
426 }
427 mutatedLayersFilenames := getLayersFilenames(mutatedLayersHashes)
428
429 refToImage := make(map[name.Reference]v1.Image)
430 refToImage[tag1] = randImage
431 refToImage[tag2] = mutatedImage
432
433
434 m, err := tarball.ComputeManifest(refToImage)
435 if err != nil {
436 t.Fatalf("Unexpected error calculating manifest: %v", err)
437 }
438
439
440 expected := []tarball.Descriptor{
441 {
442 Config: mutatedConfig.String(),
443 RepoTags: []string{mutatedTag},
444 Layers: mutatedLayersFilenames,
445 },
446 {
447 Config: randConfig.String(),
448 RepoTags: []string{randomTagWritten},
449 Layers: randomLayersFilenames,
450 },
451 }
452 if len(m) != len(expected) {
453 t.Fatalf("mismatched manifest lengths: actual %d, expected %d", len(m), len(expected))
454 }
455 mBytes, err := json.Marshal(m)
456 if err != nil {
457 t.Fatalf("unable to marshall actual manifest to json: %v", err)
458 }
459 eBytes, err := json.Marshal(expected)
460 if err != nil {
461 t.Fatalf("unable to marshall expected manifest to json: %v", err)
462 }
463 if !bytes.Equal(mBytes, eBytes) {
464 t.Errorf("mismatched manifests.\nActual: %s\nExpected: %s", string(mBytes), string(eBytes))
465 }
466 }
467
468 func TestComputeManifest_FailsOnNoRefs(t *testing.T) {
469 _, err := tarball.ComputeManifest(nil)
470 if err == nil || !strings.Contains(err.Error(), "set of images is empty") {
471 t.Error("expected calculateManifest to fail with nil input")
472 }
473
474 _, err = tarball.ComputeManifest(map[name.Reference]v1.Image{})
475 if err == nil || !strings.Contains(err.Error(), "set of images is empty") {
476 t.Error("expected calculateManifest to fail with empty input")
477 }
478 }
479
480 func getLayersHashes(img v1.Image) ([]string, error) {
481 hashes := []string{}
482 layers, err := img.Layers()
483 if err != nil {
484 return nil, fmt.Errorf("error getting image layers: %w", err)
485 }
486 for i, l := range layers {
487 hash, err := l.Digest()
488 if err != nil {
489 return nil, fmt.Errorf("error getting digest for layer %d: %w", i, err)
490 }
491 hashes = append(hashes, hash.Hex)
492 }
493 return hashes, nil
494 }
495
496 func getLayersFilenames(hashes []string) []string {
497 filenames := []string{}
498 for _, h := range hashes {
499 filenames = append(filenames, fmt.Sprintf("%s.tar.gz", h))
500 }
501 return filenames
502 }
503
View as plain text