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