docker_cli_push_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. package main
  2. import (
  3. "archive/tar"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/http/httptest"
  8. "os"
  9. "strings"
  10. "sync"
  11. "github.com/docker/distribution/reference"
  12. "github.com/docker/docker/integration-cli/checker"
  13. "github.com/docker/docker/integration-cli/cli/build"
  14. "github.com/go-check/check"
  15. "gotest.tools/icmd"
  16. )
  17. // Pushing an image to a private registry.
  18. func testPushBusyboxImage(c *check.C) {
  19. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  20. // tag the image to upload it to the private registry
  21. dockerCmd(c, "tag", "busybox", repoName)
  22. // push the image to the registry
  23. dockerCmd(c, "push", repoName)
  24. }
  25. func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) {
  26. testPushBusyboxImage(c)
  27. }
  28. // pushing an image without a prefix should throw an error
  29. func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) {
  30. out, _, err := dockerCmdWithError("push", "busybox")
  31. c.Assert(err, check.NotNil, check.Commentf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out))
  32. }
  33. func testPushUntagged(c *check.C) {
  34. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  35. expected := "An image does not exist locally with the tag"
  36. out, _, err := dockerCmdWithError("push", repoName)
  37. c.Assert(err, check.NotNil, check.Commentf("pushing the image to the private registry should have failed: output %q", out))
  38. c.Assert(out, checker.Contains, expected, check.Commentf("pushing the image failed"))
  39. }
  40. func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) {
  41. testPushUntagged(c)
  42. }
  43. func testPushBadTag(c *check.C) {
  44. repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL)
  45. expected := "does not exist"
  46. out, _, err := dockerCmdWithError("push", repoName)
  47. c.Assert(err, check.NotNil, check.Commentf("pushing the image to the private registry should have failed: output %q", out))
  48. c.Assert(out, checker.Contains, expected, check.Commentf("pushing the image failed"))
  49. }
  50. func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) {
  51. testPushBadTag(c)
  52. }
  53. func testPushMultipleTags(c *check.C) {
  54. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  55. repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL)
  56. repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL)
  57. // tag the image and upload it to the private registry
  58. dockerCmd(c, "tag", "busybox", repoTag1)
  59. dockerCmd(c, "tag", "busybox", repoTag2)
  60. dockerCmd(c, "push", repoName)
  61. // Ensure layer list is equivalent for repoTag1 and repoTag2
  62. out1, _ := dockerCmd(c, "pull", repoTag1)
  63. imageAlreadyExists := ": Image already exists"
  64. var out1Lines []string
  65. for _, outputLine := range strings.Split(out1, "\n") {
  66. if strings.Contains(outputLine, imageAlreadyExists) {
  67. out1Lines = append(out1Lines, outputLine)
  68. }
  69. }
  70. out2, _ := dockerCmd(c, "pull", repoTag2)
  71. var out2Lines []string
  72. for _, outputLine := range strings.Split(out2, "\n") {
  73. if strings.Contains(outputLine, imageAlreadyExists) {
  74. out1Lines = append(out1Lines, outputLine)
  75. }
  76. }
  77. c.Assert(out2Lines, checker.HasLen, len(out1Lines))
  78. for i := range out1Lines {
  79. c.Assert(out1Lines[i], checker.Equals, out2Lines[i])
  80. }
  81. }
  82. func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
  83. testPushMultipleTags(c)
  84. }
  85. func testPushEmptyLayer(c *check.C) {
  86. repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
  87. emptyTarball, err := ioutil.TempFile("", "empty_tarball")
  88. c.Assert(err, check.IsNil, check.Commentf("Unable to create test file"))
  89. tw := tar.NewWriter(emptyTarball)
  90. err = tw.Close()
  91. c.Assert(err, check.IsNil, check.Commentf("Error creating empty tarball"))
  92. freader, err := os.Open(emptyTarball.Name())
  93. c.Assert(err, check.IsNil, check.Commentf("Could not open test tarball"))
  94. defer freader.Close()
  95. icmd.RunCmd(icmd.Cmd{
  96. Command: []string{dockerBinary, "import", "-", repoName},
  97. Stdin: freader,
  98. }).Assert(c, icmd.Success)
  99. // Now verify we can push it
  100. out, _, err := dockerCmdWithError("push", repoName)
  101. c.Assert(err, check.IsNil, check.Commentf("pushing the image to the private registry has failed: %s", out))
  102. }
  103. func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
  104. testPushEmptyLayer(c)
  105. }
  106. // testConcurrentPush pushes multiple tags to the same repo
  107. // concurrently.
  108. func testConcurrentPush(c *check.C) {
  109. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  110. var repos []string
  111. for _, tag := range []string{"push1", "push2", "push3"} {
  112. repo := fmt.Sprintf("%v:%v", repoName, tag)
  113. buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(`
  114. FROM busybox
  115. ENTRYPOINT ["/bin/echo"]
  116. ENV FOO foo
  117. ENV BAR bar
  118. CMD echo %s
  119. `, repo)))
  120. repos = append(repos, repo)
  121. }
  122. // Push tags, in parallel
  123. results := make(chan error)
  124. for _, repo := range repos {
  125. go func(repo string) {
  126. result := icmd.RunCommand(dockerBinary, "push", repo)
  127. results <- result.Error
  128. }(repo)
  129. }
  130. for range repos {
  131. err := <-results
  132. c.Assert(err, checker.IsNil, check.Commentf("concurrent push failed with error: %v", err))
  133. }
  134. // Clear local images store.
  135. args := append([]string{"rmi"}, repos...)
  136. dockerCmd(c, args...)
  137. // Re-pull and run individual tags, to make sure pushes succeeded
  138. for _, repo := range repos {
  139. dockerCmd(c, "pull", repo)
  140. dockerCmd(c, "inspect", repo)
  141. out, _ := dockerCmd(c, "run", "--rm", repo)
  142. c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo)
  143. }
  144. }
  145. func (s *DockerRegistrySuite) TestConcurrentPush(c *check.C) {
  146. testConcurrentPush(c)
  147. }
  148. func (s *DockerRegistrySuite) TestCrossRepositoryLayerPush(c *check.C) {
  149. sourceRepoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  150. // tag the image to upload it to the private registry
  151. dockerCmd(c, "tag", "busybox", sourceRepoName)
  152. // push the image to the registry
  153. out1, _, err := dockerCmdWithError("push", sourceRepoName)
  154. c.Assert(err, check.IsNil, check.Commentf("pushing the image to the private registry has failed: %s", out1))
  155. // ensure that none of the layers were mounted from another repository during push
  156. c.Assert(strings.Contains(out1, "Mounted from"), check.Equals, false)
  157. digest1 := reference.DigestRegexp.FindString(out1)
  158. c.Assert(len(digest1), checker.GreaterThan, 0, check.Commentf("no digest found for pushed manifest"))
  159. destRepoName := fmt.Sprintf("%v/dockercli/crossrepopush", privateRegistryURL)
  160. // retag the image to upload the same layers to another repo in the same registry
  161. dockerCmd(c, "tag", "busybox", destRepoName)
  162. // push the image to the registry
  163. out2, _, err := dockerCmdWithError("push", destRepoName)
  164. c.Assert(err, check.IsNil, check.Commentf("pushing the image to the private registry has failed: %s", out2))
  165. // ensure that layers were mounted from the first repo during push
  166. c.Assert(strings.Contains(out2, "Mounted from dockercli/busybox"), check.Equals, true)
  167. digest2 := reference.DigestRegexp.FindString(out2)
  168. c.Assert(len(digest2), checker.GreaterThan, 0, check.Commentf("no digest found for pushed manifest"))
  169. c.Assert(digest1, check.Equals, digest2)
  170. // ensure that pushing again produces the same digest
  171. out3, _, err := dockerCmdWithError("push", destRepoName)
  172. c.Assert(err, check.IsNil, check.Commentf("pushing the image to the private registry has failed: %s", out2))
  173. digest3 := reference.DigestRegexp.FindString(out3)
  174. c.Assert(len(digest2), checker.GreaterThan, 0, check.Commentf("no digest found for pushed manifest"))
  175. c.Assert(digest3, check.Equals, digest2)
  176. // ensure that we can pull and run the cross-repo-pushed repository
  177. dockerCmd(c, "rmi", destRepoName)
  178. dockerCmd(c, "pull", destRepoName)
  179. out4, _ := dockerCmd(c, "run", destRepoName, "echo", "-n", "hello world")
  180. c.Assert(out4, check.Equals, "hello world")
  181. }
  182. func (s *DockerRegistryAuthHtpasswdSuite) TestPushNoCredentialsNoRetry(c *check.C) {
  183. repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
  184. dockerCmd(c, "tag", "busybox", repoName)
  185. out, _, err := dockerCmdWithError("push", repoName)
  186. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  187. c.Assert(out, check.Not(checker.Contains), "Retrying")
  188. c.Assert(out, checker.Contains, "no basic auth credentials")
  189. }
  190. // This may be flaky but it's needed not to regress on unauthorized push, see #21054
  191. func (s *DockerSuite) TestPushToCentralRegistryUnauthorized(c *check.C) {
  192. testRequires(c, Network)
  193. repoName := "test/busybox"
  194. dockerCmd(c, "tag", "busybox", repoName)
  195. out, _, err := dockerCmdWithError("push", repoName)
  196. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  197. c.Assert(out, check.Not(checker.Contains), "Retrying")
  198. }
  199. func getTestTokenService(status int, body string, retries int) *httptest.Server {
  200. var mu sync.Mutex
  201. return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  202. mu.Lock()
  203. if retries > 0 {
  204. w.WriteHeader(http.StatusServiceUnavailable)
  205. w.Header().Set("Content-Type", "application/json")
  206. w.Write([]byte(`{"errors":[{"code":"UNAVAILABLE","message":"cannot create token at this time"}]}`))
  207. retries--
  208. } else {
  209. w.WriteHeader(status)
  210. w.Header().Set("Content-Type", "application/json")
  211. w.Write([]byte(body))
  212. }
  213. mu.Unlock()
  214. }))
  215. }
  216. func (s *DockerRegistryAuthTokenSuite) TestPushTokenServiceUnauthResponse(c *check.C) {
  217. ts := getTestTokenService(http.StatusUnauthorized, `{"errors": [{"Code":"UNAUTHORIZED", "message": "a message", "detail": null}]}`, 0)
  218. defer ts.Close()
  219. s.setupRegistryWithTokenService(c, ts.URL)
  220. repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
  221. dockerCmd(c, "tag", "busybox", repoName)
  222. out, _, err := dockerCmdWithError("push", repoName)
  223. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  224. c.Assert(out, checker.Not(checker.Contains), "Retrying")
  225. c.Assert(out, checker.Contains, "unauthorized: a message")
  226. }
  227. func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseUnauthorized(c *check.C) {
  228. ts := getTestTokenService(http.StatusUnauthorized, `{"error": "unauthorized"}`, 0)
  229. defer ts.Close()
  230. s.setupRegistryWithTokenService(c, ts.URL)
  231. repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
  232. dockerCmd(c, "tag", "busybox", repoName)
  233. out, _, err := dockerCmdWithError("push", repoName)
  234. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  235. c.Assert(out, checker.Not(checker.Contains), "Retrying")
  236. split := strings.Split(out, "\n")
  237. c.Assert(split[len(split)-2], check.Equals, "unauthorized: authentication required")
  238. }
  239. func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseError(c *check.C) {
  240. ts := getTestTokenService(http.StatusTooManyRequests, `{"errors": [{"code":"TOOMANYREQUESTS","message":"out of tokens"}]}`, 3)
  241. defer ts.Close()
  242. s.setupRegistryWithTokenService(c, ts.URL)
  243. repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
  244. dockerCmd(c, "tag", "busybox", repoName)
  245. out, _, err := dockerCmdWithError("push", repoName)
  246. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  247. // TODO: isolate test so that it can be guaranteed that the 503 will trigger xfer retries
  248. //c.Assert(out, checker.Contains, "Retrying")
  249. //c.Assert(out, checker.Not(checker.Contains), "Retrying in 15")
  250. split := strings.Split(out, "\n")
  251. c.Assert(split[len(split)-2], check.Equals, "toomanyrequests: out of tokens")
  252. }
  253. func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseUnparsable(c *check.C) {
  254. ts := getTestTokenService(http.StatusForbidden, `no way`, 0)
  255. defer ts.Close()
  256. s.setupRegistryWithTokenService(c, ts.URL)
  257. repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
  258. dockerCmd(c, "tag", "busybox", repoName)
  259. out, _, err := dockerCmdWithError("push", repoName)
  260. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  261. c.Assert(out, checker.Not(checker.Contains), "Retrying")
  262. split := strings.Split(out, "\n")
  263. c.Assert(split[len(split)-2], checker.Contains, "error parsing HTTP 403 response body: ")
  264. }
  265. func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseNoToken(c *check.C) {
  266. ts := getTestTokenService(http.StatusOK, `{"something": "wrong"}`, 0)
  267. defer ts.Close()
  268. s.setupRegistryWithTokenService(c, ts.URL)
  269. repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
  270. dockerCmd(c, "tag", "busybox", repoName)
  271. out, _, err := dockerCmdWithError("push", repoName)
  272. c.Assert(err, check.NotNil, check.Commentf("%s", out))
  273. c.Assert(out, checker.Not(checker.Contains), "Retrying")
  274. split := strings.Split(out, "\n")
  275. c.Assert(split[len(split)-2], check.Equals, "authorization server did not include a token in the response")
  276. }