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