github.com/kinvolk/docker@v1.13.1/integration-cli/docker_cli_pull_local_test.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 "strings" 12 13 "github.com/docker/distribution" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/distribution/manifest" 16 "github.com/docker/distribution/manifest/manifestlist" 17 "github.com/docker/distribution/manifest/schema2" 18 "github.com/docker/docker/pkg/integration/checker" 19 "github.com/go-check/check" 20 ) 21 22 // testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other 23 // tags for the same image) are not also pulled down. 24 // 25 // Ref: docker/docker#8141 26 func testPullImageWithAliases(c *check.C) { 27 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 28 29 repos := []string{} 30 for _, tag := range []string{"recent", "fresh"} { 31 repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag)) 32 } 33 34 // Tag and push the same image multiple times. 35 for _, repo := range repos { 36 dockerCmd(c, "tag", "busybox", repo) 37 dockerCmd(c, "push", repo) 38 } 39 40 // Clear local images store. 41 args := append([]string{"rmi"}, repos...) 42 dockerCmd(c, args...) 43 44 // Pull a single tag and verify it doesn't bring down all aliases. 45 dockerCmd(c, "pull", repos[0]) 46 dockerCmd(c, "inspect", repos[0]) 47 for _, repo := range repos[1:] { 48 _, _, err := dockerCmdWithError("inspect", repo) 49 c.Assert(err, checker.NotNil, check.Commentf("Image %v shouldn't have been pulled down", repo)) 50 } 51 } 52 53 func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { 54 testPullImageWithAliases(c) 55 } 56 57 func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) { 58 testPullImageWithAliases(c) 59 } 60 61 // testConcurrentPullWholeRepo pulls the same repo concurrently. 62 func testConcurrentPullWholeRepo(c *check.C) { 63 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 64 65 repos := []string{} 66 for _, tag := range []string{"recent", "fresh", "todays"} { 67 repo := fmt.Sprintf("%v:%v", repoName, tag) 68 _, err := buildImage(repo, fmt.Sprintf(` 69 FROM busybox 70 ENTRYPOINT ["/bin/echo"] 71 ENV FOO foo 72 ENV BAR bar 73 CMD echo %s 74 `, repo), true) 75 c.Assert(err, checker.IsNil) 76 dockerCmd(c, "push", repo) 77 repos = append(repos, repo) 78 } 79 80 // Clear local images store. 81 args := append([]string{"rmi"}, repos...) 82 dockerCmd(c, args...) 83 84 // Run multiple re-pulls concurrently 85 results := make(chan error) 86 numPulls := 3 87 88 for i := 0; i != numPulls; i++ { 89 go func() { 90 _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", "-a", repoName)) 91 results <- err 92 }() 93 } 94 95 // These checks are separate from the loop above because the check 96 // package is not goroutine-safe. 97 for i := 0; i != numPulls; i++ { 98 err := <-results 99 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err)) 100 } 101 102 // Ensure all tags were pulled successfully 103 for _, repo := range repos { 104 dockerCmd(c, "inspect", repo) 105 out, _ := dockerCmd(c, "run", "--rm", repo) 106 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 107 } 108 } 109 110 func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) { 111 testConcurrentPullWholeRepo(c) 112 } 113 114 func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) { 115 testConcurrentPullWholeRepo(c) 116 } 117 118 // testConcurrentFailingPull tries a concurrent pull that doesn't succeed. 119 func testConcurrentFailingPull(c *check.C) { 120 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 121 122 // Run multiple pulls concurrently 123 results := make(chan error) 124 numPulls := 3 125 126 for i := 0; i != numPulls; i++ { 127 go func() { 128 _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repoName+":asdfasdf")) 129 results <- err 130 }() 131 } 132 133 // These checks are separate from the loop above because the check 134 // package is not goroutine-safe. 135 for i := 0; i != numPulls; i++ { 136 err := <-results 137 c.Assert(err, checker.NotNil, check.Commentf("expected pull to fail")) 138 } 139 } 140 141 func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) { 142 testConcurrentFailingPull(c) 143 } 144 145 func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) { 146 testConcurrentFailingPull(c) 147 } 148 149 // testConcurrentPullMultipleTags pulls multiple tags from the same repo 150 // concurrently. 151 func testConcurrentPullMultipleTags(c *check.C) { 152 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 153 154 repos := []string{} 155 for _, tag := range []string{"recent", "fresh", "todays"} { 156 repo := fmt.Sprintf("%v:%v", repoName, tag) 157 _, err := buildImage(repo, fmt.Sprintf(` 158 FROM busybox 159 ENTRYPOINT ["/bin/echo"] 160 ENV FOO foo 161 ENV BAR bar 162 CMD echo %s 163 `, repo), true) 164 c.Assert(err, checker.IsNil) 165 dockerCmd(c, "push", repo) 166 repos = append(repos, repo) 167 } 168 169 // Clear local images store. 170 args := append([]string{"rmi"}, repos...) 171 dockerCmd(c, args...) 172 173 // Re-pull individual tags, in parallel 174 results := make(chan error) 175 176 for _, repo := range repos { 177 go func(repo string) { 178 _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repo)) 179 results <- err 180 }(repo) 181 } 182 183 // These checks are separate from the loop above because the check 184 // package is not goroutine-safe. 185 for range repos { 186 err := <-results 187 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err)) 188 } 189 190 // Ensure all tags were pulled successfully 191 for _, repo := range repos { 192 dockerCmd(c, "inspect", repo) 193 out, _ := dockerCmd(c, "run", "--rm", repo) 194 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 195 } 196 } 197 198 func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 199 testConcurrentPullMultipleTags(c) 200 } 201 202 func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 203 testConcurrentPullMultipleTags(c) 204 } 205 206 // testPullIDStability verifies that pushing an image and pulling it back 207 // preserves the image ID. 208 func testPullIDStability(c *check.C) { 209 derivedImage := privateRegistryURL + "/dockercli/id-stability" 210 baseImage := "busybox" 211 212 _, err := buildImage(derivedImage, fmt.Sprintf(` 213 FROM %s 214 ENV derived true 215 ENV asdf true 216 RUN dd if=/dev/zero of=/file bs=1024 count=1024 217 CMD echo %s 218 `, baseImage, derivedImage), true) 219 if err != nil { 220 c.Fatal(err) 221 } 222 223 originalID, err := getIDByName(derivedImage) 224 if err != nil { 225 c.Fatalf("error inspecting: %v", err) 226 } 227 dockerCmd(c, "push", derivedImage) 228 229 // Pull 230 out, _ := dockerCmd(c, "pull", derivedImage) 231 if strings.Contains(out, "Pull complete") { 232 c.Fatalf("repull redownloaded a layer: %s", out) 233 } 234 235 derivedIDAfterPull, err := getIDByName(derivedImage) 236 if err != nil { 237 c.Fatalf("error inspecting: %v", err) 238 } 239 240 if derivedIDAfterPull != originalID { 241 c.Fatal("image's ID unexpectedly changed after a repush/repull") 242 } 243 244 // Make sure the image runs correctly 245 out, _ = dockerCmd(c, "run", "--rm", derivedImage) 246 if strings.TrimSpace(out) != derivedImage { 247 c.Fatalf("expected %s; got %s", derivedImage, out) 248 } 249 250 // Confirm that repushing and repulling does not change the computed ID 251 dockerCmd(c, "push", derivedImage) 252 dockerCmd(c, "rmi", derivedImage) 253 dockerCmd(c, "pull", derivedImage) 254 255 derivedIDAfterPull, err = getIDByName(derivedImage) 256 if err != nil { 257 c.Fatalf("error inspecting: %v", err) 258 } 259 260 if derivedIDAfterPull != originalID { 261 c.Fatal("image's ID unexpectedly changed after a repush/repull") 262 } 263 if err != nil { 264 c.Fatalf("error inspecting: %v", err) 265 } 266 267 // Make sure the image still runs 268 out, _ = dockerCmd(c, "run", "--rm", derivedImage) 269 if strings.TrimSpace(out) != derivedImage { 270 c.Fatalf("expected %s; got %s", derivedImage, out) 271 } 272 } 273 274 func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) { 275 testPullIDStability(c) 276 } 277 278 func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) { 279 testPullIDStability(c) 280 } 281 282 // #21213 283 func testPullNoLayers(c *check.C) { 284 repoName := fmt.Sprintf("%v/dockercli/scratch", privateRegistryURL) 285 286 _, err := buildImage(repoName, ` 287 FROM scratch 288 ENV foo bar`, 289 true) 290 if err != nil { 291 c.Fatal(err) 292 } 293 294 dockerCmd(c, "push", repoName) 295 dockerCmd(c, "rmi", repoName) 296 dockerCmd(c, "pull", repoName) 297 } 298 299 func (s *DockerRegistrySuite) TestPullNoLayers(c *check.C) { 300 testPullNoLayers(c) 301 } 302 303 func (s *DockerSchema1RegistrySuite) TestPullNoLayers(c *check.C) { 304 testPullNoLayers(c) 305 } 306 307 func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) { 308 testRequires(c, NotArm) 309 pushDigest, err := setupImage(c) 310 c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) 311 312 // Inject a manifest list into the registry 313 manifestList := &manifestlist.ManifestList{ 314 Versioned: manifest.Versioned{ 315 SchemaVersion: 2, 316 MediaType: manifestlist.MediaTypeManifestList, 317 }, 318 Manifests: []manifestlist.ManifestDescriptor{ 319 { 320 Descriptor: distribution.Descriptor{ 321 Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", 322 Size: 3253, 323 MediaType: schema2.MediaTypeManifest, 324 }, 325 Platform: manifestlist.PlatformSpec{ 326 Architecture: "bogus_arch", 327 OS: "bogus_os", 328 }, 329 }, 330 { 331 Descriptor: distribution.Descriptor{ 332 Digest: pushDigest, 333 Size: 3253, 334 MediaType: schema2.MediaTypeManifest, 335 }, 336 Platform: manifestlist.PlatformSpec{ 337 Architecture: runtime.GOARCH, 338 OS: runtime.GOOS, 339 }, 340 }, 341 }, 342 } 343 344 manifestListJSON, err := json.MarshalIndent(manifestList, "", " ") 345 c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list")) 346 347 manifestListDigest := digest.FromBytes(manifestListJSON) 348 hexDigest := manifestListDigest.Hex() 349 350 registryV2Path := filepath.Join(s.reg.dir, "docker", "registry", "v2") 351 352 // Write manifest list to blob store 353 blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest) 354 err = os.MkdirAll(blobDir, 0755) 355 c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir")) 356 blobPath := filepath.Join(blobDir, "data") 357 err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644) 358 c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list")) 359 360 // Add to revision store 361 revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest) 362 err = os.Mkdir(revisionDir, 0755) 363 c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir")) 364 revisionPath := filepath.Join(revisionDir, "link") 365 err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644) 366 c.Assert(err, checker.IsNil, check.Commentf("error writing revision link")) 367 368 // Update tag 369 tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link") 370 err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644) 371 c.Assert(err, checker.IsNil, check.Commentf("error writing tag link")) 372 373 // Verify that the image can be pulled through the manifest list. 374 out, _ := dockerCmd(c, "pull", repoName) 375 376 // The pull output includes "Digest: <digest>", so find that 377 matches := digestRegex.FindStringSubmatch(out) 378 c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out)) 379 pullDigest := matches[1] 380 381 // Make sure the pushed and pull digests match 382 c.Assert(manifestListDigest.String(), checker.Equals, pullDigest) 383 384 // Was the image actually created? 385 dockerCmd(c, "inspect", repoName) 386 387 dockerCmd(c, "rmi", repoName) 388 } 389 390 // #23100 391 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuthLoginWithScheme(c *check.C) { 392 osPath := os.Getenv("PATH") 393 defer os.Setenv("PATH", osPath) 394 395 workingDir, err := os.Getwd() 396 c.Assert(err, checker.IsNil) 397 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 398 c.Assert(err, checker.IsNil) 399 testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute) 400 401 os.Setenv("PATH", testPath) 402 403 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL) 404 405 tmp, err := ioutil.TempDir("", "integration-cli-") 406 c.Assert(err, checker.IsNil) 407 408 externalAuthConfig := `{ "credsStore": "shell-test" }` 409 410 configPath := filepath.Join(tmp, "config.json") 411 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) 412 c.Assert(err, checker.IsNil) 413 414 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) 415 416 b, err := ioutil.ReadFile(configPath) 417 c.Assert(err, checker.IsNil) 418 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") 419 420 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 421 dockerCmd(c, "--config", tmp, "push", repoName) 422 423 dockerCmd(c, "--config", tmp, "logout", privateRegistryURL) 424 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, "https://"+privateRegistryURL) 425 dockerCmd(c, "--config", tmp, "pull", repoName) 426 427 // likewise push should work 428 repoName2 := fmt.Sprintf("%v/dockercli/busybox:nocreds", privateRegistryURL) 429 dockerCmd(c, "tag", repoName, repoName2) 430 dockerCmd(c, "--config", tmp, "push", repoName2) 431 432 // logout should work w scheme also because it will be stripped 433 dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL) 434 } 435 436 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) { 437 osPath := os.Getenv("PATH") 438 defer os.Setenv("PATH", osPath) 439 440 workingDir, err := os.Getwd() 441 c.Assert(err, checker.IsNil) 442 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 443 c.Assert(err, checker.IsNil) 444 testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute) 445 446 os.Setenv("PATH", testPath) 447 448 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL) 449 450 tmp, err := ioutil.TempDir("", "integration-cli-") 451 c.Assert(err, checker.IsNil) 452 453 externalAuthConfig := `{ "credsStore": "shell-test" }` 454 455 configPath := filepath.Join(tmp, "config.json") 456 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) 457 c.Assert(err, checker.IsNil) 458 459 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) 460 461 b, err := ioutil.ReadFile(configPath) 462 c.Assert(err, checker.IsNil) 463 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") 464 465 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 466 dockerCmd(c, "--config", tmp, "push", repoName) 467 468 dockerCmd(c, "--config", tmp, "pull", repoName) 469 } 470 471 // TestRunImplicitPullWithNoTag should pull implicitly only the default tag (latest) 472 func (s *DockerRegistrySuite) TestRunImplicitPullWithNoTag(c *check.C) { 473 testRequires(c, DaemonIsLinux) 474 repo := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 475 repoTag1 := fmt.Sprintf("%v:latest", repo) 476 repoTag2 := fmt.Sprintf("%v:t1", repo) 477 // tag the image and upload it to the private registry 478 dockerCmd(c, "tag", "busybox", repoTag1) 479 dockerCmd(c, "tag", "busybox", repoTag2) 480 dockerCmd(c, "push", repo) 481 dockerCmd(c, "rmi", repoTag1) 482 dockerCmd(c, "rmi", repoTag2) 483 484 out, _, err := dockerCmdWithError("run", repo) 485 c.Assert(err, check.IsNil) 486 c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo)) 487 488 // There should be only one line for repo, the one with repo:latest 489 outImageCmd, _, err := dockerCmdWithError("images", repo) 490 splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n") 491 c.Assert(splitOutImageCmd, checker.HasLen, 2) 492 }