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