github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/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/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 *check.C) { 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 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 // testConcurrentPullWholeRepo pulls the same repo concurrently. 59 func testConcurrentPullWholeRepo(c *check.C) { 60 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 61 62 var repos []string 63 for _, tag := range []string{"recent", "fresh", "todays"} { 64 repo := fmt.Sprintf("%v:%v", repoName, tag) 65 buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(` 66 FROM busybox 67 ENTRYPOINT ["/bin/echo"] 68 ENV FOO foo 69 ENV BAR bar 70 CMD echo %s 71 `, repo))) 72 dockerCmd(c, "push", repo) 73 repos = append(repos, repo) 74 } 75 76 // Clear local images store. 77 args := append([]string{"rmi"}, repos...) 78 dockerCmd(c, args...) 79 80 // Run multiple re-pulls concurrently 81 results := make(chan error) 82 numPulls := 3 83 84 for i := 0; i != numPulls; i++ { 85 go func() { 86 result := icmd.RunCommand(dockerBinary, "pull", "-a", repoName) 87 results <- result.Error 88 }() 89 } 90 91 // These checks are separate from the loop above because the check 92 // package is not goroutine-safe. 93 for i := 0; i != numPulls; i++ { 94 err := <-results 95 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err)) 96 } 97 98 // Ensure all tags were pulled successfully 99 for _, repo := range repos { 100 dockerCmd(c, "inspect", repo) 101 out, _ := dockerCmd(c, "run", "--rm", repo) 102 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 103 } 104 } 105 106 func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) { 107 testConcurrentPullWholeRepo(c) 108 } 109 110 // testConcurrentFailingPull tries a concurrent pull that doesn't succeed. 111 func testConcurrentFailingPull(c *check.C) { 112 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 113 114 // Run multiple pulls concurrently 115 results := make(chan error) 116 numPulls := 3 117 118 for i := 0; i != numPulls; i++ { 119 go func() { 120 result := icmd.RunCommand(dockerBinary, "pull", repoName+":asdfasdf") 121 results <- result.Error 122 }() 123 } 124 125 // These checks are separate from the loop above because the check 126 // package is not goroutine-safe. 127 for i := 0; i != numPulls; i++ { 128 err := <-results 129 c.Assert(err, checker.NotNil, check.Commentf("expected pull to fail")) 130 } 131 } 132 133 func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) { 134 testConcurrentFailingPull(c) 135 } 136 137 // testConcurrentPullMultipleTags pulls multiple tags from the same repo 138 // concurrently. 139 func testConcurrentPullMultipleTags(c *check.C) { 140 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 141 142 var repos []string 143 for _, tag := range []string{"recent", "fresh", "todays"} { 144 repo := fmt.Sprintf("%v:%v", repoName, tag) 145 buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(` 146 FROM busybox 147 ENTRYPOINT ["/bin/echo"] 148 ENV FOO foo 149 ENV BAR bar 150 CMD echo %s 151 `, repo))) 152 dockerCmd(c, "push", repo) 153 repos = append(repos, repo) 154 } 155 156 // Clear local images store. 157 args := append([]string{"rmi"}, repos...) 158 dockerCmd(c, args...) 159 160 // Re-pull individual tags, in parallel 161 results := make(chan error) 162 163 for _, repo := range repos { 164 go func(repo string) { 165 result := icmd.RunCommand(dockerBinary, "pull", repo) 166 results <- result.Error 167 }(repo) 168 } 169 170 // These checks are separate from the loop above because the check 171 // package is not goroutine-safe. 172 for range repos { 173 err := <-results 174 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err)) 175 } 176 177 // Ensure all tags were pulled successfully 178 for _, repo := range repos { 179 dockerCmd(c, "inspect", repo) 180 out, _ := dockerCmd(c, "run", "--rm", repo) 181 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 182 } 183 } 184 185 func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 186 testConcurrentPullMultipleTags(c) 187 } 188 189 // testPullIDStability verifies that pushing an image and pulling it back 190 // preserves the image ID. 191 func testPullIDStability(c *check.C) { 192 derivedImage := privateRegistryURL + "/dockercli/id-stability" 193 baseImage := "busybox" 194 195 buildImageSuccessfully(c, derivedImage, build.WithDockerfile(fmt.Sprintf(` 196 FROM %s 197 ENV derived true 198 ENV asdf true 199 RUN dd if=/dev/zero of=/file bs=1024 count=1024 200 CMD echo %s 201 `, baseImage, derivedImage))) 202 203 originalID := getIDByName(c, derivedImage) 204 dockerCmd(c, "push", derivedImage) 205 206 // Pull 207 out, _ := dockerCmd(c, "pull", derivedImage) 208 if strings.Contains(out, "Pull complete") { 209 c.Fatalf("repull redownloaded a layer: %s", out) 210 } 211 212 derivedIDAfterPull := getIDByName(c, derivedImage) 213 214 if derivedIDAfterPull != originalID { 215 c.Fatal("image's ID unexpectedly changed after a repush/repull") 216 } 217 218 // Make sure the image runs correctly 219 out, _ = dockerCmd(c, "run", "--rm", derivedImage) 220 if strings.TrimSpace(out) != derivedImage { 221 c.Fatalf("expected %s; got %s", derivedImage, out) 222 } 223 224 // Confirm that repushing and repulling does not change the computed ID 225 dockerCmd(c, "push", derivedImage) 226 dockerCmd(c, "rmi", derivedImage) 227 dockerCmd(c, "pull", derivedImage) 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 still runs 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 242 func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) { 243 testPullIDStability(c) 244 } 245 246 // #21213 247 func testPullNoLayers(c *check.C) { 248 repoName := fmt.Sprintf("%v/dockercli/scratch", privateRegistryURL) 249 250 buildImageSuccessfully(c, repoName, build.WithDockerfile(` 251 FROM scratch 252 ENV foo bar`)) 253 dockerCmd(c, "push", repoName) 254 dockerCmd(c, "rmi", repoName) 255 dockerCmd(c, "pull", repoName) 256 } 257 258 func (s *DockerRegistrySuite) TestPullNoLayers(c *check.C) { 259 testPullNoLayers(c) 260 } 261 262 func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) { 263 testRequires(c, NotArm) 264 pushDigest, err := setupImage(c) 265 c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) 266 267 // Inject a manifest list into the registry 268 manifestList := &manifestlist.ManifestList{ 269 Versioned: manifest.Versioned{ 270 SchemaVersion: 2, 271 MediaType: manifestlist.MediaTypeManifestList, 272 }, 273 Manifests: []manifestlist.ManifestDescriptor{ 274 { 275 Descriptor: distribution.Descriptor{ 276 Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", 277 Size: 3253, 278 MediaType: schema2.MediaTypeManifest, 279 }, 280 Platform: manifestlist.PlatformSpec{ 281 Architecture: "bogus_arch", 282 OS: "bogus_os", 283 }, 284 }, 285 { 286 Descriptor: distribution.Descriptor{ 287 Digest: pushDigest, 288 Size: 3253, 289 MediaType: schema2.MediaTypeManifest, 290 }, 291 Platform: manifestlist.PlatformSpec{ 292 Architecture: runtime.GOARCH, 293 OS: runtime.GOOS, 294 }, 295 }, 296 }, 297 } 298 299 manifestListJSON, err := json.MarshalIndent(manifestList, "", " ") 300 c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list")) 301 302 manifestListDigest := digest.FromBytes(manifestListJSON) 303 hexDigest := manifestListDigest.Hex() 304 305 registryV2Path := s.reg.Path() 306 307 // Write manifest list to blob store 308 blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest) 309 err = os.MkdirAll(blobDir, 0755) 310 c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir")) 311 blobPath := filepath.Join(blobDir, "data") 312 err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644) 313 c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list")) 314 315 // Add to revision store 316 revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest) 317 err = os.Mkdir(revisionDir, 0755) 318 c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir")) 319 revisionPath := filepath.Join(revisionDir, "link") 320 err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644) 321 c.Assert(err, checker.IsNil, check.Commentf("error writing revision link")) 322 323 // Update tag 324 tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link") 325 err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644) 326 c.Assert(err, checker.IsNil, check.Commentf("error writing tag link")) 327 328 // Verify that the image can be pulled through the manifest list. 329 out, _ := dockerCmd(c, "pull", repoName) 330 331 // The pull output includes "Digest: <digest>", so find that 332 matches := digestRegex.FindStringSubmatch(out) 333 c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out)) 334 pullDigest := matches[1] 335 336 // Make sure the pushed and pull digests match 337 c.Assert(manifestListDigest.String(), checker.Equals, pullDigest) 338 339 // Was the image actually created? 340 dockerCmd(c, "inspect", repoName) 341 342 dockerCmd(c, "rmi", repoName) 343 } 344 345 // #23100 346 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuthLoginWithScheme(c *check.C) { 347 osPath := os.Getenv("PATH") 348 defer os.Setenv("PATH", osPath) 349 350 workingDir, err := os.Getwd() 351 c.Assert(err, checker.IsNil) 352 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 353 c.Assert(err, checker.IsNil) 354 testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute) 355 356 os.Setenv("PATH", testPath) 357 358 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL) 359 360 tmp, err := ioutil.TempDir("", "integration-cli-") 361 c.Assert(err, checker.IsNil) 362 363 externalAuthConfig := `{ "credsStore": "shell-test" }` 364 365 configPath := filepath.Join(tmp, "config.json") 366 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) 367 c.Assert(err, checker.IsNil) 368 369 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL) 370 371 b, err := ioutil.ReadFile(configPath) 372 c.Assert(err, checker.IsNil) 373 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") 374 375 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 376 dockerCmd(c, "--config", tmp, "push", repoName) 377 378 dockerCmd(c, "--config", tmp, "logout", privateRegistryURL) 379 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), "https://"+privateRegistryURL) 380 dockerCmd(c, "--config", tmp, "pull", repoName) 381 382 // likewise push should work 383 repoName2 := fmt.Sprintf("%v/dockercli/busybox:nocreds", privateRegistryURL) 384 dockerCmd(c, "tag", repoName, repoName2) 385 dockerCmd(c, "--config", tmp, "push", repoName2) 386 387 // logout should work w scheme also because it will be stripped 388 dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL) 389 } 390 391 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(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, "pull", repoName) 424 } 425 426 // TestRunImplicitPullWithNoTag should pull implicitly only the default tag (latest) 427 func (s *DockerRegistrySuite) TestRunImplicitPullWithNoTag(c *check.C) { 428 testRequires(c, DaemonIsLinux) 429 repo := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 430 repoTag1 := fmt.Sprintf("%v:latest", repo) 431 repoTag2 := fmt.Sprintf("%v:t1", repo) 432 // tag the image and upload it to the private registry 433 dockerCmd(c, "tag", "busybox", repoTag1) 434 dockerCmd(c, "tag", "busybox", repoTag2) 435 dockerCmd(c, "push", repo) 436 dockerCmd(c, "rmi", repoTag1) 437 dockerCmd(c, "rmi", repoTag2) 438 439 out, _ := dockerCmd(c, "run", repo) 440 c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo)) 441 442 // There should be only one line for repo, the one with repo:latest 443 outImageCmd, _ := dockerCmd(c, "images", repo) 444 splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n") 445 c.Assert(splitOutImageCmd, checker.HasLen, 2) 446 }