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