diff options
Diffstat (limited to 'pkg/v1/tarball/write_test.go')
-rw-r--r-- | pkg/v1/tarball/write_test.go | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/pkg/v1/tarball/write_test.go b/pkg/v1/tarball/write_test.go new file mode 100644 index 0000000..fdfe499 --- /dev/null +++ b/pkg/v1/tarball/write_test.go @@ -0,0 +1,502 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// 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 tarball_test + +import ( + "archive/tar" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/google/go-containerregistry/internal/compare" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/google/go-containerregistry/pkg/v1/validate" +) + +func TestWrite(t *testing.T) { + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file.") + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + + // Make a random image + randImage, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image.") + } + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag.") + } + if err := tarball.WriteToFile(fp.Name(), tag, randImage); err != nil { + t.Fatalf("Unexpected error writing tarball: %v", err) + } + + // Make sure the image is valid and can be loaded. + // Load it both by nil and by its name. + for _, it := range []*name.Tag{nil, &tag} { + tarImage, err := tarball.ImageFromPath(fp.Name(), it) + if err != nil { + t.Fatalf("Unexpected error reading tarball: %v", err) + } + + if err := validate.Image(tarImage); err != nil { + t.Errorf("validate.Image: %v", err) + } + + if err := compare.Images(randImage, tarImage); err != nil { + t.Errorf("compare.Images: %v", err) + } + } + + // Try loading a different tag, it should error. + fakeTag, err := name.NewTag("gcr.io/notthistag:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error generating tag: %v", err) + } + if _, err := tarball.ImageFromPath(fp.Name(), &fakeTag); err == nil { + t.Errorf("Expected error loading tag %v from image", fakeTag) + } +} + +func TestMultiWriteSameImage(t *testing.T) { + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file.") + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + + // Make a random image + randImage, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image.") + } + + // Make two tags that point to the random image above. + tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag1.") + } + tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag2.") + } + dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test dig3.") + } + refToImage := make(map[name.Reference]v1.Image) + refToImage[tag1] = randImage + refToImage[tag2] = randImage + refToImage[dig3] = randImage + + // Write the images with both tags to the tarball + if err := tarball.MultiRefWriteToFile(fp.Name(), refToImage); err != nil { + t.Fatalf("Unexpected error writing tarball: %v", err) + } + for ref := range refToImage { + tag, ok := ref.(name.Tag) + if !ok { + continue + } + + tarImage, err := tarball.ImageFromPath(fp.Name(), &tag) + if err != nil { + t.Fatalf("Unexpected error reading tarball: %v", err) + } + + if err := validate.Image(tarImage); err != nil { + t.Errorf("validate.Image: %v", err) + } + + if err := compare.Images(randImage, tarImage); err != nil { + t.Errorf("compare.Images: %v", err) + } + } +} + +func TestMultiWriteDifferentImages(t *testing.T) { + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file.") + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + + // Make a random image + randImage1, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image 1.") + } + + // Make another random image + randImage2, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image 2.") + } + + // Make another random image + randImage3, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image 3.") + } + + // Create two tags, one pointing to each image created. + tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag1.") + } + tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag2.") + } + dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test dig3.") + } + refToImage := make(map[name.Reference]v1.Image) + refToImage[tag1] = randImage1 + refToImage[tag2] = randImage2 + refToImage[dig3] = randImage3 + + // Write both images to the tarball. + if err := tarball.MultiRefWriteToFile(fp.Name(), refToImage); err != nil { + t.Fatalf("Unexpected error writing tarball: %v", err) + } + for ref, img := range refToImage { + tag, ok := ref.(name.Tag) + if !ok { + continue + } + + tarImage, err := tarball.ImageFromPath(fp.Name(), &tag) + if err != nil { + t.Fatalf("Unexpected error reading tarball: %v", err) + } + + if err := validate.Image(tarImage); err != nil { + t.Errorf("validate.Image: %v", err) + } + + if err := compare.Images(img, tarImage); err != nil { + t.Errorf("compare.Images: %v", err) + } + } +} + +func TestWriteForeignLayers(t *testing.T) { + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file.") + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + + // Make a random image + randImage, err := random.Image(256, 1) + if err != nil { + t.Fatalf("Error creating random image.") + } + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag.") + } + randLayer, err := random.Layer(512, types.DockerForeignLayer) + if err != nil { + t.Fatalf("random.Layer: %v", err) + } + img, err := mutate.Append(randImage, mutate.Addendum{ + Layer: randLayer, + URLs: []string{ + "example.com", + }, + }) + if err != nil { + t.Fatal(err) + } + if err := tarball.WriteToFile(fp.Name(), tag, img); err != nil { + t.Fatalf("Unexpected error writing tarball: %v", err) + } + + tarImage, err := tarball.ImageFromPath(fp.Name(), &tag) + if err != nil { + t.Fatalf("Unexpected error reading tarball: %v", err) + } + + if err := validate.Image(tarImage); err != nil { + t.Fatalf("validate.Image(): %v", err) + } + + m, err := tarImage.Manifest() + if err != nil { + t.Fatal(err) + } + + if got, want := m.Layers[1].MediaType, types.DockerForeignLayer; got != want { + t.Errorf("Wrong MediaType: %s != %s", got, want) + } + if got, want := m.Layers[1].URLs[0], "example.com"; got != want { + t.Errorf("Wrong URLs: %s != %s", got, want) + } +} + +func TestWriteSharedLayers(t *testing.T) { + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file.") + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + + // Make a random image + randImage, err := random.Image(256, 1) + if err != nil { + t.Fatalf("Error creating random image.") + } + tag1, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag1.") + } + tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag2.") + } + randLayer, err := random.Layer(512, types.DockerLayer) + if err != nil { + t.Fatalf("random.Layer: %v", err) + } + mutatedImage, err := mutate.Append(randImage, mutate.Addendum{ + Layer: randLayer, + }) + if err != nil { + t.Fatal(err) + } + refToImage := make(map[name.Reference]v1.Image) + refToImage[tag1] = randImage + refToImage[tag2] = mutatedImage + + // Write the images with both tags to the tarball + if err := tarball.MultiRefWriteToFile(fp.Name(), refToImage); err != nil { + t.Fatalf("Unexpected error writing tarball: %v", err) + } + for ref := range refToImage { + tag, ok := ref.(name.Tag) + if !ok { + continue + } + + tarImage, err := tarball.ImageFromPath(fp.Name(), &tag) + if err != nil { + t.Fatalf("Unexpected error reading tarball: %v", err) + } + + if err := validate.Image(tarImage); err != nil { + t.Errorf("validate.Image: %v", err) + } + + if err := compare.Images(refToImage[tag], tarImage); err != nil { + t.Errorf("compare.Images: %v", err) + } + } + _, err = fp.Seek(0, io.SeekStart) + if err != nil { + t.Fatalf("Seek to start of file: %v", err) + } + layers, err := randImage.Layers() + if err != nil { + t.Fatalf("Get image layers: %v", err) + } + layers = append(layers, randLayer) + wantDigests := make(map[string]struct{}) + for _, layer := range layers { + d, err := layer.Digest() + if err != nil { + t.Fatalf("Get layer digest: %v", err) + } + wantDigests[d.Hex] = struct{}{} + } + + const layerFileSuffix = ".tar.gz" + r := tar.NewReader(fp) + for { + hdr, err := r.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + t.Fatalf("Get tar header: %v", err) + } + if strings.HasSuffix(hdr.Name, layerFileSuffix) { + hex := hdr.Name[:len(hdr.Name)-len(layerFileSuffix)] + if _, ok := wantDigests[hex]; ok { + delete(wantDigests, hex) + } else { + t.Errorf("Found unwanted layer with digest %q", hex) + } + } + } + if len(wantDigests) != 0 { + for hex := range wantDigests { + t.Errorf("Expected to find layer with digest %q but it didn't exist", hex) + } + } +} + +func TestComputeManifest(t *testing.T) { + var randomTag, mutatedTag = "ubuntu", "gcr.io/baz/bat:latest" + + // https://github.com/google/go-containerregistry/issues/890 + randomTagWritten := "ubuntu:latest" + + // Make a random image + randImage, err := random.Image(256, 1) + if err != nil { + t.Fatalf("Error creating random image.") + } + randConfig, err := randImage.ConfigName() + if err != nil { + t.Fatalf("error getting random image config: %v", err) + } + tag1, err := name.NewTag(randomTag) + if err != nil { + t.Fatalf("Error creating test tag1.") + } + tag2, err := name.NewTag(mutatedTag, name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag2.") + } + randLayer, err := random.Layer(512, types.DockerLayer) + if err != nil { + t.Fatalf("random.Layer: %v", err) + } + mutatedImage, err := mutate.Append(randImage, mutate.Addendum{ + Layer: randLayer, + }) + if err != nil { + t.Fatal(err) + } + mutatedConfig, err := mutatedImage.ConfigName() + if err != nil { + t.Fatalf("error getting mutated image config: %v", err) + } + randomLayersHashes, err := getLayersHashes(randImage) + if err != nil { + t.Fatalf("error getting random image hashes: %v", err) + } + randomLayersFilenames := getLayersFilenames(randomLayersHashes) + + mutatedLayersHashes, err := getLayersHashes(mutatedImage) + if err != nil { + t.Fatalf("error getting mutated image hashes: %v", err) + } + mutatedLayersFilenames := getLayersFilenames(mutatedLayersHashes) + + refToImage := make(map[name.Reference]v1.Image) + refToImage[tag1] = randImage + refToImage[tag2] = mutatedImage + + // calculate the manifest + m, err := tarball.ComputeManifest(refToImage) + if err != nil { + t.Fatalf("Unexpected error calculating manifest: %v", err) + } + // the order of these two is based on the repo tags + // so mutated "gcr.io/baz/bat:latest" is before random "gcr.io/foo/bar:latest" + expected := []tarball.Descriptor{ + { + Config: mutatedConfig.String(), + RepoTags: []string{mutatedTag}, + Layers: mutatedLayersFilenames, + }, + { + Config: randConfig.String(), + RepoTags: []string{randomTagWritten}, + Layers: randomLayersFilenames, + }, + } + if len(m) != len(expected) { + t.Fatalf("mismatched manifest lengths: actual %d, expected %d", len(m), len(expected)) + } + mBytes, err := json.Marshal(m) + if err != nil { + t.Fatalf("unable to marshall actual manifest to json: %v", err) + } + eBytes, err := json.Marshal(expected) + if err != nil { + t.Fatalf("unable to marshall expected manifest to json: %v", err) + } + if !bytes.Equal(mBytes, eBytes) { + t.Errorf("mismatched manifests.\nActual: %s\nExpected: %s", string(mBytes), string(eBytes)) + } +} + +func TestComputeManifest_FailsOnNoRefs(t *testing.T) { + _, err := tarball.ComputeManifest(nil) + if err == nil || !strings.Contains(err.Error(), "set of images is empty") { + t.Error("expected calculateManifest to fail with nil input") + } + + _, err = tarball.ComputeManifest(map[name.Reference]v1.Image{}) + if err == nil || !strings.Contains(err.Error(), "set of images is empty") { + t.Error("expected calculateManifest to fail with empty input") + } +} + +func getLayersHashes(img v1.Image) ([]string, error) { + hashes := []string{} + layers, err := img.Layers() + if err != nil { + return nil, fmt.Errorf("error getting image layers: %w", err) + } + for i, l := range layers { + hash, err := l.Digest() + if err != nil { + return nil, fmt.Errorf("error getting digest for layer %d: %w", i, err) + } + hashes = append(hashes, hash.Hex) + } + return hashes, nil +} + +func getLayersFilenames(hashes []string) []string { + filenames := []string{} + for _, h := range hashes { + filenames = append(filenames, fmt.Sprintf("%s.tar.gz", h)) + } + return filenames +} |