github.com/goern/docker@v1.9.0-rc1/integration-cli/docker_cli_by_digest_test.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/docker/distribution/digest"
    10  	"github.com/docker/distribution/manifest"
    11  	"github.com/docker/docker/utils"
    12  	"github.com/go-check/check"
    13  )
    14  
    15  var (
    16  	remoteRepoName  = "dockercli/busybox-by-dgst"
    17  	repoName        = fmt.Sprintf("%v/%s", privateRegistryURL, remoteRepoName)
    18  	pushDigestRegex = regexp.MustCompile("[\\S]+: digest: ([\\S]+) size: [0-9]+")
    19  	digestRegex     = regexp.MustCompile("Digest: ([\\S]+)")
    20  )
    21  
    22  func setupImage(c *check.C) (digest.Digest, error) {
    23  	return setupImageWithTag(c, "latest")
    24  }
    25  
    26  func setupImageWithTag(c *check.C, tag string) (digest.Digest, error) {
    27  	containerName := "busyboxbydigest"
    28  
    29  	dockerCmd(c, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox")
    30  
    31  	// tag the image to upload it to the private registry
    32  	repoAndTag := utils.ImageReference(repoName, tag)
    33  	if out, _, err := dockerCmdWithError("commit", containerName, repoAndTag); err != nil {
    34  		return "", fmt.Errorf("image tagging failed: %s, %v", out, err)
    35  	}
    36  
    37  	// delete the container as we don't need it any more
    38  	if err := deleteContainer(containerName); err != nil {
    39  		return "", err
    40  	}
    41  
    42  	// push the image
    43  	out, _, err := dockerCmdWithError("push", repoAndTag)
    44  	if err != nil {
    45  		return "", fmt.Errorf("pushing the image to the private registry has failed: %s, %v", out, err)
    46  	}
    47  
    48  	// delete our local repo that we previously tagged
    49  	if rmiout, _, err := dockerCmdWithError("rmi", repoAndTag); err != nil {
    50  		return "", fmt.Errorf("error deleting images prior to real test: %s, %v", rmiout, err)
    51  	}
    52  
    53  	matches := pushDigestRegex.FindStringSubmatch(out)
    54  	if len(matches) != 2 {
    55  		return "", fmt.Errorf("unable to parse digest from push output: %s", out)
    56  	}
    57  	pushDigest := matches[1]
    58  
    59  	return digest.Digest(pushDigest), nil
    60  }
    61  
    62  func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
    63  	testRequires(c, DaemonIsLinux)
    64  	pushDigest, err := setupImage(c)
    65  	if err != nil {
    66  		c.Fatalf("error setting up image: %v", err)
    67  	}
    68  
    69  	// pull from the registry using the tag
    70  	out, _ := dockerCmd(c, "pull", repoName)
    71  
    72  	// the pull output includes "Digest: <digest>", so find that
    73  	matches := digestRegex.FindStringSubmatch(out)
    74  	if len(matches) != 2 {
    75  		c.Fatalf("unable to parse digest from pull output: %s", out)
    76  	}
    77  	pullDigest := matches[1]
    78  
    79  	// make sure the pushed and pull digests match
    80  	if pushDigest.String() != pullDigest {
    81  		c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
    82  	}
    83  }
    84  
    85  func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) {
    86  	testRequires(c, DaemonIsLinux)
    87  	pushDigest, err := setupImage(c)
    88  	if err != nil {
    89  		c.Fatalf("error setting up image: %v", err)
    90  	}
    91  
    92  	// pull from the registry using the <name>@<digest> reference
    93  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
    94  	out, _ := dockerCmd(c, "pull", imageReference)
    95  
    96  	// the pull output includes "Digest: <digest>", so find that
    97  	matches := digestRegex.FindStringSubmatch(out)
    98  	if len(matches) != 2 {
    99  		c.Fatalf("unable to parse digest from pull output: %s", out)
   100  	}
   101  	pullDigest := matches[1]
   102  
   103  	// make sure the pushed and pull digests match
   104  	if pushDigest.String() != pullDigest {
   105  		c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
   106  	}
   107  }
   108  
   109  func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) {
   110  	testRequires(c, DaemonIsLinux)
   111  	// pull from the registry using the <name>@<digest> reference
   112  	imageReference := fmt.Sprintf("%s@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", repoName)
   113  	out, _, err := dockerCmdWithError("pull", imageReference)
   114  	if err == nil || !strings.Contains(out, "manifest unknown") {
   115  		c.Fatalf("expected non-zero exit status and correct error message when pulling non-existing image: %s", out)
   116  	}
   117  }
   118  
   119  func (s *DockerRegistrySuite) TestCreateByDigest(c *check.C) {
   120  	pushDigest, err := setupImage(c)
   121  	if err != nil {
   122  		c.Fatalf("error setting up image: %v", err)
   123  	}
   124  
   125  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   126  
   127  	containerName := "createByDigest"
   128  	out, _ := dockerCmd(c, "create", "--name", containerName, imageReference)
   129  
   130  	res, err := inspectField(containerName, "Config.Image")
   131  	if err != nil {
   132  		c.Fatalf("failed to get Config.Image: %s, %v", out, err)
   133  	}
   134  	if res != imageReference {
   135  		c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
   136  	}
   137  }
   138  
   139  func (s *DockerRegistrySuite) TestRunByDigest(c *check.C) {
   140  	pushDigest, err := setupImage(c)
   141  	if err != nil {
   142  		c.Fatalf("error setting up image: %v", err)
   143  	}
   144  
   145  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   146  
   147  	containerName := "runByDigest"
   148  	out, _ := dockerCmd(c, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest")
   149  
   150  	foundRegex := regexp.MustCompile("found=([^\n]+)")
   151  	matches := foundRegex.FindStringSubmatch(out)
   152  	if len(matches) != 2 {
   153  		c.Fatalf("error locating expected 'found=1' output: %s", out)
   154  	}
   155  	if matches[1] != "1" {
   156  		c.Fatalf("Expected %q, got %q", "1", matches[1])
   157  	}
   158  
   159  	res, err := inspectField(containerName, "Config.Image")
   160  	if err != nil {
   161  		c.Fatalf("failed to get Config.Image: %s, %v", out, err)
   162  	}
   163  	if res != imageReference {
   164  		c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
   165  	}
   166  }
   167  
   168  func (s *DockerRegistrySuite) TestRemoveImageByDigest(c *check.C) {
   169  	digest, err := setupImage(c)
   170  	if err != nil {
   171  		c.Fatalf("error setting up image: %v", err)
   172  	}
   173  
   174  	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
   175  
   176  	// pull from the registry using the <name>@<digest> reference
   177  	dockerCmd(c, "pull", imageReference)
   178  
   179  	// make sure inspect runs ok
   180  	if _, err := inspectField(imageReference, "Id"); err != nil {
   181  		c.Fatalf("failed to inspect image: %v", err)
   182  	}
   183  
   184  	// do the delete
   185  	if err := deleteImages(imageReference); err != nil {
   186  		c.Fatalf("unexpected error deleting image: %v", err)
   187  	}
   188  
   189  	// try to inspect again - it should error this time
   190  	if _, err := inspectField(imageReference, "Id"); err == nil {
   191  		c.Fatalf("unexpected nil err trying to inspect what should be a non-existent image")
   192  	} else if !strings.Contains(err.Error(), "No such image") {
   193  		c.Fatalf("expected 'No such image' output, got %v", err)
   194  	}
   195  }
   196  
   197  func (s *DockerRegistrySuite) TestBuildByDigest(c *check.C) {
   198  	digest, err := setupImage(c)
   199  	if err != nil {
   200  		c.Fatalf("error setting up image: %v", err)
   201  	}
   202  
   203  	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
   204  
   205  	// pull from the registry using the <name>@<digest> reference
   206  	dockerCmd(c, "pull", imageReference)
   207  
   208  	// get the image id
   209  	imageID, err := inspectField(imageReference, "Id")
   210  	if err != nil {
   211  		c.Fatalf("error getting image id: %v", err)
   212  	}
   213  
   214  	// do the build
   215  	name := "buildbydigest"
   216  	_, err = buildImage(name, fmt.Sprintf(
   217  		`FROM %s
   218       CMD ["/bin/echo", "Hello World"]`, imageReference),
   219  		true)
   220  	if err != nil {
   221  		c.Fatal(err)
   222  	}
   223  
   224  	// get the build's image id
   225  	res, err := inspectField(name, "Config.Image")
   226  	if err != nil {
   227  		c.Fatal(err)
   228  	}
   229  	// make sure they match
   230  	if res != imageID {
   231  		c.Fatalf("Image %s, expected %s", res, imageID)
   232  	}
   233  }
   234  
   235  func (s *DockerRegistrySuite) TestTagByDigest(c *check.C) {
   236  	digest, err := setupImage(c)
   237  	if err != nil {
   238  		c.Fatalf("error setting up image: %v", err)
   239  	}
   240  
   241  	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
   242  
   243  	// pull from the registry using the <name>@<digest> reference
   244  	dockerCmd(c, "pull", imageReference)
   245  
   246  	// tag it
   247  	tag := "tagbydigest"
   248  	dockerCmd(c, "tag", imageReference, tag)
   249  
   250  	expectedID, err := inspectField(imageReference, "Id")
   251  	if err != nil {
   252  		c.Fatalf("error getting original image id: %v", err)
   253  	}
   254  
   255  	tagID, err := inspectField(tag, "Id")
   256  	if err != nil {
   257  		c.Fatalf("error getting tagged image id: %v", err)
   258  	}
   259  
   260  	if tagID != expectedID {
   261  		c.Fatalf("expected image id %q, got %q", expectedID, tagID)
   262  	}
   263  }
   264  
   265  func (s *DockerRegistrySuite) TestListImagesWithoutDigests(c *check.C) {
   266  	digest, err := setupImage(c)
   267  	if err != nil {
   268  		c.Fatalf("error setting up image: %v", err)
   269  	}
   270  
   271  	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
   272  
   273  	// pull from the registry using the <name>@<digest> reference
   274  	dockerCmd(c, "pull", imageReference)
   275  
   276  	out, _ := dockerCmd(c, "images")
   277  
   278  	if strings.Contains(out, "DIGEST") {
   279  		c.Fatalf("list output should not have contained DIGEST header: %s", out)
   280  	}
   281  
   282  }
   283  
   284  func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
   285  
   286  	// setup image1
   287  	digest1, err := setupImageWithTag(c, "tag1")
   288  	if err != nil {
   289  		c.Fatalf("error setting up image: %v", err)
   290  	}
   291  	imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1)
   292  	c.Logf("imageReference1 = %s", imageReference1)
   293  
   294  	// pull image1 by digest
   295  	dockerCmd(c, "pull", imageReference1)
   296  
   297  	// list images
   298  	out, _ := dockerCmd(c, "images", "--digests")
   299  
   300  	// make sure repo shown, tag=<none>, digest = $digest1
   301  	re1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
   302  	if !re1.MatchString(out) {
   303  		c.Fatalf("expected %q: %s", re1.String(), out)
   304  	}
   305  
   306  	// setup image2
   307  	digest2, err := setupImageWithTag(c, "tag2")
   308  	if err != nil {
   309  		c.Fatalf("error setting up image: %v", err)
   310  	}
   311  	imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2)
   312  	c.Logf("imageReference2 = %s", imageReference2)
   313  
   314  	// pull image1 by digest
   315  	dockerCmd(c, "pull", imageReference1)
   316  
   317  	// pull image2 by digest
   318  	dockerCmd(c, "pull", imageReference2)
   319  
   320  	// list images
   321  	out, _ = dockerCmd(c, "images", "--digests")
   322  
   323  	// make sure repo shown, tag=<none>, digest = $digest1
   324  	if !re1.MatchString(out) {
   325  		c.Fatalf("expected %q: %s", re1.String(), out)
   326  	}
   327  
   328  	// make sure repo shown, tag=<none>, digest = $digest2
   329  	re2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
   330  	if !re2.MatchString(out) {
   331  		c.Fatalf("expected %q: %s", re2.String(), out)
   332  	}
   333  
   334  	// pull tag1
   335  	dockerCmd(c, "pull", repoName+":tag1")
   336  
   337  	// list images
   338  	out, _ = dockerCmd(c, "images", "--digests")
   339  
   340  	// make sure image 1 has repo, tag, <none> AND repo, <none>, digest
   341  	reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*<none>\s`)
   342  	reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
   343  	if !reWithTag1.MatchString(out) {
   344  		c.Fatalf("expected %q: %s", reWithTag1.String(), out)
   345  	}
   346  	if !reWithDigest1.MatchString(out) {
   347  		c.Fatalf("expected %q: %s", reWithDigest1.String(), out)
   348  	}
   349  	// make sure image 2 has repo, <none>, digest
   350  	if !re2.MatchString(out) {
   351  		c.Fatalf("expected %q: %s", re2.String(), out)
   352  	}
   353  
   354  	// pull tag 2
   355  	dockerCmd(c, "pull", repoName+":tag2")
   356  
   357  	// list images
   358  	out, _ = dockerCmd(c, "images", "--digests")
   359  
   360  	// make sure image 1 has repo, tag, digest
   361  	if !reWithTag1.MatchString(out) {
   362  		c.Fatalf("expected %q: %s", re1.String(), out)
   363  	}
   364  
   365  	// make sure image 2 has repo, tag, digest
   366  	reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*<none>\s`)
   367  	reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
   368  	if !reWithTag2.MatchString(out) {
   369  		c.Fatalf("expected %q: %s", reWithTag2.String(), out)
   370  	}
   371  	if !reWithDigest2.MatchString(out) {
   372  		c.Fatalf("expected %q: %s", reWithDigest2.String(), out)
   373  	}
   374  
   375  	// list images
   376  	out, _ = dockerCmd(c, "images", "--digests")
   377  
   378  	// make sure image 1 has repo, tag, digest
   379  	if !reWithTag1.MatchString(out) {
   380  		c.Fatalf("expected %q: %s", re1.String(), out)
   381  	}
   382  	// make sure image 2 has repo, tag, digest
   383  	if !reWithTag2.MatchString(out) {
   384  		c.Fatalf("expected %q: %s", re2.String(), out)
   385  	}
   386  	// make sure busybox has tag, but not digest
   387  	busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*<none>\s`)
   388  	if !busyboxRe.MatchString(out) {
   389  		c.Fatalf("expected %q: %s", busyboxRe.String(), out)
   390  	}
   391  }
   392  
   393  func (s *DockerRegistrySuite) TestPsListContainersFilterAncestorImageByDigest(c *check.C) {
   394  	digest, err := setupImage(c)
   395  	c.Assert(err, check.IsNil, check.Commentf("error setting up image: %v", err))
   396  
   397  	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
   398  
   399  	// pull from the registry using the <name>@<digest> reference
   400  	dockerCmd(c, "pull", imageReference)
   401  
   402  	// build a image from it
   403  	imageName1 := "images_ps_filter_test"
   404  	_, err = buildImage(imageName1, fmt.Sprintf(
   405  		`FROM %s
   406  		 LABEL match me 1`, imageReference), true)
   407  	c.Assert(err, check.IsNil)
   408  
   409  	// run a container based on that
   410  	out, _ := dockerCmd(c, "run", "-d", imageReference, "echo", "hello")
   411  	expectedID := strings.TrimSpace(out)
   412  
   413  	// run a container based on the a descendant of that too
   414  	out, _ = dockerCmd(c, "run", "-d", imageName1, "echo", "hello")
   415  	expectedID1 := strings.TrimSpace(out)
   416  
   417  	expectedIDs := []string{expectedID, expectedID1}
   418  
   419  	// Invalid imageReference
   420  	out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc", fmt.Sprintf("--filter=ancestor=busybox@%s", digest))
   421  	if strings.TrimSpace(out) != "" {
   422  		c.Fatalf("Expected filter container for %s ancestor filter to be empty, got %v", fmt.Sprintf("busybox@%s", digest), strings.TrimSpace(out))
   423  	}
   424  
   425  	// Valid imageReference
   426  	out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc", "--filter=ancestor="+imageReference)
   427  	checkPsAncestorFilterOutput(c, out, imageReference, expectedIDs)
   428  }
   429  
   430  func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) {
   431  	pushDigest, err := setupImage(c)
   432  	if err != nil {
   433  		c.Fatalf("error setting up image: %v", err)
   434  	}
   435  
   436  	// pull from the registry using the <name>@<digest> reference
   437  	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
   438  	dockerCmd(c, "pull", imageReference)
   439  	// just in case...
   440  
   441  	imageID, err := inspectField(imageReference, "Id")
   442  	if err != nil {
   443  		c.Fatalf("error inspecting image id: %v", err)
   444  	}
   445  
   446  	dockerCmd(c, "rmi", imageID)
   447  }
   448  
   449  // TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
   450  // we have modified a manifest blob and its digest cannot be verified.
   451  func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) {
   452  	testRequires(c, DaemonIsLinux)
   453  	manifestDigest, err := setupImage(c)
   454  	if err != nil {
   455  		c.Fatalf("error setting up image: %v", err)
   456  	}
   457  
   458  	// Load the target manifest blob.
   459  	manifestBlob := s.reg.readBlobContents(c, manifestDigest)
   460  
   461  	var imgManifest manifest.Manifest
   462  	if err := json.Unmarshal(manifestBlob, &imgManifest); err != nil {
   463  		c.Fatalf("unable to decode image manifest from blob: %s", err)
   464  	}
   465  
   466  	// Add a malicious layer digest to the list of layers in the manifest.
   467  	imgManifest.FSLayers = append(imgManifest.FSLayers, manifest.FSLayer{
   468  		BlobSum: digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
   469  	})
   470  
   471  	// Move the existing data file aside, so that we can replace it with a
   472  	// malicious blob of data. NOTE: we defer the returned undo func.
   473  	undo := s.reg.tempMoveBlobData(c, manifestDigest)
   474  	defer undo()
   475  
   476  	alteredManifestBlob, err := json.Marshal(imgManifest)
   477  	if err != nil {
   478  		c.Fatalf("unable to encode altered image manifest to JSON: %s", err)
   479  	}
   480  
   481  	s.reg.writeBlobContents(c, manifestDigest, alteredManifestBlob)
   482  
   483  	// Now try pulling that image by digest. We should get an error about
   484  	// digest verification for the manifest digest.
   485  
   486  	// Pull from the registry using the <name>@<digest> reference.
   487  	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
   488  	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
   489  	if exitStatus == 0 {
   490  		c.Fatalf("expected a non-zero exit status but got %d: %s", exitStatus, out)
   491  	}
   492  
   493  	expectedErrorMsg := fmt.Sprintf("image verification failed for digest %s", manifestDigest)
   494  	if !strings.Contains(out, expectedErrorMsg) {
   495  		c.Fatalf("expected error message %q in output: %s", expectedErrorMsg, out)
   496  	}
   497  }
   498  
   499  // TestPullFailsWithAlteredLayer tests that a `docker pull` fails when
   500  // we have modified a layer blob and its digest cannot be verified.
   501  func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) {
   502  	testRequires(c, DaemonIsLinux)
   503  	manifestDigest, err := setupImage(c)
   504  	if err != nil {
   505  		c.Fatalf("error setting up image: %v", err)
   506  	}
   507  
   508  	// Load the target manifest blob.
   509  	manifestBlob := s.reg.readBlobContents(c, manifestDigest)
   510  
   511  	var imgManifest manifest.Manifest
   512  	if err := json.Unmarshal(manifestBlob, &imgManifest); err != nil {
   513  		c.Fatalf("unable to decode image manifest from blob: %s", err)
   514  	}
   515  
   516  	// Next, get the digest of one of the layers from the manifest.
   517  	targetLayerDigest := imgManifest.FSLayers[0].BlobSum
   518  
   519  	// Move the existing data file aside, so that we can replace it with a
   520  	// malicious blob of data. NOTE: we defer the returned undo func.
   521  	undo := s.reg.tempMoveBlobData(c, targetLayerDigest)
   522  	defer undo()
   523  
   524  	// Now make a fake data blob in this directory.
   525  	s.reg.writeBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for."))
   526  
   527  	// Now try pulling that image by digest. We should get an error about
   528  	// digest verification for the target layer digest.
   529  
   530  	// Pull from the registry using the <name>@<digest> reference.
   531  	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
   532  	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
   533  	if exitStatus == 0 {
   534  		c.Fatalf("expected a zero exit status but got: %d", exitStatus)
   535  	}
   536  
   537  	expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest)
   538  	if !strings.Contains(out, expectedErrorMsg) {
   539  		c.Fatalf("expected error message %q in output: %s", expectedErrorMsg, out)
   540  	}
   541  }