github.com/ttys3/engine@v17.12.1-ce-rc2+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 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/gotestyourself/gotestyourself/icmd" 20 "github.com/opencontainers/go-digest" 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 *check.C) { 28 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 29 30 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 c.Assert(err, checker.NotNil, check.Commentf("Image %v shouldn't have been pulled down", repo)) 51 } 52 } 53 54 func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { 55 testPullImageWithAliases(c) 56 } 57 58 func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) { 59 testPullImageWithAliases(c) 60 } 61 62 // testConcurrentPullWholeRepo pulls the same repo concurrently. 63 func testConcurrentPullWholeRepo(c *check.C) { 64 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 65 66 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 results := make(chan error) 86 numPulls := 3 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 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 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 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 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) 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 c.Assert(err, checker.IsNil, check.Commentf("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 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 194 } 195 } 196 197 func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 198 testConcurrentPullMultipleTags(c) 199 } 200 201 func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 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 *check.C) { 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 *check.C) { 259 testPullIDStability(c) 260 } 261 262 func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) { 263 testPullIDStability(c) 264 } 265 266 // #21213 267 func testPullNoLayers(c *check.C) { 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 *check.C) { 279 testPullNoLayers(c) 280 } 281 282 func (s *DockerSchema1RegistrySuite) TestPullNoLayers(c *check.C) { 283 testPullNoLayers(c) 284 } 285 286 func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) { 287 testRequires(c, NotArm) 288 pushDigest, err := setupImage(c) 289 c.Assert(err, checker.IsNil, check.Commentf("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 c.Assert(err, checker.IsNil, check.Commentf("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 c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir")) 335 blobPath := filepath.Join(blobDir, "data") 336 err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644) 337 c.Assert(err, checker.IsNil, check.Commentf("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 c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir")) 343 revisionPath := filepath.Join(revisionDir, "link") 344 err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644) 345 c.Assert(err, checker.IsNil, check.Commentf("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 c.Assert(err, checker.IsNil, check.Commentf("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 c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out)) 358 pullDigest := matches[1] 359 360 // Make sure the pushed and pull digests match 361 c.Assert(manifestListDigest.String(), checker.Equals, 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 *check.C) { 371 osPath := os.Getenv("PATH") 372 defer os.Setenv("PATH", osPath) 373 374 workingDir, err := os.Getwd() 375 c.Assert(err, checker.IsNil) 376 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 377 c.Assert(err, checker.IsNil) 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 c.Assert(err, checker.IsNil) 386 387 externalAuthConfig := `{ "credsStore": "shell-test" }` 388 389 configPath := filepath.Join(tmp, "config.json") 390 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) 391 c.Assert(err, checker.IsNil) 392 393 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL) 394 395 b, err := ioutil.ReadFile(configPath) 396 c.Assert(err, checker.IsNil) 397 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") 398 399 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 400 dockerCmd(c, "--config", tmp, "push", repoName) 401 402 dockerCmd(c, "--config", tmp, "logout", privateRegistryURL) 403 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), "https://"+privateRegistryURL) 404 dockerCmd(c, "--config", tmp, "pull", repoName) 405 406 // likewise push should work 407 repoName2 := fmt.Sprintf("%v/dockercli/busybox:nocreds", privateRegistryURL) 408 dockerCmd(c, "tag", repoName, repoName2) 409 dockerCmd(c, "--config", tmp, "push", repoName2) 410 411 // logout should work w scheme also because it will be stripped 412 dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL) 413 } 414 415 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) { 416 osPath := os.Getenv("PATH") 417 defer os.Setenv("PATH", osPath) 418 419 workingDir, err := os.Getwd() 420 c.Assert(err, checker.IsNil) 421 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 422 c.Assert(err, checker.IsNil) 423 testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute) 424 425 os.Setenv("PATH", testPath) 426 427 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL) 428 429 tmp, err := ioutil.TempDir("", "integration-cli-") 430 c.Assert(err, checker.IsNil) 431 432 externalAuthConfig := `{ "credsStore": "shell-test" }` 433 434 configPath := filepath.Join(tmp, "config.json") 435 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) 436 c.Assert(err, checker.IsNil) 437 438 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL) 439 440 b, err := ioutil.ReadFile(configPath) 441 c.Assert(err, checker.IsNil) 442 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") 443 444 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 445 dockerCmd(c, "--config", tmp, "push", repoName) 446 447 dockerCmd(c, "--config", tmp, "pull", repoName) 448 } 449 450 // TestRunImplicitPullWithNoTag should pull implicitly only the default tag (latest) 451 func (s *DockerRegistrySuite) TestRunImplicitPullWithNoTag(c *check.C) { 452 testRequires(c, DaemonIsLinux) 453 repo := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 454 repoTag1 := fmt.Sprintf("%v:latest", repo) 455 repoTag2 := fmt.Sprintf("%v:t1", repo) 456 // tag the image and upload it to the private registry 457 dockerCmd(c, "tag", "busybox", repoTag1) 458 dockerCmd(c, "tag", "busybox", repoTag2) 459 dockerCmd(c, "push", repo) 460 dockerCmd(c, "rmi", repoTag1) 461 dockerCmd(c, "rmi", repoTag2) 462 463 out, _ := dockerCmd(c, "run", repo) 464 c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo)) 465 466 // There should be only one line for repo, the one with repo:latest 467 outImageCmd, _ := dockerCmd(c, "images", repo) 468 splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n") 469 c.Assert(splitOutImageCmd, checker.HasLen, 2) 470 }