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