Tizen_4.0 base
[platform/upstream/docker-engine.git] / integration-cli / docker_cli_pull_local_test.go
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         icmd "github.com/docker/docker/pkg/testutil/cmd"
19         "github.com/go-check/check"
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 }