...

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

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

     1  // Copyright 2018 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_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  	// 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.")
    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  	// Make sure the image is valid and can be loaded.
    62  	// Load it both by nil and by its name.
    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  	// Try loading a different tag, it should error.
    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  	// Make a tempfile for tarball writes.
    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  	// Make a random image
    99  	randImage, err := random.Image(256, 8)
   100  	if err != nil {
   101  		t.Fatalf("Error creating random image.")
   102  	}
   103  
   104  	// Make two tags that point to the random image above.
   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  	// Write the images with both tags to the tarball
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   158  	randImage1, err := random.Image(256, 8)
   159  	if err != nil {
   160  		t.Fatalf("Error creating random image 1.")
   161  	}
   162  
   163  	// Make another random image
   164  	randImage2, err := random.Image(256, 8)
   165  	if err != nil {
   166  		t.Fatalf("Error creating random image 2.")
   167  	}
   168  
   169  	// Make another random image
   170  	randImage3, err := random.Image(256, 8)
   171  	if err != nil {
   172  		t.Fatalf("Error creating random image 3.")
   173  	}
   174  
   175  	// Create two tags, one pointing to each image created.
   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  	// Write both images to the tarball.
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   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  	// Make a tempfile for tarball writes.
   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  	// Make a random image
   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  	// Write the images with both tags to the tarball
   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  	// https://github.com/google/go-containerregistry/issues/890
   384  	randomTagWritten := "ubuntu:latest"
   385  
   386  	// Make a random image
   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  	// calculate the manifest
   434  	m, err := tarball.ComputeManifest(refToImage)
   435  	if err != nil {
   436  		t.Fatalf("Unexpected error calculating manifest: %v", err)
   437  	}
   438  	// the order of these two is based on the repo tags
   439  	// so mutated "gcr.io/baz/bat:latest" is before random "gcr.io/foo/bar:latest"
   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