github.com/goern/docker@v1.9.0-rc1/integration-cli/docker_cli_pull_test.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 "time" 12 13 "github.com/docker/distribution/digest" 14 "github.com/docker/docker/pkg/integration/checker" 15 "github.com/go-check/check" 16 ) 17 18 // TestPullFromCentralRegistry pulls an image from the central registry and verifies that the client 19 // prints all expected output. 20 func (s *DockerHubPullSuite) TestPullFromCentralRegistry(c *check.C) { 21 testRequires(c, DaemonIsLinux) 22 out := s.Cmd(c, "pull", "hello-world") 23 defer deleteImages("hello-world") 24 25 c.Assert(out, checker.Contains, "Using default tag: latest", check.Commentf("expected the 'latest' tag to be automatically assumed")) 26 c.Assert(out, checker.Contains, "Pulling from library/hello-world", check.Commentf("expected the 'library/' prefix to be automatically assumed")) 27 c.Assert(out, checker.Contains, "Downloaded newer image for hello-world:latest") 28 29 matches := regexp.MustCompile(`Digest: (.+)\n`).FindAllStringSubmatch(out, -1) 30 c.Assert(len(matches), checker.Equals, 1, check.Commentf("expected exactly one image digest in the output")) 31 c.Assert(len(matches[0]), checker.Equals, 2, check.Commentf("unexpected number of submatches for the digest")) 32 _, err := digest.ParseDigest(matches[0][1]) 33 c.Check(err, checker.IsNil, check.Commentf("invalid digest %q in output", matches[0][1])) 34 35 // We should have a single entry in images. 36 img := strings.TrimSpace(s.Cmd(c, "images")) 37 if splitImg := strings.Split(img, "\n"); len(splitImg) != 2 { 38 c.Fatalf("expected only two lines in the output of `docker images`, got %d", len(splitImg)) 39 } else if re := regexp.MustCompile(`^hello-world\s+latest`); !re.Match([]byte(splitImg[1])) { 40 c.Fatal("invalid output for `docker images` (expected image and tag name") 41 } 42 } 43 44 // TestPullNonExistingImage pulls non-existing images from the central registry, with different 45 // combinations of implicit tag and library prefix. 46 func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) { 47 testRequires(c, DaemonIsLinux) 48 for _, e := range []struct { 49 Image string 50 Alias string 51 }{ 52 {"library/asdfasdf:foobar", "asdfasdf:foobar"}, 53 {"library/asdfasdf:foobar", "library/asdfasdf:foobar"}, 54 {"library/asdfasdf:latest", "asdfasdf"}, 55 {"library/asdfasdf:latest", "asdfasdf:latest"}, 56 {"library/asdfasdf:latest", "library/asdfasdf"}, 57 {"library/asdfasdf:latest", "library/asdfasdf:latest"}, 58 } { 59 out, err := s.CmdWithError("pull", e.Alias) 60 c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out)) 61 c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Image), check.Commentf("expected image not found error messages")) 62 } 63 } 64 65 // TestPullFromCentralRegistryImplicitRefParts pulls an image from the central registry and verifies 66 // that pulling the same image with different combinations of implicit elements of the the image 67 // reference (tag, repository, central registry url, ...) doesn't trigger a new pull nor leads to 68 // multiple images. 69 func (s *DockerHubPullSuite) TestPullFromCentralRegistryImplicitRefParts(c *check.C) { 70 testRequires(c, DaemonIsLinux) 71 s.Cmd(c, "pull", "hello-world") 72 defer deleteImages("hello-world") 73 74 for _, i := range []string{ 75 "hello-world", 76 "hello-world:latest", 77 "library/hello-world", 78 "library/hello-world:latest", 79 "docker.io/library/hello-world", 80 "index.docker.io/library/hello-world", 81 } { 82 out := s.Cmd(c, "pull", i) 83 c.Assert(out, checker.Contains, "Image is up to date for hello-world:latest") 84 } 85 86 // We should have a single entry in images. 87 img := strings.TrimSpace(s.Cmd(c, "images")) 88 if splitImg := strings.Split(img, "\n"); len(splitImg) != 2 { 89 c.Fatalf("expected only two lines in the output of `docker images`, got %d", len(splitImg)) 90 } else if re := regexp.MustCompile(`^hello-world\s+latest`); !re.Match([]byte(splitImg[1])) { 91 c.Fatal("invalid output for `docker images` (expected image and tag name") 92 } 93 } 94 95 // TestPullScratchNotAllowed verifies that pulling 'scratch' is rejected. 96 func (s *DockerHubPullSuite) TestPullScratchNotAllowed(c *check.C) { 97 testRequires(c, DaemonIsLinux) 98 out, err := s.CmdWithError("pull", "scratch") 99 c.Assert(err, checker.NotNil, check.Commentf("expected pull of scratch to fail")) 100 c.Assert(out, checker.Contains, "'scratch' is a reserved name") 101 c.Assert(out, checker.Not(checker.Contains), "Pulling repository scratch") 102 } 103 104 // TestPullAllTagsFromCentralRegistry pulls using `all-tags` for a given image and verifies that it 105 // results in more images than a naked pull. 106 func (s *DockerHubPullSuite) TestPullAllTagsFromCentralRegistry(c *check.C) { 107 testRequires(c, DaemonIsLinux) 108 s.Cmd(c, "pull", "busybox") 109 outImageCmd := s.Cmd(c, "images", "busybox") 110 splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n") 111 c.Assert(splitOutImageCmd, checker.HasLen, 2, check.Commentf("expected a single entry in images\n%v", outImageCmd)) 112 113 s.Cmd(c, "pull", "--all-tags=true", "busybox") 114 outImageAllTagCmd := s.Cmd(c, "images", "busybox") 115 if linesCount := strings.Count(outImageAllTagCmd, "\n"); linesCount <= 2 { 116 c.Fatalf("pulling all tags should provide more images, got %d", linesCount-1) 117 } 118 119 // Verify that the line for 'busybox:latest' is left unchanged. 120 var latestLine string 121 for _, line := range strings.Split(outImageAllTagCmd, "\n") { 122 if strings.HasPrefix(line, "busybox") && strings.Contains(line, "latest") { 123 latestLine = line 124 break 125 } 126 } 127 c.Assert(latestLine, checker.Not(checker.Equals), "", check.Commentf("no entry for busybox:latest found after pulling all tags")) 128 splitLatest := strings.Fields(latestLine) 129 splitCurrent := strings.Fields(splitOutImageCmd[1]) 130 c.Assert(splitLatest, checker.DeepEquals, splitCurrent, check.Commentf("busybox:latest was changed after pulling all tags")) 131 } 132 133 // TestPullClientDisconnect kills the client during a pull operation and verifies that the operation 134 // still succesfully completes on the daemon side. 135 // 136 // Ref: docker/docker#15589 137 func (s *DockerHubPullSuite) TestPullClientDisconnect(c *check.C) { 138 testRequires(c, DaemonIsLinux) 139 repoName := "hello-world:latest" 140 141 pullCmd := s.MakeCmd("pull", repoName) 142 stdout, err := pullCmd.StdoutPipe() 143 c.Assert(err, checker.IsNil) 144 err = pullCmd.Start() 145 c.Assert(err, checker.IsNil) 146 147 // Cancel as soon as we get some output. 148 buf := make([]byte, 10) 149 _, err = stdout.Read(buf) 150 c.Assert(err, checker.IsNil) 151 152 err = pullCmd.Process.Kill() 153 c.Assert(err, checker.IsNil) 154 155 maxAttempts := 20 156 for i := 0; ; i++ { 157 if _, err := s.CmdWithError("inspect", repoName); err == nil { 158 break 159 } 160 if i >= maxAttempts { 161 c.Fatal("timeout reached: image was not pulled after client disconnected") 162 } 163 time.Sleep(500 * time.Millisecond) 164 } 165 } 166 167 type idAndParent struct { 168 ID string 169 Parent string 170 } 171 172 func inspectImage(c *check.C, imageRef string) idAndParent { 173 out, _ := dockerCmd(c, "inspect", imageRef) 174 var inspectOutput []idAndParent 175 err := json.Unmarshal([]byte(out), &inspectOutput) 176 if err != nil { 177 c.Fatal(err) 178 } 179 180 return inspectOutput[0] 181 } 182 183 func imageID(c *check.C, imageRef string) string { 184 return inspectImage(c, imageRef).ID 185 } 186 187 func imageParent(c *check.C, imageRef string) string { 188 return inspectImage(c, imageRef).Parent 189 } 190 191 // TestPullMigration verifies that pulling an image based on layers 192 // that already exists locally will reuse those existing layers. 193 func (s *DockerRegistrySuite) TestPullMigration(c *check.C) { 194 repoName := privateRegistryURL + "/dockercli/migration" 195 196 baseImage := repoName + ":base" 197 _, err := buildImage(baseImage, fmt.Sprintf(` 198 FROM scratch 199 ENV IMAGE base 200 CMD echo %s 201 `, baseImage), true) 202 if err != nil { 203 c.Fatal(err) 204 } 205 206 baseIDBeforePush := imageID(c, baseImage) 207 baseParentBeforePush := imageParent(c, baseImage) 208 209 derivedImage := repoName + ":derived" 210 _, err = buildImage(derivedImage, fmt.Sprintf(` 211 FROM %s 212 CMD echo %s 213 `, baseImage, derivedImage), true) 214 if err != nil { 215 c.Fatal(err) 216 } 217 218 derivedIDBeforePush := imageID(c, derivedImage) 219 220 dockerCmd(c, "push", derivedImage) 221 222 // Remove derived image from the local store 223 dockerCmd(c, "rmi", derivedImage) 224 225 // Repull 226 dockerCmd(c, "pull", derivedImage) 227 228 // Check that the parent of this pulled image is the original base 229 // image 230 derivedIDAfterPull1 := imageID(c, derivedImage) 231 derivedParentAfterPull1 := imageParent(c, derivedImage) 232 233 if derivedIDAfterPull1 == derivedIDBeforePush { 234 c.Fatal("image's ID should have changed on after deleting and pulling") 235 } 236 237 if derivedParentAfterPull1 != baseIDBeforePush { 238 c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull1, baseIDBeforePush) 239 } 240 241 // Confirm that repushing and repulling does not change the computed ID 242 dockerCmd(c, "push", derivedImage) 243 dockerCmd(c, "rmi", derivedImage) 244 dockerCmd(c, "pull", derivedImage) 245 246 derivedIDAfterPull2 := imageID(c, derivedImage) 247 derivedParentAfterPull2 := imageParent(c, derivedImage) 248 249 if derivedIDAfterPull2 != derivedIDAfterPull1 { 250 c.Fatal("image's ID unexpectedly changed after a repush/repull") 251 } 252 253 if derivedParentAfterPull2 != baseIDBeforePush { 254 c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull2, baseIDBeforePush) 255 } 256 257 // Remove everything, repull, and make sure everything uses computed IDs 258 dockerCmd(c, "rmi", baseImage, derivedImage) 259 dockerCmd(c, "pull", derivedImage) 260 261 derivedIDAfterPull3 := imageID(c, derivedImage) 262 derivedParentAfterPull3 := imageParent(c, derivedImage) 263 derivedGrandparentAfterPull3 := imageParent(c, derivedParentAfterPull3) 264 265 if derivedIDAfterPull3 != derivedIDAfterPull1 { 266 c.Fatal("image's ID unexpectedly changed after a second repull") 267 } 268 269 if derivedParentAfterPull3 == baseIDBeforePush { 270 c.Fatalf("pulled image's parent ID (%s) should not match base image's original ID (%s)", derivedParentAfterPull3, derivedIDBeforePush) 271 } 272 273 if derivedGrandparentAfterPull3 == baseParentBeforePush { 274 c.Fatal("base image's parent ID should have been rewritten on pull") 275 } 276 } 277 278 // TestPullMigrationRun verifies that pulling an image based on layers 279 // that already exists locally will result in an image that runs properly. 280 func (s *DockerRegistrySuite) TestPullMigrationRun(c *check.C) { 281 type idAndParent struct { 282 ID string 283 Parent string 284 } 285 286 derivedImage := privateRegistryURL + "/dockercli/migration-run" 287 baseImage := "busybox" 288 289 _, err := buildImage(derivedImage, fmt.Sprintf(` 290 FROM %s 291 RUN dd if=/dev/zero of=/file bs=1024 count=1024 292 CMD echo %s 293 `, baseImage, derivedImage), true) 294 if err != nil { 295 c.Fatal(err) 296 } 297 298 baseIDBeforePush := imageID(c, baseImage) 299 derivedIDBeforePush := imageID(c, derivedImage) 300 301 dockerCmd(c, "push", derivedImage) 302 303 // Remove derived image from the local store 304 dockerCmd(c, "rmi", derivedImage) 305 306 // Repull 307 dockerCmd(c, "pull", derivedImage) 308 309 // Check that this pulled image is based on the original base image 310 derivedIDAfterPull1 := imageID(c, derivedImage) 311 derivedParentAfterPull1 := imageParent(c, imageParent(c, derivedImage)) 312 313 if derivedIDAfterPull1 == derivedIDBeforePush { 314 c.Fatal("image's ID should have changed on after deleting and pulling") 315 } 316 317 if derivedParentAfterPull1 != baseIDBeforePush { 318 c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull1, baseIDBeforePush) 319 } 320 321 // Make sure the image runs correctly 322 out, _ := dockerCmd(c, "run", "--rm", derivedImage) 323 if strings.TrimSpace(out) != derivedImage { 324 c.Fatalf("expected %s; got %s", derivedImage, out) 325 } 326 327 // Confirm that repushing and repulling does not change the computed ID 328 dockerCmd(c, "push", derivedImage) 329 dockerCmd(c, "rmi", derivedImage) 330 dockerCmd(c, "pull", derivedImage) 331 332 derivedIDAfterPull2 := imageID(c, derivedImage) 333 derivedParentAfterPull2 := imageParent(c, imageParent(c, derivedImage)) 334 335 if derivedIDAfterPull2 != derivedIDAfterPull1 { 336 c.Fatal("image's ID unexpectedly changed after a repush/repull") 337 } 338 339 if derivedParentAfterPull2 != baseIDBeforePush { 340 c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull2, baseIDBeforePush) 341 } 342 343 // Make sure the image still runs 344 out, _ = dockerCmd(c, "run", "--rm", derivedImage) 345 if strings.TrimSpace(out) != derivedImage { 346 c.Fatalf("expected %s; got %s", derivedImage, out) 347 } 348 } 349 350 // TestPullConflict provides coverage of the situation where a computed 351 // strongID conflicts with some unverifiable data in the graph. 352 func (s *DockerRegistrySuite) TestPullConflict(c *check.C) { 353 repoName := privateRegistryURL + "/dockercli/conflict" 354 355 _, err := buildImage(repoName, ` 356 FROM scratch 357 ENV IMAGE conflict 358 CMD echo conflict 359 `, true) 360 if err != nil { 361 c.Fatal(err) 362 } 363 364 dockerCmd(c, "push", repoName) 365 366 // Pull to make it content-addressable 367 dockerCmd(c, "rmi", repoName) 368 dockerCmd(c, "pull", repoName) 369 370 IDBeforeLoad := imageID(c, repoName) 371 372 // Load/save to turn this into an unverified image with the same ID 373 tmpDir, err := ioutil.TempDir("", "conflict-save-output") 374 if err != nil { 375 c.Errorf("failed to create temporary directory: %s", err) 376 } 377 defer os.RemoveAll(tmpDir) 378 379 tarFile := filepath.Join(tmpDir, "repo.tar") 380 381 dockerCmd(c, "save", "-o", tarFile, repoName) 382 dockerCmd(c, "rmi", repoName) 383 dockerCmd(c, "load", "-i", tarFile) 384 385 // Check that the the ID is the same after save/load. 386 IDAfterLoad := imageID(c, repoName) 387 388 if IDAfterLoad != IDBeforeLoad { 389 c.Fatal("image's ID should be the same after save/load") 390 } 391 392 // Repull 393 dockerCmd(c, "pull", repoName) 394 395 // Check that the ID is now different because of the conflict. 396 IDAfterPull1 := imageID(c, repoName) 397 398 // Expect the new ID to be SHA256(oldID) 399 expectedIDDigest, err := digest.FromBytes([]byte(IDBeforeLoad)) 400 if err != nil { 401 c.Fatalf("digest error: %v", err) 402 } 403 expectedID := expectedIDDigest.Hex() 404 if IDAfterPull1 != expectedID { 405 c.Fatalf("image's ID should have changed on pull to %s (got %s)", expectedID, IDAfterPull1) 406 } 407 408 // A second pull should use the new ID again. 409 dockerCmd(c, "pull", repoName) 410 411 IDAfterPull2 := imageID(c, repoName) 412 413 if IDAfterPull2 != IDAfterPull1 { 414 c.Fatal("image's ID unexpectedly changed after a repull") 415 } 416 }