docker_cli_pull_local_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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 *DockerRegistryAuthHtpasswdSuite) 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. }
  349. // TestRunImplicitPullWithNoTag should pull implicitly only the default tag (latest)
  350. func (s *DockerRegistrySuite) TestRunImplicitPullWithNoTag(c *check.C) {
  351. testRequires(c, DaemonIsLinux)
  352. repo := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  353. repoTag1 := fmt.Sprintf("%v:latest", repo)
  354. repoTag2 := fmt.Sprintf("%v:t1", repo)
  355. // tag the image and upload it to the private registry
  356. dockerCmd(c, "tag", "busybox", repoTag1)
  357. dockerCmd(c, "tag", "busybox", repoTag2)
  358. dockerCmd(c, "push", repo)
  359. dockerCmd(c, "rmi", repoTag1)
  360. dockerCmd(c, "rmi", repoTag2)
  361. out, _, err := dockerCmdWithError("run", repo)
  362. c.Assert(err, check.IsNil)
  363. c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo))
  364. // There should be only one line for repo, the one with repo:latest
  365. outImageCmd, _, err := dockerCmdWithError("images", repo)
  366. splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n")
  367. c.Assert(splitOutImageCmd, checker.HasLen, 2)
  368. }