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 icmd "github.com/docker/docker/pkg/testutil/cmd"
19 "github.com/go-check/check"
20 "github.com/opencontainers/go-digest"
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.
26 // Ref: docker/docker#8141
27 func testPullImageWithAliases(c *check.C) {
28 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
31 for _, tag := range []string{"recent", "fresh"} {
32 repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag))
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)
41 // Clear local images store.
42 args := append([]string{"rmi"}, repos...)
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))
54 func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
55 testPullImageWithAliases(c)
58 func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) {
59 testPullImageWithAliases(c)
62 // testConcurrentPullWholeRepo pulls the same repo concurrently.
63 func testConcurrentPullWholeRepo(c *check.C) {
64 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
67 for _, tag := range []string{"recent", "fresh", "todays"} {
68 repo := fmt.Sprintf("%v:%v", repoName, tag)
69 buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(`
71 ENTRYPOINT ["/bin/echo"]
76 dockerCmd(c, "push", repo)
77 repos = append(repos, repo)
80 // Clear local images store.
81 args := append([]string{"rmi"}, repos...)
84 // Run multiple re-pulls concurrently
85 results := make(chan error)
88 for i := 0; i != numPulls; i++ {
90 result := icmd.RunCommand(dockerBinary, "pull", "-a", repoName)
91 results <- result.Error
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++ {
99 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
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)
110 func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
111 testConcurrentPullWholeRepo(c)
114 func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
115 testConcurrentPullWholeRepo(c)
118 // testConcurrentFailingPull tries a concurrent pull that doesn't succeed.
119 func testConcurrentFailingPull(c *check.C) {
120 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
122 // Run multiple pulls concurrently
123 results := make(chan error)
126 for i := 0; i != numPulls; i++ {
128 result := icmd.RunCommand(dockerBinary, "pull", repoName+":asdfasdf")
129 results <- result.Error
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++ {
137 c.Assert(err, checker.NotNil, check.Commentf("expected pull to fail"))
141 func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) {
142 testConcurrentFailingPull(c)
145 func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) {
146 testConcurrentFailingPull(c)
149 // testConcurrentPullMultipleTags pulls multiple tags from the same repo
151 func testConcurrentPullMultipleTags(c *check.C) {
152 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
155 for _, tag := range []string{"recent", "fresh", "todays"} {
156 repo := fmt.Sprintf("%v:%v", repoName, tag)
157 buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(`
159 ENTRYPOINT ["/bin/echo"]
164 dockerCmd(c, "push", repo)
165 repos = append(repos, repo)
168 // Clear local images store.
169 args := append([]string{"rmi"}, repos...)
170 dockerCmd(c, args...)
172 // Re-pull individual tags, in parallel
173 results := make(chan error)
175 for _, repo := range repos {
176 go func(repo string) {
177 result := icmd.RunCommand(dockerBinary, "pull", repo)
178 results <- result.Error
182 // These checks are separate from the loop above because the check
183 // package is not goroutine-safe.
186 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
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)
197 func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
198 testConcurrentPullMultipleTags(c)
201 func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
202 testConcurrentPullMultipleTags(c)
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"
211 buildImageSuccessfully(c, derivedImage, build.WithDockerfile(fmt.Sprintf(`
215 RUN dd if=/dev/zero of=/file bs=1024 count=1024
217 `, baseImage, derivedImage)))
219 originalID := getIDByName(c, derivedImage)
220 dockerCmd(c, "push", derivedImage)
223 out, _ := dockerCmd(c, "pull", derivedImage)
224 if strings.Contains(out, "Pull complete") {
225 c.Fatalf("repull redownloaded a layer: %s", out)
228 derivedIDAfterPull := getIDByName(c, derivedImage)
230 if derivedIDAfterPull != originalID {
231 c.Fatal("image's ID unexpectedly changed after a repush/repull")
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)
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)
245 derivedIDAfterPull = getIDByName(c, derivedImage)
247 if derivedIDAfterPull != originalID {
248 c.Fatal("image's ID unexpectedly changed after a repush/repull")
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)
258 func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
259 testPullIDStability(c)
262 func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) {
263 testPullIDStability(c)
267 func testPullNoLayers(c *check.C) {
268 repoName := fmt.Sprintf("%v/dockercli/scratch", privateRegistryURL)
270 buildImageSuccessfully(c, repoName, build.WithDockerfile(`
273 dockerCmd(c, "push", repoName)
274 dockerCmd(c, "rmi", repoName)
275 dockerCmd(c, "pull", repoName)
278 func (s *DockerRegistrySuite) TestPullNoLayers(c *check.C) {
282 func (s *DockerSchema1RegistrySuite) TestPullNoLayers(c *check.C) {
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"))
291 // Inject a manifest list into the registry
292 manifestList := &manifestlist.ManifestList{
293 Versioned: manifest.Versioned{
295 MediaType: manifestlist.MediaTypeManifestList,
297 Manifests: []manifestlist.ManifestDescriptor{
299 Descriptor: distribution.Descriptor{
300 Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
302 MediaType: schema2.MediaTypeManifest,
304 Platform: manifestlist.PlatformSpec{
305 Architecture: "bogus_arch",
310 Descriptor: distribution.Descriptor{
313 MediaType: schema2.MediaTypeManifest,
315 Platform: manifestlist.PlatformSpec{
316 Architecture: runtime.GOARCH,
323 manifestListJSON, err := json.MarshalIndent(manifestList, "", " ")
324 c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list"))
326 manifestListDigest := digest.FromBytes(manifestListJSON)
327 hexDigest := manifestListDigest.Hex()
329 registryV2Path := s.reg.Path()
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"))
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"))
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"))
352 // Verify that the image can be pulled through the manifest list.
353 out, _ := dockerCmd(c, "pull", repoName)
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]
360 // Make sure the pushed and pull digests match
361 c.Assert(manifestListDigest.String(), checker.Equals, pullDigest)
363 // Was the image actually created?
364 dockerCmd(c, "inspect", repoName)
366 dockerCmd(c, "rmi", repoName)
370 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuthLoginWithScheme(c *check.C) {
371 osPath := os.Getenv("PATH")
372 defer os.Setenv("PATH", osPath)
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)
380 os.Setenv("PATH", testPath)
382 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
384 tmp, err := ioutil.TempDir("", "integration-cli-")
385 c.Assert(err, checker.IsNil)
387 externalAuthConfig := `{ "credsStore": "shell-test" }`
389 configPath := filepath.Join(tmp, "config.json")
390 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
391 c.Assert(err, checker.IsNil)
393 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL)
395 b, err := ioutil.ReadFile(configPath)
396 c.Assert(err, checker.IsNil)
397 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
399 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
400 dockerCmd(c, "--config", tmp, "push", repoName)
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)
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)
411 // logout should work w scheme also because it will be stripped
412 dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL)
415 func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) {
416 osPath := os.Getenv("PATH")
417 defer os.Setenv("PATH", osPath)
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)
425 os.Setenv("PATH", testPath)
427 repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
429 tmp, err := ioutil.TempDir("", "integration-cli-")
430 c.Assert(err, checker.IsNil)
432 externalAuthConfig := `{ "credsStore": "shell-test" }`
434 configPath := filepath.Join(tmp, "config.json")
435 err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
436 c.Assert(err, checker.IsNil)
438 dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL)
440 b, err := ioutil.ReadFile(configPath)
441 c.Assert(err, checker.IsNil)
442 c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
444 dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
445 dockerCmd(c, "--config", tmp, "push", repoName)
447 dockerCmd(c, "--config", tmp, "pull", repoName)
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)
463 out, _ := dockerCmd(c, "run", repo)
464 c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo))
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)