docker_cli_pull_local_test.go 15 KB

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