diff options
Diffstat (limited to 'pkg/legacy/tarball/write_test.go')
-rw-r--r-- | pkg/legacy/tarball/write_test.go | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/pkg/legacy/tarball/write_test.go b/pkg/legacy/tarball/write_test.go new file mode 100644 index 0000000..33e658c --- /dev/null +++ b/pkg/legacy/tarball/write_test.go @@ -0,0 +1,615 @@ +// Copyright 2019 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 + +import ( + "archive/tar" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "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/partial" + "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: %v", err) + } + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag: %v", err) + } + o, err := os.Create(fp.Name()) + if err != nil { + t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err) + } + defer o.Close() + if err := Write(tag, randImage, o); 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 + + o, err := os.Create(fp.Name()) + if err != nil { + t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err) + } + defer o.Close() + + // Write the images with both tags to the tarball + if err := MultiWrite(refToImage, o); 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: %v", err) + } + 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: %v", err) + } + + // Make another random image + randImage2, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image 2: %v", err) + } + + // Make another random image + randImage3, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image 3: %v", err) + } + + // 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: %v", err) + } + tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag2: %v", err) + } + dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test dig3: %v", err) + } + refToImage := make(map[name.Reference]v1.Image) + refToImage[tag1] = randImage1 + refToImage[tag2] = randImage2 + refToImage[dig3] = randImage3 + + o, err := os.Create(fp.Name()) + if err != nil { + t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err) + } + defer o.Close() + + // Write both images to the tarball. + if err := MultiWrite(refToImage, o); 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: %v", err) + } + 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: %v", err) + } + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag: %v", err) + } + 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.Fatalf("Unable to mutate image to add foreign layer: %v", err) + } + o, err := os.Create(fp.Name()) + if err != nil { + t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err) + } + defer o.Close() + if err := Write(tag, img, o); 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 TestMultiWriteNoHistory(t *testing.T) { + // Make a random image. + img, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image: %v", err) + } + cfg, err := img.ConfigFile() + if err != nil { + t.Fatalf("Error getting image config: %v", err) + } + // Blank out the layer history. + cfg.History = nil + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag: %v", err) + } + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file: %v", err) + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + if err := Write(tag, img, fp); 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) + } +} + +func TestMultiWriteHistoryEmptyLayers(t *testing.T) { + // Build a history for 2 layers that is interspersed with empty layer + // history. + h := []v1.History{ + {EmptyLayer: true}, + {EmptyLayer: false}, + {EmptyLayer: true}, + {EmptyLayer: false}, + {EmptyLayer: true}, + } + // Make a random image with the number of non-empty layers from the history + // above. + img, err := random.Image(256, int64(len(filterEmpty(h)))) + if err != nil { + t.Fatalf("Error creating random image: %v", err) + } + cfg, err := img.ConfigFile() + if err != nil { + t.Fatalf("Error getting image config: %v", err) + } + // Override the config history with our custom built history that includes + // history for empty layers. + cfg.History = h + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag: %v", err) + } + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file: %v", err) + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + if err := Write(tag, img, fp); 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) + } +} + +func TestMultiWriteMismatchedHistory(t *testing.T) { + // Make a random image + img, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image: %v", err) + } + cfg, err := img.ConfigFile() + if err != nil { + t.Fatalf("Error getting image config: %v", err) + } + + // Set the history such that number of history entries != layers. This + // should trigger an error during the image write. + cfg.History = make([]v1.History, 1) + img, err = mutate.ConfigFile(img, cfg) + if err != nil { + t.Fatalf("mutate.ConfigFile() = %v", err) + } + + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag: %v", err) + } + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file: %v", err) + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + err = Write(tag, img, fp) + if err == nil { + t.Fatal("Unexpected success writing tarball, got nil, want error.") + } + want := "image config had layer history which did not match the number of layers" + if !strings.Contains(err.Error(), want) { + t.Errorf("Got unexpected error when writing image with mismatched history & layer, got %v, want substring %q", err, want) + } +} + +type fastSizeLayer struct { + v1.Layer + size int64 + called bool +} + +func (l *fastSizeLayer) UncompressedSize() (int64, error) { + l.called = true + return l.size, nil +} + +func TestUncompressedSize(t *testing.T) { + // Make a random image + img, err := random.Image(256, 8) + if err != nil { + t.Fatalf("Error creating random image: %v", err) + } + + rand, err := random.Layer(1000, types.DockerLayer) + if err != nil { + t.Fatal(err) + } + + size, err := partial.UncompressedSize(rand) + if err != nil { + t.Fatal(err) + } + + l := &fastSizeLayer{Layer: rand, size: size} + + img, err = mutate.AppendLayers(img, l) + if err != nil { + t.Fatal(err) + } + tag, err := name.NewTag("gcr.io/foo/bar:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag: %v", err) + } + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file: %v", err) + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + if err := Write(tag, img, fp); err != nil { + t.Fatalf("Write(): %v", err) + } + if !l.called { + t.Errorf("expected UncompressedSize to be called, but it wasn't") + } +} + +// TestWriteSharedLayers tests that writing a tarball of multiple images that +// share some layers only writes those shared layers once. +func TestWriteSharedLayers(t *testing.T) { + // Make a tempfile for tarball writes. + fp, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating temp file: %v", err) + } + t.Log(fp.Name()) + defer fp.Close() + defer os.Remove(fp.Name()) + + const baseImageLayerCount = 8 + + // Make a random image + baseImage, err := random.Image(256, baseImageLayerCount) + if err != nil { + t.Fatalf("Error creating base image: %v", err) + } + + // Make another random image + randLayer, err := random.Layer(256, types.DockerLayer) + if err != nil { + t.Fatalf("Error creating random layer %v", err) + } + extendedImage, err := mutate.Append(baseImage, mutate.Addendum{ + Layer: randLayer, + }) + if err != nil { + t.Fatalf("Error mutating base image %v", err) + } + + // 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: %v", err) + } + tag2, err := name.NewTag("gcr.io/baz/bat:latest", name.StrictValidation) + if err != nil { + t.Fatalf("Error creating test tag2: %v", err) + } + refToImage := map[name.Reference]v1.Image{ + tag1: baseImage, + tag2: extendedImage, + } + + o, err := os.Create(fp.Name()) + if err != nil { + t.Fatalf("Error creating %q to write image tarball: %v", fp.Name(), err) + } + defer o.Close() + + // Write both images to the tarball. + if err := MultiWrite(refToImage, o); 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) + } + } + + wantIDs := make(map[string]struct{}) + ids, err := v1LayerIDs(baseImage) + if err != nil { + t.Fatalf("Error getting base image IDs: %v", err) + } + for _, id := range ids { + wantIDs[id] = struct{}{} + } + ids, err = v1LayerIDs(extendedImage) + if err != nil { + t.Fatalf("Error getting extended image IDs: %v", err) + } + for _, id := range ids { + wantIDs[id] = struct{}{} + } + + // base + extended layer + different top base layer + if len(wantIDs) != baseImageLayerCount+2 { + t.Errorf("Expected to have %d unique layer IDs but have %d", baseImageLayerCount+2, len(wantIDs)) + } + + const layerFileName = "layer.tar" + 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 filepath.Base(hdr.Name) == layerFileName { + id := filepath.Dir(hdr.Name) + if _, ok := wantIDs[id]; ok { + delete(wantIDs, id) + } else { + t.Errorf("Found unwanted layer with ID %q", id) + } + } + } + if len(wantIDs) != 0 { + for id := range wantIDs { + t.Errorf("Expected to find layer with ID %q but it didn't exist", id) + } + } +} + +func v1LayerIDs(img v1.Image) ([]string, error) { + layers, err := img.Layers() + if err != nil { + return nil, fmt.Errorf("get layers: %w", err) + } + ids := make([]string, len(layers)) + parentID := "" + for i, layer := range layers { + var rawCfg []byte + if i == len(layers)-1 { + rawCfg, err = img.RawConfigFile() + if err != nil { + return nil, fmt.Errorf("get raw config file: %w", err) + } + } + id, err := v1LayerID(layer, parentID, rawCfg) + if err != nil { + return nil, fmt.Errorf("get v1 layer ID: %w", err) + } + + ids[i] = id + parentID = id + } + return ids, nil +} |