...

Source file src/github.com/google/go-containerregistry/pkg/legacy/tarball/write_test.go

Documentation: github.com/google/go-containerregistry/pkg/legacy/tarball

     1  // Copyright 2019 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  	// Make a tempfile for tarball writes.
    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  	// Make a random image
    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  	// Make sure the image is valid and can be loaded.
    67  	// Load it both by nil and by its name.
    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  	// Try loading a different tag, it should error.
    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  	// Make a tempfile for tarball writes.
    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  	// Make a random image
   102  	randImage, err := random.Image(256, 8)
   103  	if err != nil {
   104  		t.Fatalf("Error creating random image.")
   105  	}
   106  
   107  	// Make two tags that point to the random image above.
   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  	// Write the images with both tags to the tarball
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   165  	randImage1, err := random.Image(256, 8)
   166  	if err != nil {
   167  		t.Fatalf("Error creating random image 1: %v", err)
   168  	}
   169  
   170  	// Make another random image
   171  	randImage2, err := random.Image(256, 8)
   172  	if err != nil {
   173  		t.Fatalf("Error creating random image 2: %v", err)
   174  	}
   175  
   176  	// Make another random image
   177  	randImage3, err := random.Image(256, 8)
   178  	if err != nil {
   179  		t.Fatalf("Error creating random image 3: %v", err)
   180  	}
   181  
   182  	// Create two tags, one pointing to each image created.
   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  	// Write both images to the tarball.
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   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  	// Make a random image.
   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  	// Blank out the layer history.
   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  	// Make a tempfile for tarball writes.
   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  	// Build a history for 2 layers that is interspersed with empty layer
   330  	// history.
   331  	h := []v1.History{
   332  		{EmptyLayer: true},
   333  		{EmptyLayer: false},
   334  		{EmptyLayer: true},
   335  		{EmptyLayer: false},
   336  		{EmptyLayer: true},
   337  	}
   338  	// Make a random image with the number of non-empty layers from the history
   339  	// above.
   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  	// Override the config history with our custom built history that includes
   349  	// history for empty layers.
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   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  	// Set the history such that number of history entries != layers. This
   387  	// should trigger an error during the image write.
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   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  	// Make a tempfile for tarball writes.
   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  // TestWriteSharedLayers tests that writing a tarball of multiple images that
   471  // share some layers only writes those shared layers once.
   472  func TestWriteSharedLayers(t *testing.T) {
   473  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   485  	baseImage, err := random.Image(256, baseImageLayerCount)
   486  	if err != nil {
   487  		t.Fatalf("Error creating base image: %v", err)
   488  	}
   489  
   490  	// Make another random image
   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  	// Create two tags, one pointing to each image created.
   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  	// Write both images to the tarball.
   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  	// base + extended layer + different top base layer
   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