...

Source file src/github.com/google/go-containerregistry/pkg/v1/mutate/mutate_test.go

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

     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 mutate_test
    16  
    17  import (
    18  	"archive/tar"
    19  	"bytes"
    20  	"errors"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	v1 "github.com/google/go-containerregistry/pkg/v1"
    32  	"github.com/google/go-containerregistry/pkg/v1/empty"
    33  	"github.com/google/go-containerregistry/pkg/v1/match"
    34  	"github.com/google/go-containerregistry/pkg/v1/mutate"
    35  	"github.com/google/go-containerregistry/pkg/v1/partial"
    36  	"github.com/google/go-containerregistry/pkg/v1/random"
    37  	"github.com/google/go-containerregistry/pkg/v1/stream"
    38  	"github.com/google/go-containerregistry/pkg/v1/tarball"
    39  	"github.com/google/go-containerregistry/pkg/v1/types"
    40  	"github.com/google/go-containerregistry/pkg/v1/validate"
    41  )
    42  
    43  func TestExtractWhiteout(t *testing.T) {
    44  	img, err := tarball.ImageFromPath("testdata/whiteout_image.tar", nil)
    45  	if err != nil {
    46  		t.Errorf("Error loading image: %v", err)
    47  	}
    48  	tarPath, _ := filepath.Abs("img.tar")
    49  	defer os.Remove(tarPath)
    50  	tr := tar.NewReader(mutate.Extract(img))
    51  	for {
    52  		header, err := tr.Next()
    53  		if errors.Is(err, io.EOF) {
    54  			break
    55  		}
    56  		name := header.Name
    57  		for _, part := range filepath.SplitList(name) {
    58  			if part == "foo" {
    59  				t.Errorf("whiteout file found in tar: %v", name)
    60  			}
    61  		}
    62  	}
    63  }
    64  
    65  func TestExtractOverwrittenFile(t *testing.T) {
    66  	img, err := tarball.ImageFromPath("testdata/overwritten_file.tar", nil)
    67  	if err != nil {
    68  		t.Fatalf("Error loading image: %v", err)
    69  	}
    70  	tr := tar.NewReader(mutate.Extract(img))
    71  	for {
    72  		header, err := tr.Next()
    73  		if errors.Is(err, io.EOF) {
    74  			break
    75  		}
    76  		name := header.Name
    77  		if strings.Contains(name, "foo.txt") {
    78  			var buf bytes.Buffer
    79  			buf.ReadFrom(tr)
    80  			if strings.Contains(buf.String(), "foo") {
    81  				t.Errorf("Contents of file were not correctly overwritten")
    82  			}
    83  		}
    84  	}
    85  }
    86  
    87  // TestExtractError tests that if there are any errors encountered
    88  func TestExtractError(t *testing.T) {
    89  	rc := mutate.Extract(invalidImage{})
    90  	if _, err := io.Copy(io.Discard, rc); err == nil {
    91  		t.Errorf("rc.Read; got nil error")
    92  	} else if !strings.Contains(err.Error(), errInvalidImage.Error()) {
    93  		t.Errorf("rc.Read; got %v, want %v", err, errInvalidImage)
    94  	}
    95  }
    96  
    97  // TestExtractPartialRead tests that the reader can be partially read (e.g.,
    98  // tar headers) and closed without error.
    99  func TestExtractPartialRead(t *testing.T) {
   100  	rc := mutate.Extract(invalidImage{})
   101  	if _, err := io.Copy(io.Discard, io.LimitReader(rc, 1)); err != nil {
   102  		t.Errorf("Could not read one byte from reader")
   103  	}
   104  	if err := rc.Close(); err != nil {
   105  		t.Errorf("rc.Close: %v", err)
   106  	}
   107  }
   108  
   109  // invalidImage is an image which returns an error when Layers() is called.
   110  type invalidImage struct {
   111  	v1.Image
   112  }
   113  
   114  var errInvalidImage = errors.New("invalid image")
   115  
   116  func (invalidImage) Layers() ([]v1.Layer, error) {
   117  	return nil, errInvalidImage
   118  }
   119  
   120  func TestNoopCondition(t *testing.T) {
   121  	source := sourceImage(t)
   122  
   123  	result, err := mutate.AppendLayers(source, []v1.Layer{}...)
   124  	if err != nil {
   125  		t.Fatalf("Unexpected error creating a writable image: %v", err)
   126  	}
   127  
   128  	if !manifestsAreEqual(t, source, result) {
   129  		t.Error("manifests are not the same")
   130  	}
   131  
   132  	if !configFilesAreEqual(t, source, result) {
   133  		t.Fatal("config files are not the same")
   134  	}
   135  }
   136  
   137  func TestAppendWithAddendum(t *testing.T) {
   138  	source := sourceImage(t)
   139  
   140  	addendum := mutate.Addendum{
   141  		Layer: mockLayer{},
   142  		History: v1.History{
   143  			Author: "dave",
   144  		},
   145  		URLs: []string{
   146  			"example.com",
   147  		},
   148  		Annotations: map[string]string{
   149  			"foo": "bar",
   150  		},
   151  		MediaType: types.MediaType("foo"),
   152  	}
   153  
   154  	result, err := mutate.Append(source, addendum)
   155  	if err != nil {
   156  		t.Fatalf("failed to append: %v", err)
   157  	}
   158  
   159  	layers := getLayers(t, result)
   160  
   161  	if diff := cmp.Diff(layers[1], mockLayer{}); diff != "" {
   162  		t.Fatalf("correct layer was not appended (-got, +want) %v", diff)
   163  	}
   164  
   165  	if configSizesAreEqual(t, source, result) {
   166  		t.Fatal("adding a layer MUST change the config file size")
   167  	}
   168  
   169  	cf := getConfigFile(t, result)
   170  
   171  	if diff := cmp.Diff(cf.History[1], addendum.History); diff != "" {
   172  		t.Fatalf("the appended history is not the same (-got, +want) %s", diff)
   173  	}
   174  
   175  	m, err := result.Manifest()
   176  	if err != nil {
   177  		t.Fatalf("failed to get manifest: %v", err)
   178  	}
   179  
   180  	if diff := cmp.Diff(m.Layers[1].URLs, addendum.URLs); diff != "" {
   181  		t.Fatalf("the appended URLs is not the same (-got, +want) %s", diff)
   182  	}
   183  
   184  	if diff := cmp.Diff(m.Layers[1].Annotations, addendum.Annotations); diff != "" {
   185  		t.Fatalf("the appended Annotations is not the same (-got, +want) %s", diff)
   186  	}
   187  	if diff := cmp.Diff(m.Layers[1].MediaType, addendum.MediaType); diff != "" {
   188  		t.Fatalf("the appended MediaType is not the same (-got, +want) %s", diff)
   189  	}
   190  }
   191  
   192  func TestAppendLayers(t *testing.T) {
   193  	source := sourceImage(t)
   194  	layer, err := random.Layer(100, types.DockerLayer)
   195  	if err != nil {
   196  		t.Fatal(err)
   197  	}
   198  	result, err := mutate.AppendLayers(source, layer)
   199  	if err != nil {
   200  		t.Fatalf("failed to append a layer: %v", err)
   201  	}
   202  
   203  	if manifestsAreEqual(t, source, result) {
   204  		t.Fatal("appending a layer did not mutate the manifest")
   205  	}
   206  
   207  	if configFilesAreEqual(t, source, result) {
   208  		t.Fatal("appending a layer did not mutate the config file")
   209  	}
   210  
   211  	if configSizesAreEqual(t, source, result) {
   212  		t.Fatal("adding a layer MUST change the config file size")
   213  	}
   214  
   215  	layers := getLayers(t, result)
   216  
   217  	if got, want := len(layers), 2; got != want {
   218  		t.Fatalf("Layers did not return the appended layer "+
   219  			"- got size %d; expected 2", len(layers))
   220  	}
   221  
   222  	if layers[1] != layer {
   223  		t.Errorf("correct layer was not appended: got %v; want %v", layers[1], layer)
   224  	}
   225  
   226  	if err := validate.Image(result); err != nil {
   227  		t.Errorf("validate.Image() = %v", err)
   228  	}
   229  }
   230  
   231  func TestMutateConfig(t *testing.T) {
   232  	source := sourceImage(t)
   233  	cfg, err := source.ConfigFile()
   234  	if err != nil {
   235  		t.Fatalf("error getting source config file")
   236  	}
   237  
   238  	newEnv := []string{"foo=bar"}
   239  	cfg.Config.Env = newEnv
   240  	result, err := mutate.Config(source, cfg.Config)
   241  	if err != nil {
   242  		t.Fatalf("failed to mutate a config: %v", err)
   243  	}
   244  
   245  	if manifestsAreEqual(t, source, result) {
   246  		t.Error("mutating the config MUST mutate the manifest")
   247  	}
   248  
   249  	if configFilesAreEqual(t, source, result) {
   250  		t.Error("mutating the config did not mutate the config file")
   251  	}
   252  
   253  	if configSizesAreEqual(t, source, result) {
   254  		t.Error("adding an environment variable MUST change the config file size")
   255  	}
   256  
   257  	if configDigestsAreEqual(t, source, result) {
   258  		t.Errorf("mutating the config MUST mutate the config digest")
   259  	}
   260  
   261  	if !reflect.DeepEqual(cfg.Config.Env, newEnv) {
   262  		t.Errorf("incorrect environment set %v!=%v", cfg.Config.Env, newEnv)
   263  	}
   264  
   265  	if err := validate.Image(result); err != nil {
   266  		t.Errorf("validate.Image() = %v", err)
   267  	}
   268  }
   269  
   270  type arbitrary struct {
   271  }
   272  
   273  func (arbitrary) RawManifest() ([]byte, error) {
   274  	return []byte(`{"hello":"world"}`), nil
   275  }
   276  func TestAnnotations(t *testing.T) {
   277  	anns := map[string]string{
   278  		"foo": "bar",
   279  	}
   280  
   281  	for _, c := range []struct {
   282  		desc string
   283  		in   partial.WithRawManifest
   284  		want string
   285  	}{{
   286  		desc: "image",
   287  		in:   empty.Image,
   288  		want: `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","size":115,"digest":"sha256:5b943e2b943f6c81dbbd4e2eca5121f4fcc39139e3d1219d6d89bd925b77d9fe"},"layers":[],"annotations":{"foo":"bar"}}`,
   289  	}, {
   290  		desc: "index",
   291  		in:   empty.Index,
   292  		want: `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[],"annotations":{"foo":"bar"}}`,
   293  	}, {
   294  		desc: "arbitrary",
   295  		in:   arbitrary{},
   296  		want: `{"annotations":{"foo":"bar"},"hello":"world"}`,
   297  	}} {
   298  		t.Run(c.desc, func(t *testing.T) {
   299  			got, err := mutate.Annotations(c.in, anns).RawManifest()
   300  			if err != nil {
   301  				t.Fatalf("Annotations: %v", err)
   302  			}
   303  			if d := cmp.Diff(c.want, string(got)); d != "" {
   304  				t.Errorf("Diff(-want,+got): %s", d)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func TestMutateCreatedAt(t *testing.T) {
   311  	source := sourceImage(t)
   312  	want := time.Now().Add(-2 * time.Minute)
   313  	result, err := mutate.CreatedAt(source, v1.Time{Time: want})
   314  	if err != nil {
   315  		t.Fatalf("CreatedAt: %v", err)
   316  	}
   317  
   318  	if configDigestsAreEqual(t, source, result) {
   319  		t.Errorf("mutating the created time MUST mutate the config digest")
   320  	}
   321  
   322  	got := getConfigFile(t, result).Created.Time
   323  	if got != want {
   324  		t.Errorf("mutating the created time MUST mutate the time from %v to %v", got, want)
   325  	}
   326  }
   327  
   328  func TestMutateTime(t *testing.T) {
   329  	for _, tc := range []struct {
   330  		name   string
   331  		source v1.Image
   332  	}{
   333  		{
   334  			name:   "image with matching history and layers",
   335  			source: sourceImage(t),
   336  		},
   337  		{
   338  			name:   "image with empty_layer history entries",
   339  			source: sourceImagePath(t, "testdata/source_image_with_empty_layer_history.tar"),
   340  		},
   341  	} {
   342  		t.Run(tc.name, func(t *testing.T) {
   343  			want := time.Time{}
   344  			result, err := mutate.Time(tc.source, want)
   345  			if err != nil {
   346  				t.Fatalf("failed to mutate a config: %v", err)
   347  			}
   348  
   349  			if configDigestsAreEqual(t, tc.source, result) {
   350  				t.Fatal("mutating the created time MUST mutate the config digest")
   351  			}
   352  
   353  			mutatedOriginalConfig := getConfigFile(t, tc.source).DeepCopy()
   354  			gotConfig := getConfigFile(t, result)
   355  
   356  			// manually change the fields we expect to be changed by mutate.Time
   357  			mutatedOriginalConfig.Author = ""
   358  			mutatedOriginalConfig.Created = v1.Time{Time: want}
   359  			for i := range mutatedOriginalConfig.History {
   360  				mutatedOriginalConfig.History[i].Created = v1.Time{Time: want}
   361  				mutatedOriginalConfig.History[i].Author = ""
   362  			}
   363  
   364  			if diff := cmp.Diff(mutatedOriginalConfig, gotConfig,
   365  				cmpopts.IgnoreFields(v1.RootFS{}, "DiffIDs"),
   366  			); diff != "" {
   367  				t.Errorf("configFile() mismatch (-want +got):\n%s", diff)
   368  			}
   369  		})
   370  	}
   371  }
   372  
   373  func TestMutateMediaType(t *testing.T) {
   374  	want := types.OCIManifestSchema1
   375  	wantCfg := types.OCIConfigJSON
   376  	img := mutate.MediaType(empty.Image, want)
   377  	img = mutate.ConfigMediaType(img, wantCfg)
   378  	got, err := img.MediaType()
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	if want != got {
   383  		t.Errorf("%q != %q", want, got)
   384  	}
   385  	manifest, err := img.Manifest()
   386  	if err != nil {
   387  		t.Fatal(err)
   388  	}
   389  	if manifest.MediaType == "" {
   390  		t.Error("MediaType should be set for OCI media types")
   391  	}
   392  	if gotCfg := manifest.Config.MediaType; gotCfg != wantCfg {
   393  		t.Errorf("manifest.Config.MediaType = %v, wanted %v", gotCfg, wantCfg)
   394  	}
   395  
   396  	want = types.DockerManifestSchema2
   397  	wantCfg = types.DockerConfigJSON
   398  	img = mutate.MediaType(img, want)
   399  	img = mutate.ConfigMediaType(img, wantCfg)
   400  	got, err = img.MediaType()
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  	if want != got {
   405  		t.Errorf("%q != %q", want, got)
   406  	}
   407  	manifest, err = img.Manifest()
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  	if manifest.MediaType != want {
   412  		t.Errorf("MediaType should be set for Docker media types: %v", manifest.MediaType)
   413  	}
   414  	if gotCfg := manifest.Config.MediaType; gotCfg != wantCfg {
   415  		t.Errorf("manifest.Config.MediaType = %v, wanted %v", gotCfg, wantCfg)
   416  	}
   417  
   418  	want = types.OCIImageIndex
   419  	idx := mutate.IndexMediaType(empty.Index, want)
   420  	got, err = idx.MediaType()
   421  	if err != nil {
   422  		t.Fatal(err)
   423  	}
   424  	if want != got {
   425  		t.Errorf("%q != %q", want, got)
   426  	}
   427  	im, err := idx.IndexManifest()
   428  	if err != nil {
   429  		t.Fatal(err)
   430  	}
   431  	if im.MediaType == "" {
   432  		t.Error("MediaType should be set for OCI media types")
   433  	}
   434  
   435  	want = types.DockerManifestList
   436  	idx = mutate.IndexMediaType(idx, want)
   437  	got, err = idx.MediaType()
   438  	if err != nil {
   439  		t.Fatal(err)
   440  	}
   441  	if want != got {
   442  		t.Errorf("%q != %q", want, got)
   443  	}
   444  	im, err = idx.IndexManifest()
   445  	if err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	if im.MediaType != want {
   449  		t.Errorf("MediaType should be set for Docker media types: %v", im.MediaType)
   450  	}
   451  }
   452  
   453  func TestAppendStreamableLayer(t *testing.T) {
   454  	img, err := mutate.AppendLayers(
   455  		sourceImage(t),
   456  		stream.NewLayer(io.NopCloser(strings.NewReader(strings.Repeat("a", 100)))),
   457  		stream.NewLayer(io.NopCloser(strings.NewReader(strings.Repeat("b", 100)))),
   458  		stream.NewLayer(io.NopCloser(strings.NewReader(strings.Repeat("c", 100)))),
   459  	)
   460  	if err != nil {
   461  		t.Fatalf("AppendLayers: %v", err)
   462  	}
   463  
   464  	// Until the streams are consumed, the image manifest is not yet computed.
   465  	if _, err := img.Manifest(); !errors.Is(err, stream.ErrNotComputed) {
   466  		t.Errorf("Manifest: got %v, want %v", err, stream.ErrNotComputed)
   467  	}
   468  
   469  	// We can still get Layers while some are not yet computed.
   470  	ls, err := img.Layers()
   471  	if err != nil {
   472  		t.Errorf("Layers: %v", err)
   473  	}
   474  	wantDigests := []string{
   475  		"sha256:bfa1c600931132f55789459e2f5a5eb85659ac91bc5a54ce09e3ed14809f8a7f",
   476  		"sha256:77a52b9a141dcc4d3d277d053193765dca725626f50eaf56b903ac2439cf7fd1",
   477  		"sha256:b78472d63f6e3d31059819173b56fcb0d9479a2b13c097d4addd84889f6aff06",
   478  	}
   479  	for i, l := range ls[1:] {
   480  		rc, err := l.Compressed()
   481  		if err != nil {
   482  			t.Errorf("Layer %d Compressed: %v", i, err)
   483  		}
   484  
   485  		// Consume the layer's stream and close it to compute the
   486  		// layer's metadata.
   487  		if _, err := io.Copy(io.Discard, rc); err != nil {
   488  			t.Errorf("Reading layer %d: %v", i, err)
   489  		}
   490  		if err := rc.Close(); err != nil {
   491  			t.Errorf("Closing layer %d: %v", i, err)
   492  		}
   493  
   494  		// The layer's metadata is now available.
   495  		h, err := l.Digest()
   496  		if err != nil {
   497  			t.Errorf("Digest after consuming layer %d: %v", i, err)
   498  		}
   499  		if h.String() != wantDigests[i] {
   500  			t.Errorf("Layer %d digest got %q, want %q", i, h, wantDigests[i])
   501  		}
   502  	}
   503  
   504  	// Now that the streamable layers have been consumed, the image's
   505  	// manifest can be computed.
   506  	if _, err := img.Manifest(); err != nil {
   507  		t.Errorf("Manifest: %v", err)
   508  	}
   509  
   510  	h, err := img.Digest()
   511  	if err != nil {
   512  		t.Errorf("Digest: %v", err)
   513  	}
   514  	wantDigest := "sha256:14d140947afedc6901b490265a08bc8ebe7f9d9faed6fdf19a451f054a7dd746"
   515  	if h.String() != wantDigest {
   516  		t.Errorf("Image digest got %q, want %q", h, wantDigest)
   517  	}
   518  }
   519  
   520  func TestCanonical(t *testing.T) {
   521  	source := sourceImage(t)
   522  	img, err := mutate.Canonical(source)
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	sourceCf, err := source.ConfigFile()
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  	cf, err := img.ConfigFile()
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	for _, h := range cf.History {
   535  		want := "bazel build ..."
   536  		got := h.CreatedBy
   537  		if want != got {
   538  			t.Errorf("%q != %q", want, got)
   539  		}
   540  	}
   541  	var want, got string
   542  	want = cf.Architecture
   543  	got = sourceCf.Architecture
   544  	if want != got {
   545  		t.Errorf("%q != %q", want, got)
   546  	}
   547  	want = cf.OS
   548  	got = sourceCf.OS
   549  	if want != got {
   550  		t.Errorf("%q != %q", want, got)
   551  	}
   552  	want = cf.OSVersion
   553  	got = sourceCf.OSVersion
   554  	if want != got {
   555  		t.Errorf("%q != %q", want, got)
   556  	}
   557  	for _, s := range []string{
   558  		cf.Container,
   559  		cf.Config.Hostname,
   560  		cf.DockerVersion,
   561  	} {
   562  		if s != "" {
   563  			t.Errorf("non-zeroed string: %v", s)
   564  		}
   565  	}
   566  
   567  	expectedLayerTime := time.Unix(0, 0)
   568  	layers := getLayers(t, img)
   569  	for _, layer := range layers {
   570  		assertMTime(t, layer, expectedLayerTime)
   571  	}
   572  }
   573  
   574  func TestRemoveManifests(t *testing.T) {
   575  	// Load up the registry.
   576  	count := 3
   577  	for i := 0; i < count; i++ {
   578  		ii, err := random.Index(1024, int64(count), int64(count))
   579  		if err != nil {
   580  			t.Fatal(err)
   581  		}
   582  		// test removing the first layer, second layer or the third layer
   583  		manifest, err := ii.IndexManifest()
   584  		if err != nil {
   585  			t.Fatal(err)
   586  		}
   587  		if len(manifest.Manifests) != count {
   588  			t.Fatalf("mismatched manifests on setup, had %d, expected %d", len(manifest.Manifests), count)
   589  		}
   590  		digest := manifest.Manifests[i].Digest
   591  		ii = mutate.RemoveManifests(ii, match.Digests(digest))
   592  		manifest, err = ii.IndexManifest()
   593  		if err != nil {
   594  			t.Fatal(err)
   595  		}
   596  		if len(manifest.Manifests) != (count - 1) {
   597  			t.Fatalf("mismatched manifests after removal, had %d, expected %d", len(manifest.Manifests), count-1)
   598  		}
   599  		for j, m := range manifest.Manifests {
   600  			if m.Digest == digest {
   601  				t.Fatalf("unexpectedly found removed hash %v at position %d", digest, j)
   602  			}
   603  		}
   604  	}
   605  }
   606  
   607  func TestImageImmutability(t *testing.T) {
   608  	img := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
   609  
   610  	t.Run("manifest", func(t *testing.T) {
   611  		// Check that Manifest is immutable.
   612  		changed, err := img.Manifest()
   613  		if err != nil {
   614  			t.Errorf("Manifest() = %v", err)
   615  		}
   616  		want := changed.DeepCopy() // Create a copy of original before mutating it.
   617  		changed.MediaType = types.DockerManifestList
   618  
   619  		if got, err := img.Manifest(); err != nil {
   620  			t.Errorf("Manifest() = %v", err)
   621  		} else if !cmp.Equal(got, want) {
   622  			t.Errorf("manifest changed! %s", cmp.Diff(got, want))
   623  		}
   624  	})
   625  
   626  	t.Run("config file", func(t *testing.T) {
   627  		// Check that ConfigFile is immutable.
   628  		changed, err := img.ConfigFile()
   629  		if err != nil {
   630  			t.Errorf("ConfigFile() = %v", err)
   631  		}
   632  		want := changed.DeepCopy() // Create a copy of original before mutating it.
   633  		changed.Author = "Jay Pegg"
   634  
   635  		if got, err := img.ConfigFile(); err != nil {
   636  			t.Errorf("ConfigFile() = %v", err)
   637  		} else if !cmp.Equal(got, want) {
   638  			t.Errorf("ConfigFile changed! %s", cmp.Diff(got, want))
   639  		}
   640  	})
   641  }
   642  
   643  func assertMTime(t *testing.T, layer v1.Layer, expectedTime time.Time) {
   644  	l, err := layer.Uncompressed()
   645  
   646  	if err != nil {
   647  		t.Fatalf("reading layer failed: %v", err)
   648  	}
   649  
   650  	tr := tar.NewReader(l)
   651  	for {
   652  		header, err := tr.Next()
   653  		if errors.Is(err, io.EOF) {
   654  			break
   655  		}
   656  		if err != nil {
   657  			t.Fatalf("Error reading layer: %v", err)
   658  		}
   659  
   660  		mtime := header.ModTime
   661  		if mtime.Equal(expectedTime) == false {
   662  			t.Errorf("unexpected mod time for layer. expected %v, got %v.", expectedTime, mtime)
   663  		}
   664  	}
   665  }
   666  
   667  func sourceImage(t *testing.T) v1.Image {
   668  	return sourceImagePath(t, "testdata/source_image.tar")
   669  }
   670  
   671  func sourceImagePath(t *testing.T, tarPath string) v1.Image {
   672  	t.Helper()
   673  
   674  	image, err := tarball.ImageFromPath(tarPath, nil)
   675  	if err != nil {
   676  		t.Fatalf("Error loading image: %v", err)
   677  	}
   678  	return image
   679  }
   680  
   681  func getManifest(t *testing.T, i v1.Image) *v1.Manifest {
   682  	t.Helper()
   683  
   684  	m, err := i.Manifest()
   685  	if err != nil {
   686  		t.Fatalf("Error fetching image manifest: %v", err)
   687  	}
   688  
   689  	return m
   690  }
   691  
   692  func getLayers(t *testing.T, i v1.Image) []v1.Layer {
   693  	t.Helper()
   694  
   695  	l, err := i.Layers()
   696  	if err != nil {
   697  		t.Fatalf("Error fetching image layers: %v", err)
   698  	}
   699  
   700  	return l
   701  }
   702  
   703  func getConfigFile(t *testing.T, i v1.Image) *v1.ConfigFile {
   704  	t.Helper()
   705  
   706  	c, err := i.ConfigFile()
   707  	if err != nil {
   708  		t.Fatalf("Error fetching image config file: %v", err)
   709  	}
   710  
   711  	return c
   712  }
   713  
   714  func configFilesAreEqual(t *testing.T, first, second v1.Image) bool {
   715  	t.Helper()
   716  
   717  	fc := getConfigFile(t, first)
   718  	sc := getConfigFile(t, second)
   719  
   720  	return cmp.Equal(fc, sc)
   721  }
   722  
   723  func configDigestsAreEqual(t *testing.T, first, second v1.Image) bool {
   724  	t.Helper()
   725  
   726  	fm := getManifest(t, first)
   727  	sm := getManifest(t, second)
   728  
   729  	return fm.Config.Digest == sm.Config.Digest
   730  }
   731  
   732  func configSizesAreEqual(t *testing.T, first, second v1.Image) bool {
   733  	t.Helper()
   734  
   735  	fm := getManifest(t, first)
   736  	sm := getManifest(t, second)
   737  
   738  	return fm.Config.Size == sm.Config.Size
   739  }
   740  
   741  func manifestsAreEqual(t *testing.T, first, second v1.Image) bool {
   742  	t.Helper()
   743  
   744  	fm := getManifest(t, first)
   745  	sm := getManifest(t, second)
   746  
   747  	return cmp.Equal(fm, sm)
   748  }
   749  
   750  type mockLayer struct{}
   751  
   752  func (m mockLayer) Digest() (v1.Hash, error) {
   753  	return v1.Hash{Algorithm: "fake", Hex: "digest"}, nil
   754  }
   755  
   756  func (m mockLayer) DiffID() (v1.Hash, error) {
   757  	return v1.Hash{Algorithm: "fake", Hex: "diff id"}, nil
   758  }
   759  
   760  func (m mockLayer) MediaType() (types.MediaType, error) {
   761  	return "some-media-type", nil
   762  }
   763  
   764  func (m mockLayer) Size() (int64, error) { return 137438691328, nil }
   765  func (m mockLayer) Compressed() (io.ReadCloser, error) {
   766  	return io.NopCloser(strings.NewReader("compressed times")), nil
   767  }
   768  func (m mockLayer) Uncompressed() (io.ReadCloser, error) {
   769  	return io.NopCloser(strings.NewReader("uncompressed")), nil
   770  }
   771  

View as plain text