github.com/moby/docker@v26.1.3+incompatible/integration-cli/docker_cli_by_digest_test.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"regexp"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/docker/distribution/manifest/schema1"
    13  	"github.com/docker/distribution/manifest/schema2"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/integration-cli/cli"
    16  	"github.com/docker/docker/integration-cli/cli/build"
    17  	"github.com/opencontainers/go-digest"
    18  	"gotest.tools/v3/assert"
    19  	is "gotest.tools/v3/assert/cmp"
    20  	"gotest.tools/v3/skip"
    21  )
    22  
    23  const (
    24  	remoteRepoName = "dockercli/busybox-by-dgst"
    25  	repoName       = privateRegistryURL + "/" + remoteRepoName
    26  )
    27  
    28  var (
    29  	pushDigestRegex = regexp.MustCompile(`[\S]+: digest: ([\S]+) size: [0-9]+`)
    30  	digestRegex     = regexp.MustCompile(`Digest: ([\S]+)`)
    31  )
    32  
    33  func setupImage(c *testing.T) (digest.Digest, error) {
    34  	return setupImageWithTag(c, "latest")
    35  }
    36  
    37  func setupImageWithTag(c *testing.T, tag string) (digest.Digest, error) {
    38  	const containerName = "busyboxbydigest"
    39  
    40  	// new file is committed because this layer is used for detecting malicious
    41  	// changes. if this was committed as empty layer it would be skipped on pull
    42  	// and malicious changes would never be detected.
    43  	cli.DockerCmd(c, "run", "-e", "digest=1", "--name", containerName, "busybox", "touch", "anewfile")
    44  
    45  	// tag the image to upload it to the private registry
    46  	repoAndTag := repoName + ":" + tag
    47  	cli.DockerCmd(c, "commit", containerName, repoAndTag)
    48  
    49  	// delete the container as we don't need it any more
    50  	cli.DockerCmd(c, "rm", "-fv", containerName)
    51  
    52  	// push the image
    53  	out := cli.DockerCmd(c, "push", repoAndTag).Combined()
    54  
    55  	// delete our local repo that we previously tagged
    56  	cli.DockerCmd(c, "rmi", repoAndTag)
    57  
    58  	matches := pushDigestRegex.FindStringSubmatch(out)
    59  	assert.Equal(c, len(matches), 2, "unable to parse digest from push output: %s", out)
    60  	pushDigest := matches[1]
    61  
    62  	return digest.Digest(pushDigest), nil
    63  }
    64  
    65  func testPullByTagDisplaysDigest(c *testing.T) {
    66  	testRequires(c, DaemonIsLinux)
    67  	pushDigest, err := setupImage(c)
    68  	assert.NilError(c, err, "error setting up image")
    69  
    70  	// pull from the registry using the tag
    71  	out := cli.DockerCmd(c, "pull", repoName).Combined()
    72  
    73  	// the pull output includes "Digest: <digest>", so find that
    74  	matches := digestRegex.FindStringSubmatch(out)
    75  	assert.Equal(c, len(matches), 2, "unable to parse digest from push output: %s", out)
    76  	pullDigest := matches[1]
    77  
    78  	// make sure the pushed and pull digests match
    79  	assert.Equal(c, pushDigest.String(), pullDigest)
    80  }
    81  
    82  func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *testing.T) {
    83  	testPullByTagDisplaysDigest(c)
    84  }
    85  
    86  func (s *DockerSchema1RegistrySuite) TestPullByTagDisplaysDigest(c *testing.T) {
    87  	testPullByTagDisplaysDigest(c)
    88  }
    89  
    90  func testPullByDigest(c *testing.T) {
    91  	testRequires(c, DaemonIsLinux)
    92  	pushDigest, err := setupImage(c)
    93  	assert.NilError(c, err, "error setting up image")
    94  
    95  	// pull from the registry using the <name>@<digest> reference
    96  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
    97  	out := cli.DockerCmd(c, "pull", imageReference).Combined()
    98  
    99  	// the pull output includes "Digest: <digest>", so find that
   100  	matches := digestRegex.FindStringSubmatch(out)
   101  	assert.Equal(c, len(matches), 2, "unable to parse digest from push output: %s", out)
   102  	pullDigest := matches[1]
   103  
   104  	// make sure the pushed and pull digests match
   105  	assert.Equal(c, pushDigest.String(), pullDigest)
   106  }
   107  
   108  func (s *DockerRegistrySuite) TestPullByDigest(c *testing.T) {
   109  	testPullByDigest(c)
   110  }
   111  
   112  func (s *DockerSchema1RegistrySuite) TestPullByDigest(c *testing.T) {
   113  	testPullByDigest(c)
   114  }
   115  
   116  func testPullByDigestNoFallback(c *testing.T) {
   117  	testRequires(c, DaemonIsLinux)
   118  	// pull from the registry using the <name>@<digest> reference
   119  	imageReference := fmt.Sprintf("%s@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", repoName)
   120  	out, _, err := dockerCmdWithError("pull", imageReference)
   121  	assert.Assert(c, err != nil, "expected non-zero exit status and correct error message when pulling non-existing image")
   122  
   123  	expectedMsg := fmt.Sprintf("manifest for %s not found", imageReference)
   124  	if testEnv.UsingSnapshotter() {
   125  		expectedMsg = fmt.Sprintf("%s: not found", imageReference)
   126  	}
   127  
   128  	assert.Check(c, is.Contains(out, expectedMsg), "expected non-zero exit status and correct error message when pulling non-existing image")
   129  }
   130  
   131  func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *testing.T) {
   132  	testPullByDigestNoFallback(c)
   133  }
   134  
   135  func (s *DockerSchema1RegistrySuite) TestPullByDigestNoFallback(c *testing.T) {
   136  	testPullByDigestNoFallback(c)
   137  }
   138  
   139  func (s *DockerRegistrySuite) TestCreateByDigest(c *testing.T) {
   140  	pushDigest, err := setupImage(c)
   141  	assert.NilError(c, err, "error setting up image")
   142  
   143  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   144  
   145  	const containerName = "createByDigest"
   146  	cli.DockerCmd(c, "create", "--name", containerName, imageReference)
   147  
   148  	res := inspectField(c, containerName, "Config.Image")
   149  	assert.Equal(c, res, imageReference)
   150  }
   151  
   152  func (s *DockerRegistrySuite) TestRunByDigest(c *testing.T) {
   153  	pushDigest, err := setupImage(c)
   154  	assert.NilError(c, err)
   155  
   156  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   157  
   158  	const containerName = "runByDigest"
   159  	out := cli.DockerCmd(c, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest").Combined()
   160  
   161  	foundRegex := regexp.MustCompile("found=([^\n]+)")
   162  	matches := foundRegex.FindStringSubmatch(out)
   163  	assert.Equal(c, len(matches), 2, fmt.Sprintf("unable to parse digest from pull output: %s", out))
   164  	assert.Equal(c, matches[1], "1", fmt.Sprintf("Expected %q, got %q", "1", matches[1]))
   165  
   166  	res := inspectField(c, containerName, "Config.Image")
   167  	assert.Equal(c, res, imageReference)
   168  }
   169  
   170  func (s *DockerRegistrySuite) TestRemoveImageByDigest(c *testing.T) {
   171  	imgDigest, err := setupImage(c)
   172  	assert.NilError(c, err, "error setting up image")
   173  
   174  	imageReference := fmt.Sprintf("%s@%s", repoName, imgDigest)
   175  
   176  	// pull from the registry using the <name>@<digest> reference
   177  	cli.DockerCmd(c, "pull", imageReference)
   178  
   179  	// make sure inspect runs ok
   180  	inspectField(c, imageReference, "Id")
   181  
   182  	// do the delete
   183  	err = deleteImages(imageReference)
   184  	assert.NilError(c, err, "unexpected error deleting image")
   185  
   186  	// try to inspect again - it should error this time
   187  	_, err = inspectFieldWithError(imageReference, "Id")
   188  	// unexpected nil err trying to inspect what should be a non-existent image
   189  	assert.ErrorContains(c, err, "No such object")
   190  }
   191  
   192  func (s *DockerRegistrySuite) TestBuildByDigest(c *testing.T) {
   193  	skip.If(c, testEnv.UsingSnapshotter(), "Config.Image is not created with containerd, buildkit doesn't set it either")
   194  	imgDigest, err := setupImage(c)
   195  	assert.NilError(c, err, "error setting up image")
   196  
   197  	imageReference := fmt.Sprintf("%s@%s", repoName, imgDigest)
   198  
   199  	// pull from the registry using the <name>@<digest> reference
   200  	cli.DockerCmd(c, "pull", imageReference)
   201  
   202  	// get the image id
   203  	imageID := inspectField(c, imageReference, "Id")
   204  
   205  	// do the build
   206  	const name = "buildbydigest"
   207  	buildImageSuccessfully(c, name, build.WithDockerfile(fmt.Sprintf(
   208  		`FROM %s
   209       CMD ["/bin/echo", "Hello World"]`, imageReference)))
   210  	assert.NilError(c, err)
   211  
   212  	// get the build's image id
   213  	res := inspectField(c, name, "Config.Image")
   214  	// make sure they match
   215  	assert.Equal(c, res, imageID)
   216  }
   217  
   218  func (s *DockerRegistrySuite) TestTagByDigest(c *testing.T) {
   219  	imgDigest, err := setupImage(c)
   220  	assert.NilError(c, err, "error setting up image")
   221  
   222  	imageReference := fmt.Sprintf("%s@%s", repoName, imgDigest)
   223  
   224  	// pull from the registry using the <name>@<digest> reference
   225  	cli.DockerCmd(c, "pull", imageReference)
   226  
   227  	// tag it
   228  	const tag = "tagbydigest"
   229  	cli.DockerCmd(c, "tag", imageReference, tag)
   230  
   231  	expectedID := inspectField(c, imageReference, "Id")
   232  
   233  	tagID := inspectField(c, tag, "Id")
   234  	assert.Equal(c, tagID, expectedID)
   235  }
   236  
   237  func (s *DockerRegistrySuite) TestListImagesWithoutDigests(c *testing.T) {
   238  	imgDigest, err := setupImage(c)
   239  	assert.NilError(c, err, "error setting up image")
   240  
   241  	imageReference := fmt.Sprintf("%s@%s", repoName, imgDigest)
   242  
   243  	// pull from the registry using the <name>@<digest> reference
   244  	cli.DockerCmd(c, "pull", imageReference)
   245  
   246  	out := cli.DockerCmd(c, "images").Stdout()
   247  	assert.Assert(c, !strings.Contains(out, "DIGEST"), "list output should not have contained DIGEST header")
   248  }
   249  
   250  func (s *DockerRegistrySuite) TestListImagesWithDigests(c *testing.T) {
   251  	// setup image1
   252  	digest1, err := setupImageWithTag(c, "tag1")
   253  	assert.NilError(c, err, "error setting up image")
   254  	imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1)
   255  	c.Logf("imageReference1 = %s", imageReference1)
   256  
   257  	// pull image1 by digest
   258  	cli.DockerCmd(c, "pull", imageReference1)
   259  
   260  	// list images
   261  	out := cli.DockerCmd(c, "images", "--digests").Combined()
   262  
   263  	// make sure repo shown, tag=<none>, digest = $digest1
   264  	re1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
   265  	assert.Assert(c, re1.MatchString(out), "expected %q: %s", re1.String(), out)
   266  	// setup image2
   267  	digest2, err := setupImageWithTag(c, "tag2")
   268  	assert.NilError(c, err, "error setting up image")
   269  	imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2)
   270  	c.Logf("imageReference2 = %s", imageReference2)
   271  
   272  	// pull image1 by digest
   273  	cli.DockerCmd(c, "pull", imageReference1)
   274  
   275  	// pull image2 by digest
   276  	cli.DockerCmd(c, "pull", imageReference2)
   277  
   278  	// list images
   279  	out = cli.DockerCmd(c, "images", "--digests").Stdout()
   280  
   281  	// make sure repo shown, tag=<none>, digest = $digest1
   282  	assert.Assert(c, re1.MatchString(out), "expected %q: %s", re1.String(), out)
   283  
   284  	// make sure repo shown, tag=<none>, digest = $digest2
   285  	re2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
   286  	assert.Assert(c, re2.MatchString(out), "expected %q: %s", re2.String(), out)
   287  
   288  	// pull tag1
   289  	cli.DockerCmd(c, "pull", repoName+":tag1")
   290  
   291  	// list images
   292  	out = cli.DockerCmd(c, "images", "--digests").Stdout()
   293  
   294  	// make sure image 1 has repo, tag, <none> AND repo, <none>, digest
   295  	reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*` + digest1.String() + `\s`)
   296  	assert.Assert(c, reWithDigest1.MatchString(out), "expected %q: %s", reWithDigest1.String(), out)
   297  	// make sure image 2 has repo, <none>, digest
   298  	assert.Assert(c, re2.MatchString(out), "expected %q: %s", re2.String(), out)
   299  
   300  	// pull tag 2
   301  	cli.DockerCmd(c, "pull", repoName+":tag2")
   302  
   303  	// list images
   304  	out = cli.DockerCmd(c, "images", "--digests").Stdout()
   305  
   306  	// make sure image 1 has repo, tag, digest
   307  	assert.Assert(c, reWithDigest1.MatchString(out), "expected %q: %s", reWithDigest1.String(), out)
   308  
   309  	// make sure image 2 has repo, tag, digest
   310  	reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*` + digest2.String() + `\s`)
   311  	assert.Assert(c, reWithDigest2.MatchString(out), "expected %q: %s", reWithDigest2.String(), out)
   312  
   313  	// list images
   314  	out = cli.DockerCmd(c, "images", "--digests").Stdout()
   315  
   316  	// make sure image 1 has repo, tag, digest
   317  	assert.Assert(c, reWithDigest1.MatchString(out), "expected %q: %s", reWithDigest1.String(), out)
   318  	// make sure image 2 has repo, tag, digest
   319  	assert.Assert(c, reWithDigest2.MatchString(out), "expected %q: %s", reWithDigest2.String(), out)
   320  	// We always have a digest when using containerd to store images
   321  	if !testEnv.UsingSnapshotter() {
   322  		// make sure busybox has tag, but not digest
   323  		busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*<none>\s`)
   324  		assert.Assert(c, busyboxRe.MatchString(out), "expected %q: %s", busyboxRe.String(), out)
   325  	}
   326  }
   327  
   328  func (s *DockerRegistrySuite) TestListDanglingImagesWithDigests(c *testing.T) {
   329  	// See https://github.com/moby/moby/pull/46856
   330  	skip.If(c, testEnv.UsingSnapshotter(), "dangling=true filter behaves a bit differently with c8d")
   331  
   332  	// setup image1
   333  	digest1, err := setupImageWithTag(c, "dangle1")
   334  	assert.NilError(c, err, "error setting up image")
   335  	imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1)
   336  	c.Logf("imageReference1 = %s", imageReference1)
   337  
   338  	// pull image1 by digest
   339  	cli.DockerCmd(c, "pull", imageReference1)
   340  
   341  	// list images
   342  	out := cli.DockerCmd(c, "images", "--digests").Stdout()
   343  
   344  	// make sure repo shown, tag=<none>, digest = $digest1
   345  	re1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
   346  	assert.Assert(c, re1.MatchString(out), "expected %q: %s", re1.String(), out)
   347  	// setup image2
   348  	digest2, err := setupImageWithTag(c, "dangle2")
   349  	// error setting up image
   350  	assert.NilError(c, err)
   351  	imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2)
   352  	c.Logf("imageReference2 = %s", imageReference2)
   353  
   354  	// pull image1 by digest
   355  	cli.DockerCmd(c, "pull", imageReference1)
   356  
   357  	// pull image2 by digest
   358  	cli.DockerCmd(c, "pull", imageReference2)
   359  
   360  	// list images
   361  	out = cli.DockerCmd(c, "images", "--digests", "--filter=dangling=true").Stdout()
   362  
   363  	// make sure repo shown, tag=<none>, digest = $digest1
   364  	assert.Assert(c, re1.MatchString(out), "expected %q: %s", re1.String(), out)
   365  
   366  	// make sure repo shown, tag=<none>, digest = $digest2
   367  	re2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
   368  	assert.Assert(c, re2.MatchString(out), "expected %q: %s", re2.String(), out)
   369  
   370  	// pull dangle1 tag
   371  	cli.DockerCmd(c, "pull", repoName+":dangle1")
   372  
   373  	// list images
   374  	out = cli.DockerCmd(c, "images", "--digests", "--filter=dangling=true").Stdout()
   375  
   376  	// make sure image 1 has repo, tag, <none> AND repo, <none>, digest
   377  	reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*dangle1\s*` + digest1.String() + `\s`)
   378  	assert.Assert(c, !reWithDigest1.MatchString(out), "unexpected %q: %s", reWithDigest1.String(), out)
   379  	// make sure image 2 has repo, <none>, digest
   380  	assert.Assert(c, re2.MatchString(out), "expected %q: %s", re2.String(), out)
   381  
   382  	// pull dangle2 tag
   383  	cli.DockerCmd(c, "pull", repoName+":dangle2")
   384  
   385  	// list images, show tagged images
   386  	out = cli.DockerCmd(c, "images", "--digests").Stdout()
   387  
   388  	// make sure image 1 has repo, tag, digest
   389  	assert.Assert(c, reWithDigest1.MatchString(out), "expected %q: %s", reWithDigest1.String(), out)
   390  
   391  	// make sure image 2 has repo, tag, digest
   392  	reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*dangle2\s*` + digest2.String() + `\s`)
   393  	assert.Assert(c, reWithDigest2.MatchString(out), "expected %q: %s", reWithDigest2.String(), out)
   394  
   395  	// list images, no longer dangling, should not match
   396  	out = cli.DockerCmd(c, "images", "--digests", "--filter=dangling=true").Stdout()
   397  
   398  	// make sure image 1 has repo, tag, digest
   399  	assert.Assert(c, !reWithDigest1.MatchString(out), "unexpected %q: %s", reWithDigest1.String(), out)
   400  	// make sure image 2 has repo, tag, digest
   401  	assert.Assert(c, !reWithDigest2.MatchString(out), "unexpected %q: %s", reWithDigest2.String(), out)
   402  }
   403  
   404  func (s *DockerRegistrySuite) TestInspectImageWithDigests(c *testing.T) {
   405  	imgDigest, err := setupImage(c)
   406  	assert.Assert(c, err == nil, "error setting up image")
   407  
   408  	imageReference := fmt.Sprintf("%s@%s", repoName, imgDigest)
   409  
   410  	// pull from the registry using the <name>@<digest> reference
   411  	cli.DockerCmd(c, "pull", imageReference)
   412  
   413  	out := cli.DockerCmd(c, "inspect", imageReference).Stdout()
   414  
   415  	var imageJSON []types.ImageInspect
   416  	err = json.Unmarshal([]byte(out), &imageJSON)
   417  	assert.NilError(c, err)
   418  	assert.Equal(c, len(imageJSON), 1)
   419  	assert.Equal(c, len(imageJSON[0].RepoDigests), 1)
   420  	assert.Check(c, is.Contains(imageJSON[0].RepoDigests, imageReference))
   421  }
   422  
   423  func (s *DockerRegistrySuite) TestPsListContainersFilterAncestorImageByDigest(c *testing.T) {
   424  	existingContainers := ExistingContainerIDs(c)
   425  
   426  	imgDigest, err := setupImage(c)
   427  	assert.NilError(c, err, "error setting up image")
   428  
   429  	imageReference := fmt.Sprintf("%s@%s", repoName, imgDigest)
   430  
   431  	// pull from the registry using the <name>@<digest> reference
   432  	cli.DockerCmd(c, "pull", imageReference)
   433  
   434  	// build an image from it
   435  	const imageName1 = "images_ps_filter_test"
   436  	buildImageSuccessfully(c, imageName1, build.WithDockerfile(fmt.Sprintf(
   437  		`FROM %s
   438  		 LABEL match me 1`, imageReference)))
   439  
   440  	// run a container based on that
   441  	cli.DockerCmd(c, "run", "--name=test1", imageReference, "echo", "hello")
   442  	expectedID := getIDByName(c, "test1")
   443  
   444  	// run a container based on the a descendant of that too
   445  	cli.DockerCmd(c, "run", "--name=test2", imageName1, "echo", "hello")
   446  	expectedID1 := getIDByName(c, "test2")
   447  
   448  	expectedIDs := []string{expectedID, expectedID1}
   449  
   450  	// Invalid imageReference
   451  	out := cli.DockerCmd(c, "ps", "-a", "-q", "--no-trunc", fmt.Sprintf("--filter=ancestor=busybox@%s", imgDigest)).Stdout()
   452  	assert.Equal(c, strings.TrimSpace(out), "", "Filter container for ancestor filter should be empty")
   453  
   454  	// Valid imageReference
   455  	out = cli.DockerCmd(c, "ps", "-a", "-q", "--no-trunc", "--filter=ancestor="+imageReference).Stdout()
   456  	checkPsAncestorFilterOutput(c, RemoveOutputForExistingElements(out, existingContainers), imageReference, expectedIDs)
   457  }
   458  
   459  func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *testing.T) {
   460  	pushDigest, err := setupImage(c)
   461  	assert.NilError(c, err, "error setting up image")
   462  
   463  	// pull from the registry using the <name>@<digest> reference
   464  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   465  	cli.DockerCmd(c, "pull", imageReference)
   466  	// just in case...
   467  
   468  	cli.DockerCmd(c, "tag", imageReference, repoName+":sometag")
   469  
   470  	imageID := inspectField(c, imageReference, "Id")
   471  
   472  	cli.DockerCmd(c, "rmi", imageID)
   473  
   474  	_, err = inspectFieldWithError(imageID, "Id")
   475  	assert.ErrorContains(c, err, "", "image should have been deleted")
   476  }
   477  
   478  func (s *DockerRegistrySuite) TestDeleteImageWithDigestAndTag(c *testing.T) {
   479  	pushDigest, err := setupImage(c)
   480  	assert.NilError(c, err, "error setting up image")
   481  
   482  	// pull from the registry using the <name>@<digest> reference
   483  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   484  	cli.DockerCmd(c, "pull", imageReference)
   485  
   486  	imageID := inspectField(c, imageReference, "Id")
   487  
   488  	const repoTag = repoName + ":sometag"
   489  	const repoTag2 = repoName + ":othertag"
   490  	cli.DockerCmd(c, "tag", imageReference, repoTag)
   491  	cli.DockerCmd(c, "tag", imageReference, repoTag2)
   492  
   493  	cli.DockerCmd(c, "rmi", repoTag2)
   494  
   495  	// rmi should have deleted only repoTag2, because there's another tag
   496  	inspectField(c, repoTag, "Id")
   497  
   498  	cli.DockerCmd(c, "rmi", repoTag)
   499  
   500  	// rmi should have deleted the tag, the digest reference, and the image itself
   501  	_, err = inspectFieldWithError(imageID, "Id")
   502  	assert.ErrorContains(c, err, "", "image should have been deleted")
   503  }
   504  
   505  func (s *DockerRegistrySuite) TestDeleteImageWithDigestAndMultiRepoTag(c *testing.T) {
   506  	pushDigest, err := setupImage(c)
   507  	assert.NilError(c, err, "error setting up image")
   508  
   509  	repo2 := fmt.Sprintf("%s/%s", repoName, "repo2")
   510  
   511  	// pull from the registry using the <name>@<digest> reference
   512  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   513  	cli.DockerCmd(c, "pull", imageReference)
   514  
   515  	imageID := inspectField(c, imageReference, "Id")
   516  
   517  	repoTag := repoName + ":sometag"
   518  	repoTag2 := repo2 + ":othertag"
   519  	cli.DockerCmd(c, "tag", imageReference, repoTag)
   520  	cli.DockerCmd(c, "tag", imageReference, repoTag2)
   521  
   522  	cli.DockerCmd(c, "rmi", repoTag)
   523  
   524  	// rmi should have deleted repoTag and image reference, but left repoTag2
   525  	inspectField(c, repoTag2, "Id")
   526  	_, err = inspectFieldWithError(imageReference, "Id")
   527  	assert.ErrorContains(c, err, "", "image digest reference should have been removed")
   528  
   529  	_, err = inspectFieldWithError(repoTag, "Id")
   530  	assert.ErrorContains(c, err, "", "image tag reference should have been removed")
   531  
   532  	cli.DockerCmd(c, "rmi", repoTag2)
   533  
   534  	// rmi should have deleted the tag, the digest reference, and the image itself
   535  	_, err = inspectFieldWithError(imageID, "Id")
   536  	assert.ErrorContains(c, err, "", "image should have been deleted")
   537  }
   538  
   539  // TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
   540  // we have modified a manifest blob and its digest cannot be verified.
   541  // This is the schema2 version of the test.
   542  func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *testing.T) {
   543  	testRequires(c, DaemonIsLinux)
   544  	manifestDigest, err := setupImage(c)
   545  	assert.NilError(c, err, "error setting up image")
   546  
   547  	// Load the target manifest blob.
   548  	manifestBlob := s.reg.ReadBlobContents(c, manifestDigest)
   549  
   550  	var imgManifest schema2.Manifest
   551  	err = json.Unmarshal(manifestBlob, &imgManifest)
   552  	assert.NilError(c, err, "unable to decode image manifest from blob")
   553  
   554  	// Change a layer in the manifest.
   555  	imgManifest.Layers[0].Digest = digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
   556  
   557  	// Move the existing data file aside, so that we can replace it with a
   558  	// malicious blob of data. NOTE: we defer the returned undo func.
   559  	undo := s.reg.TempMoveBlobData(c, manifestDigest)
   560  	defer undo()
   561  
   562  	alteredManifestBlob, err := json.MarshalIndent(imgManifest, "", "   ")
   563  	assert.NilError(c, err, "unable to encode altered image manifest to JSON")
   564  
   565  	s.reg.WriteBlobContents(c, manifestDigest, alteredManifestBlob)
   566  
   567  	// Now try pulling that image by digest. We should get an error about
   568  	// digest verification for the manifest digest.
   569  
   570  	// Pull from the registry using the <name>@<digest> reference.
   571  	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
   572  	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
   573  	assert.Assert(c, exitStatus != 0)
   574  
   575  	if testEnv.UsingSnapshotter() {
   576  		assert.Assert(c, is.Contains(out, "unexpected commit digest"))
   577  		assert.Assert(c, is.Contains(out, "expected "+manifestDigest))
   578  	} else {
   579  		assert.Assert(c, is.Contains(out, fmt.Sprintf("manifest verification failed for digest %s", manifestDigest)))
   580  	}
   581  }
   582  
   583  // TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
   584  // we have modified a manifest blob and its digest cannot be verified.
   585  // This is the schema1 version of the test.
   586  func (s *DockerSchema1RegistrySuite) TestPullFailsWithAlteredManifest(c *testing.T) {
   587  	testRequires(c, DaemonIsLinux)
   588  	manifestDigest, err := setupImage(c)
   589  	assert.Assert(c, err == nil, "error setting up image")
   590  
   591  	// Load the target manifest blob.
   592  	manifestBlob := s.reg.ReadBlobContents(c, manifestDigest)
   593  
   594  	var imgManifest schema1.Manifest
   595  	err = json.Unmarshal(manifestBlob, &imgManifest)
   596  	assert.Assert(c, err == nil, "unable to decode image manifest from blob")
   597  
   598  	// Change a layer in the manifest.
   599  	imgManifest.FSLayers[0] = schema1.FSLayer{
   600  		BlobSum: digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
   601  	}
   602  
   603  	// Move the existing data file aside, so that we can replace it with a
   604  	// malicious blob of data. NOTE: we defer the returned undo func.
   605  	undo := s.reg.TempMoveBlobData(c, manifestDigest)
   606  	defer undo()
   607  
   608  	alteredManifestBlob, err := json.MarshalIndent(imgManifest, "", "   ")
   609  	assert.Assert(c, err == nil, "unable to encode altered image manifest to JSON")
   610  
   611  	s.reg.WriteBlobContents(c, manifestDigest, alteredManifestBlob)
   612  
   613  	// Now try pulling that image by digest. We should get an error about
   614  	// digest verification for the manifest digest.
   615  
   616  	// Pull from the registry using the <name>@<digest> reference.
   617  	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
   618  	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
   619  	assert.Assert(c, exitStatus != 0)
   620  
   621  	expectedErrorMsg := fmt.Sprintf("image verification failed for digest %s", manifestDigest)
   622  	assert.Assert(c, strings.Contains(out, expectedErrorMsg))
   623  }
   624  
   625  // TestPullFailsWithAlteredLayer tests that a `docker pull` fails when
   626  // we have modified a layer blob and its digest cannot be verified.
   627  // This is the schema2 version of the test.
   628  func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *testing.T) {
   629  	testRequires(c, DaemonIsLinux)
   630  	skip.If(c, testEnv.UsingSnapshotter(), "Faked layer is already in the content store, so it won't be fetched from the repository at all.")
   631  
   632  	manifestDigest, err := setupImage(c)
   633  	assert.Assert(c, err == nil)
   634  
   635  	// Load the target manifest blob.
   636  	manifestBlob := s.reg.ReadBlobContents(c, manifestDigest)
   637  
   638  	var imgManifest schema2.Manifest
   639  	err = json.Unmarshal(manifestBlob, &imgManifest)
   640  	assert.Assert(c, err == nil)
   641  
   642  	// Next, get the digest of one of the layers from the manifest.
   643  	targetLayerDigest := imgManifest.Layers[0].Digest
   644  
   645  	// Move the existing data file aside, so that we can replace it with a
   646  	// malicious blob of data. NOTE: we defer the returned undo func.
   647  	undo := s.reg.TempMoveBlobData(c, targetLayerDigest)
   648  	defer undo()
   649  
   650  	// Now make a fake data blob in this directory.
   651  	s.reg.WriteBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for."))
   652  
   653  	// Now try pulling that image by digest. We should get an error about
   654  	// digest verification for the target layer digest.
   655  
   656  	// Remove distribution cache to force a re-pull of the blobs
   657  	if err := os.RemoveAll(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "image", s.d.StorageDriver(), "distribution")); err != nil {
   658  		c.Fatalf("error clearing distribution cache: %v", err)
   659  	}
   660  
   661  	// Pull from the registry using the <name>@<digest> reference.
   662  	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
   663  	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
   664  	assert.Assert(c, exitStatus != 0, "expected a non-zero exit status")
   665  
   666  	expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest)
   667  	assert.Assert(c, strings.Contains(out, expectedErrorMsg), "expected error message in output: %s", out)
   668  }
   669  
   670  // TestPullFailsWithAlteredLayer tests that a `docker pull` fails when
   671  // we have modified a layer blob and its digest cannot be verified.
   672  // This is the schema1 version of the test.
   673  func (s *DockerSchema1RegistrySuite) TestPullFailsWithAlteredLayer(c *testing.T) {
   674  	testRequires(c, DaemonIsLinux)
   675  	manifestDigest, err := setupImage(c)
   676  	assert.Assert(c, err == nil)
   677  
   678  	// Load the target manifest blob.
   679  	manifestBlob := s.reg.ReadBlobContents(c, manifestDigest)
   680  
   681  	var imgManifest schema1.Manifest
   682  	err = json.Unmarshal(manifestBlob, &imgManifest)
   683  	assert.Assert(c, err == nil)
   684  
   685  	// Next, get the digest of one of the layers from the manifest.
   686  	targetLayerDigest := imgManifest.FSLayers[0].BlobSum
   687  
   688  	// Move the existing data file aside, so that we can replace it with a
   689  	// malicious blob of data. NOTE: we defer the returned undo func.
   690  	undo := s.reg.TempMoveBlobData(c, targetLayerDigest)
   691  	defer undo()
   692  
   693  	// Now make a fake data blob in this directory.
   694  	s.reg.WriteBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for."))
   695  
   696  	// Now try pulling that image by digest. We should get an error about
   697  	// digest verification for the target layer digest.
   698  
   699  	// Remove distribution cache to force a re-pull of the blobs
   700  	if err := os.RemoveAll(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "image", s.d.StorageDriver(), "distribution")); err != nil {
   701  		c.Fatalf("error clearing distribution cache: %v", err)
   702  	}
   703  
   704  	// Pull from the registry using the <name>@<digest> reference.
   705  	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
   706  	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
   707  	assert.Assert(c, exitStatus != 0, "expected a non-zero exit status")
   708  
   709  	expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest)
   710  	assert.Assert(c, strings.Contains(out, expectedErrorMsg), "expected error message in output: %s", out)
   711  }