docker_cli_pull_local_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "runtime"
  10. "strings"
  11. "github.com/docker/distribution"
  12. "github.com/docker/distribution/digest"
  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/pkg/integration/checker"
  17. "github.com/go-check/check"
  18. )
  19. // testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other
  20. // tags for the same image) are not also pulled down.
  21. //
  22. // Ref: docker/docker#8141
  23. func testPullImageWithAliases(c *check.C) {
  24. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  25. repos := []string{}
  26. for _, tag := range []string{"recent", "fresh"} {
  27. repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag))
  28. }
  29. // Tag and push the same image multiple times.
  30. for _, repo := range repos {
  31. dockerCmd(c, "tag", "busybox", repo)
  32. dockerCmd(c, "push", repo)
  33. }
  34. // Clear local images store.
  35. args := append([]string{"rmi"}, repos...)
  36. dockerCmd(c, args...)
  37. // Pull a single tag and verify it doesn't bring down all aliases.
  38. dockerCmd(c, "pull", repos[0])
  39. dockerCmd(c, "inspect", repos[0])
  40. for _, repo := range repos[1:] {
  41. _, _, err := dockerCmdWithError("inspect", repo)
  42. c.Assert(err, checker.NotNil, check.Commentf("Image %v shouldn't have been pulled down", repo))
  43. }
  44. }
  45. func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
  46. testPullImageWithAliases(c)
  47. }
  48. func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) {
  49. testPullImageWithAliases(c)
  50. }
  51. // testConcurrentPullWholeRepo pulls the same repo concurrently.
  52. func testConcurrentPullWholeRepo(c *check.C) {
  53. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  54. repos := []string{}
  55. for _, tag := range []string{"recent", "fresh", "todays"} {
  56. repo := fmt.Sprintf("%v:%v", repoName, tag)
  57. _, err := buildImage(repo, fmt.Sprintf(`
  58. FROM busybox
  59. ENTRYPOINT ["/bin/echo"]
  60. ENV FOO foo
  61. ENV BAR bar
  62. CMD echo %s
  63. `, repo), true)
  64. c.Assert(err, checker.IsNil)
  65. dockerCmd(c, "push", repo)
  66. repos = append(repos, repo)
  67. }
  68. // Clear local images store.
  69. args := append([]string{"rmi"}, repos...)
  70. dockerCmd(c, args...)
  71. // Run multiple re-pulls concurrently
  72. results := make(chan error)
  73. numPulls := 3
  74. for i := 0; i != numPulls; i++ {
  75. go func() {
  76. _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", "-a", repoName))
  77. results <- err
  78. }()
  79. }
  80. // These checks are separate from the loop above because the check
  81. // package is not goroutine-safe.
  82. for i := 0; i != numPulls; i++ {
  83. err := <-results
  84. c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
  85. }
  86. // Ensure all tags were pulled successfully
  87. for _, repo := range repos {
  88. dockerCmd(c, "inspect", repo)
  89. out, _ := dockerCmd(c, "run", "--rm", repo)
  90. c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo)
  91. }
  92. }
  93. func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
  94. testConcurrentPullWholeRepo(c)
  95. }
  96. func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
  97. testConcurrentPullWholeRepo(c)
  98. }
  99. // testConcurrentFailingPull tries a concurrent pull that doesn't succeed.
  100. func testConcurrentFailingPull(c *check.C) {
  101. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  102. // Run multiple pulls concurrently
  103. results := make(chan error)
  104. numPulls := 3
  105. for i := 0; i != numPulls; i++ {
  106. go func() {
  107. _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repoName+":asdfasdf"))
  108. results <- err
  109. }()
  110. }
  111. // These checks are separate from the loop above because the check
  112. // package is not goroutine-safe.
  113. for i := 0; i != numPulls; i++ {
  114. err := <-results
  115. c.Assert(err, checker.NotNil, check.Commentf("expected pull to fail"))
  116. }
  117. }
  118. func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) {
  119. testConcurrentFailingPull(c)
  120. }
  121. func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) {
  122. testConcurrentFailingPull(c)
  123. }
  124. // testConcurrentPullMultipleTags pulls multiple tags from the same repo
  125. // concurrently.
  126. func testConcurrentPullMultipleTags(c *check.C) {
  127. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  128. repos := []string{}
  129. for _, tag := range []string{"recent", "fresh", "todays"} {
  130. repo := fmt.Sprintf("%v:%v", repoName, tag)
  131. _, err := buildImage(repo, fmt.Sprintf(`
  132. FROM busybox
  133. ENTRYPOINT ["/bin/echo"]
  134. ENV FOO foo
  135. ENV BAR bar
  136. CMD echo %s
  137. `, repo), true)
  138. c.Assert(err, checker.IsNil)
  139. dockerCmd(c, "push", repo)
  140. repos = append(repos, repo)
  141. }
  142. // Clear local images store.
  143. args := append([]string{"rmi"}, repos...)
  144. dockerCmd(c, args...)
  145. // Re-pull individual tags, in parallel
  146. results := make(chan error)
  147. for _, repo := range repos {
  148. go func(repo string) {
  149. _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repo))
  150. results <- err
  151. }(repo)
  152. }
  153. // These checks are separate from the loop above because the check
  154. // package is not goroutine-safe.
  155. for range repos {
  156. err := <-results
  157. c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
  158. }
  159. // Ensure all tags were pulled successfully
  160. for _, repo := range repos {
  161. dockerCmd(c, "inspect", repo)
  162. out, _ := dockerCmd(c, "run", "--rm", repo)
  163. c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo)
  164. }
  165. }
  166. func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
  167. testConcurrentPullMultipleTags(c)
  168. }
  169. func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
  170. testConcurrentPullMultipleTags(c)
  171. }
  172. // testPullIDStability verifies that pushing an image and pulling it back
  173. // preserves the image ID.
  174. func testPullIDStability(c *check.C) {
  175. derivedImage := privateRegistryURL + "/dockercli/id-stability"
  176. baseImage := "busybox"
  177. _, err := buildImage(derivedImage, fmt.Sprintf(`
  178. FROM %s
  179. ENV derived true
  180. ENV asdf true
  181. RUN dd if=/dev/zero of=/file bs=1024 count=1024
  182. CMD echo %s
  183. `, baseImage, derivedImage), true)
  184. if err != nil {
  185. c.Fatal(err)
  186. }
  187. originalID, err := getIDByName(derivedImage)
  188. if err != nil {
  189. c.Fatalf("error inspecting: %v", err)
  190. }
  191. dockerCmd(c, "push", derivedImage)
  192. // Pull
  193. out, _ := dockerCmd(c, "pull", derivedImage)
  194. if strings.Contains(out, "Pull complete") {
  195. c.Fatalf("repull redownloaded a layer: %s", out)
  196. }
  197. derivedIDAfterPull, err := getIDByName(derivedImage)
  198. if err != nil {
  199. c.Fatalf("error inspecting: %v", err)
  200. }
  201. if derivedIDAfterPull != originalID {
  202. c.Fatal("image's ID unexpectedly changed after a repush/repull")
  203. }
  204. // Make sure the image runs correctly
  205. out, _ = dockerCmd(c, "run", "--rm", derivedImage)
  206. if strings.TrimSpace(out) != derivedImage {
  207. c.Fatalf("expected %s; got %s", derivedImage, out)
  208. }
  209. // Confirm that repushing and repulling does not change the computed ID
  210. dockerCmd(c, "push", derivedImage)
  211. dockerCmd(c, "rmi", derivedImage)
  212. dockerCmd(c, "pull", derivedImage)
  213. derivedIDAfterPull, err = getIDByName(derivedImage)
  214. if err != nil {
  215. c.Fatalf("error inspecting: %v", err)
  216. }
  217. if derivedIDAfterPull != originalID {
  218. c.Fatal("image's ID unexpectedly changed after a repush/repull")
  219. }
  220. if err != nil {
  221. c.Fatalf("error inspecting: %v", err)
  222. }
  223. // Make sure the image still runs
  224. out, _ = dockerCmd(c, "run", "--rm", derivedImage)
  225. if strings.TrimSpace(out) != derivedImage {
  226. c.Fatalf("expected %s; got %s", derivedImage, out)
  227. }
  228. }
  229. func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
  230. testPullIDStability(c)
  231. }
  232. func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) {
  233. testPullIDStability(c)
  234. }
  235. // #21213
  236. func testPullNoLayers(c *check.C) {
  237. repoName := fmt.Sprintf("%v/dockercli/scratch", privateRegistryURL)
  238. _, err := buildImage(repoName, `
  239. FROM scratch
  240. ENV foo bar`,
  241. true)
  242. if err != nil {
  243. c.Fatal(err)
  244. }
  245. dockerCmd(c, "push", repoName)
  246. dockerCmd(c, "rmi", repoName)
  247. dockerCmd(c, "pull", repoName)
  248. }
  249. func (s *DockerRegistrySuite) TestPullNoLayers(c *check.C) {
  250. testPullNoLayers(c)
  251. }
  252. func (s *DockerSchema1RegistrySuite) TestPullNoLayers(c *check.C) {
  253. testPullNoLayers(c)
  254. }
  255. func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
  256. testRequires(c, NotArm)
  257. pushDigest, err := setupImage(c)
  258. c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
  259. // Inject a manifest list into the registry
  260. manifestList := &manifestlist.ManifestList{
  261. Versioned: manifest.Versioned{
  262. SchemaVersion: 2,
  263. MediaType: manifestlist.MediaTypeManifestList,
  264. },
  265. Manifests: []manifestlist.ManifestDescriptor{
  266. {
  267. Descriptor: distribution.Descriptor{
  268. Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
  269. Size: 3253,
  270. MediaType: schema2.MediaTypeManifest,
  271. },
  272. Platform: manifestlist.PlatformSpec{
  273. Architecture: "bogus_arch",
  274. OS: "bogus_os",
  275. },
  276. },
  277. {
  278. Descriptor: distribution.Descriptor{
  279. Digest: pushDigest,
  280. Size: 3253,
  281. MediaType: schema2.MediaTypeManifest,
  282. },
  283. Platform: manifestlist.PlatformSpec{
  284. Architecture: runtime.GOARCH,
  285. OS: runtime.GOOS,
  286. },
  287. },
  288. },
  289. }
  290. manifestListJSON, err := json.MarshalIndent(manifestList, "", " ")
  291. c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list"))
  292. manifestListDigest := digest.FromBytes(manifestListJSON)
  293. hexDigest := manifestListDigest.Hex()
  294. registryV2Path := filepath.Join(s.reg.dir, "docker", "registry", "v2")
  295. // Write manifest list to blob store
  296. blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest)
  297. err = os.MkdirAll(blobDir, 0755)
  298. c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir"))
  299. blobPath := filepath.Join(blobDir, "data")
  300. err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644)
  301. c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list"))
  302. // Add to revision store
  303. revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest)
  304. err = os.Mkdir(revisionDir, 0755)
  305. c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir"))
  306. revisionPath := filepath.Join(revisionDir, "link")
  307. err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644)
  308. c.Assert(err, checker.IsNil, check.Commentf("error writing revision link"))
  309. // Update tag
  310. tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link")
  311. err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644)
  312. c.Assert(err, checker.IsNil, check.Commentf("error writing tag link"))
  313. // Verify that the image can be pulled through the manifest list.
  314. out, _ := dockerCmd(c, "pull", repoName)
  315. // The pull output includes "Digest: <digest>", so find that
  316. matches := digestRegex.FindStringSubmatch(out)
  317. c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out))
  318. pullDigest := matches[1]
  319. // Make sure the pushed and pull digests match
  320. c.Assert(manifestListDigest.String(), checker.Equals, pullDigest)
  321. // Was the image actually created?
  322. dockerCmd(c, "inspect", repoName)
  323. dockerCmd(c, "rmi", repoName)
  324. }
  325. func (s *DockerRegistryAuthSuite) TestPullWithExternalAuth(c *check.C) {
  326. osPath := os.Getenv("PATH")
  327. defer os.Setenv("PATH", osPath)
  328. workingDir, err := os.Getwd()
  329. c.Assert(err, checker.IsNil)
  330. absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
  331. c.Assert(err, checker.IsNil)
  332. testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
  333. os.Setenv("PATH", testPath)
  334. repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
  335. tmp, err := ioutil.TempDir("", "integration-cli-")
  336. c.Assert(err, checker.IsNil)
  337. externalAuthConfig := `{ "credsStore": "shell-test" }`
  338. configPath := filepath.Join(tmp, "config.json")
  339. err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
  340. c.Assert(err, checker.IsNil)
  341. dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
  342. b, err := ioutil.ReadFile(configPath)
  343. c.Assert(err, checker.IsNil)
  344. c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
  345. dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
  346. dockerCmd(c, "--config", tmp, "push", repoName)
  347. dockerCmd(c, "--config", tmp, "pull", repoName)
  348. }