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 }