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