github.com/boynux/docker@v1.11.0-rc4/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 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) { 391 osPath := os.Getenv("PATH") 392 defer os.Setenv("PATH", osPath) 393 394 workingDir, err := os.Getwd() 395 c.Assert(err, checker.IsNil) 396 absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth")) 397 c.Assert(err, checker.IsNil) 398 testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute) 399 400 os.Setenv("PATH", testPath) 401 402 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL) 403 404 tmp, err := ioutil.TempDir("", "integration-cli-") 405 c.Assert(err, checker.IsNil) 406 407 externalAuthConfig := `{ "credsStore": "shell-test" }` 408 409 configPath := filepath.Join(tmp, "config.json") 410 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644) 411 c.Assert(err, checker.IsNil) 412 413 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) 414 415 b, err := ioutil.ReadFile(configPath) 416 c.Assert(err, checker.IsNil) 417 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") 418 419 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) 420 dockerCmd(c, "--config", tmp, "push", repoName) 421 422 dockerCmd(c, "--config", tmp, "pull", repoName) 423 }