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