github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/integration-cli/docker_cli_pull_local_test.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/docker/distribution"
    13  	"github.com/docker/distribution/manifest"
    14  	"github.com/docker/distribution/manifest/manifestlist"
    15  	"github.com/docker/distribution/manifest/schema2"
    16  	"github.com/docker/docker/integration-cli/checker"
    17  	"github.com/docker/docker/integration-cli/cli/build"
    18  	"github.com/go-check/check"
    19  	"github.com/gotestyourself/gotestyourself/icmd"
    20  	"github.com/opencontainers/go-digest"
    21  )
    22  
    23  // testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other
    24  // tags for the same image) are not also pulled down.
    25  //
    26  // Ref: docker/docker#8141
    27  func testPullImageWithAliases(c *check.C) {
    28  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
    29  
    30  	repos := []string{}
    31  	for _, tag := range []string{"recent", "fresh"} {
    32  		repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag))
    33  	}
    34  
    35  	// Tag and push the same image multiple times.
    36  	for _, repo := range repos {
    37  		dockerCmd(c, "tag", "busybox", repo)
    38  		dockerCmd(c, "push", repo)
    39  	}
    40  
    41  	// Clear local images store.
    42  	args := append([]string{"rmi"}, repos...)
    43  	dockerCmd(c, args...)
    44  
    45  	// Pull a single tag and verify it doesn't bring down all aliases.
    46  	dockerCmd(c, "pull", repos[0])
    47  	dockerCmd(c, "inspect", repos[0])
    48  	for _, repo := range repos[1:] {
    49  		_, _, err := dockerCmdWithError("inspect", repo)
    50  		c.Assert(err, checker.NotNil, check.Commentf("Image %v shouldn't have been pulled down", repo))
    51  	}
    52  }
    53  
    54  func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
    55  	testPullImageWithAliases(c)
    56  }
    57  
    58  func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) {
    59  	testPullImageWithAliases(c)
    60  }
    61  
    62  // testConcurrentPullWholeRepo pulls the same repo concurrently.
    63  func testConcurrentPullWholeRepo(c *check.C) {
    64  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
    65  
    66  	repos := []string{}
    67  	for _, tag := range []string{"recent", "fresh", "todays"} {
    68  		repo := fmt.Sprintf("%v:%v", repoName, tag)
    69  		buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(`
    70  		    FROM busybox
    71  		    ENTRYPOINT ["/bin/echo"]
    72  		    ENV FOO foo
    73  		    ENV BAR bar
    74  		    CMD echo %s
    75  		`, repo)))
    76  		dockerCmd(c, "push", repo)
    77  		repos = append(repos, repo)
    78  	}
    79  
    80  	// Clear local images store.
    81  	args := append([]string{"rmi"}, repos...)
    82  	dockerCmd(c, args...)
    83  
    84  	// Run multiple re-pulls concurrently
    85  	results := make(chan error)
    86  	numPulls := 3
    87  
    88  	for i := 0; i != numPulls; i++ {
    89  		go func() {
    90  			result := icmd.RunCommand(dockerBinary, "pull", "-a", repoName)
    91  			results <- result.Error
    92  		}()
    93  	}
    94  
    95  	// These checks are separate from the loop above because the check
    96  	// package is not goroutine-safe.
    97  	for i := 0; i != numPulls; i++ {
    98  		err := <-results
    99  		c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
   100  	}
   101  
   102  	// Ensure all tags were pulled successfully
   103  	for _, repo := range repos {
   104  		dockerCmd(c, "inspect", repo)
   105  		out, _ := dockerCmd(c, "run", "--rm", repo)
   106  		c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo)
   107  	}
   108  }
   109  
   110  func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
   111  	testConcurrentPullWholeRepo(c)
   112  }
   113  
   114  func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
   115  	testConcurrentPullWholeRepo(c)
   116  }
   117  
   118  // testConcurrentFailingPull tries a concurrent pull that doesn't succeed.
   119  func testConcurrentFailingPull(c *check.C) {
   120  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
   121  
   122  	// Run multiple pulls concurrently
   123  	results := make(chan error)
   124  	numPulls := 3
   125  
   126  	for i := 0; i != numPulls; i++ {
   127  		go func() {
   128  			result := icmd.RunCommand(dockerBinary, "pull", repoName+":asdfasdf")
   129  			results <- result.Error
   130  		}()
   131  	}
   132  
   133  	// These checks are separate from the loop above because the check
   134  	// package is not goroutine-safe.
   135  	for i := 0; i != numPulls; i++ {
   136  		err := <-results
   137  		c.Assert(err, checker.NotNil, check.Commentf("expected pull to fail"))
   138  	}
   139  }
   140  
   141  func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) {
   142  	testConcurrentFailingPull(c)
   143  }
   144  
   145  func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) {
   146  	testConcurrentFailingPull(c)
   147  }
   148  
   149  // testConcurrentPullMultipleTags pulls multiple tags from the same repo
   150  // concurrently.
   151  func testConcurrentPullMultipleTags(c *check.C) {
   152  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
   153  
   154  	repos := []string{}
   155  	for _, tag := range []string{"recent", "fresh", "todays"} {
   156  		repo := fmt.Sprintf("%v:%v", repoName, tag)
   157  		buildImageSuccessfully(c, repo, build.WithDockerfile(fmt.Sprintf(`
   158  		    FROM busybox
   159  		    ENTRYPOINT ["/bin/echo"]
   160  		    ENV FOO foo
   161  		    ENV BAR bar
   162  		    CMD echo %s
   163  		`, repo)))
   164  		dockerCmd(c, "push", repo)
   165  		repos = append(repos, repo)
   166  	}
   167  
   168  	// Clear local images store.
   169  	args := append([]string{"rmi"}, repos...)
   170  	dockerCmd(c, args...)
   171  
   172  	// Re-pull individual tags, in parallel
   173  	results := make(chan error)
   174  
   175  	for _, repo := range repos {
   176  		go func(repo string) {
   177  			result := icmd.RunCommand(dockerBinary, "pull", repo)
   178  			results <- result.Error
   179  		}(repo)
   180  	}
   181  
   182  	// These checks are separate from the loop above because the check
   183  	// package is not goroutine-safe.
   184  	for range repos {
   185  		err := <-results
   186  		c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err))
   187  	}
   188  
   189  	// Ensure all tags were pulled successfully
   190  	for _, repo := range repos {
   191  		dockerCmd(c, "inspect", repo)
   192  		out, _ := dockerCmd(c, "run", "--rm", repo)
   193  		c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo)
   194  	}
   195  }
   196  
   197  func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
   198  	testConcurrentPullMultipleTags(c)
   199  }
   200  
   201  func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
   202  	testConcurrentPullMultipleTags(c)
   203  }
   204  
   205  // testPullIDStability verifies that pushing an image and pulling it back
   206  // preserves the image ID.
   207  func testPullIDStability(c *check.C) {
   208  	derivedImage := privateRegistryURL + "/dockercli/id-stability"
   209  	baseImage := "busybox"
   210  
   211  	buildImageSuccessfully(c, derivedImage, build.WithDockerfile(fmt.Sprintf(`
   212  	    FROM %s
   213  	    ENV derived true
   214  	    ENV asdf true
   215  	    RUN dd if=/dev/zero of=/file bs=1024 count=1024
   216  	    CMD echo %s
   217  	`, baseImage, derivedImage)))
   218  
   219  	originalID := getIDByName(c, derivedImage)
   220  	dockerCmd(c, "push", derivedImage)
   221  
   222  	// Pull
   223  	out, _ := dockerCmd(c, "pull", derivedImage)
   224  	if strings.Contains(out, "Pull complete") {
   225  		c.Fatalf("repull redownloaded a layer: %s", out)
   226  	}
   227  
   228  	derivedIDAfterPull := getIDByName(c, derivedImage)
   229  
   230  	if derivedIDAfterPull != originalID {
   231  		c.Fatal("image's ID unexpectedly changed after a repush/repull")
   232  	}
   233  
   234  	// Make sure the image runs correctly
   235  	out, _ = dockerCmd(c, "run", "--rm", derivedImage)
   236  	if strings.TrimSpace(out) != derivedImage {
   237  		c.Fatalf("expected %s; got %s", derivedImage, out)
   238  	}
   239  
   240  	// Confirm that repushing and repulling does not change the computed ID
   241  	dockerCmd(c, "push", derivedImage)
   242  	dockerCmd(c, "rmi", derivedImage)
   243  	dockerCmd(c, "pull", derivedImage)
   244  
   245  	derivedIDAfterPull = getIDByName(c, derivedImage)
   246  
   247  	if derivedIDAfterPull != originalID {
   248  		c.Fatal("image's ID unexpectedly changed after a repush/repull")
   249  	}
   250  
   251  	// Make sure the image still runs
   252  	out, _ = dockerCmd(c, "run", "--rm", derivedImage)
   253  	if strings.TrimSpace(out) != derivedImage {
   254  		c.Fatalf("expected %s; got %s", derivedImage, out)
   255  	}
   256  }
   257  
   258  func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
   259  	testPullIDStability(c)
   260  }
   261  
   262  func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) {
   263  	testPullIDStability(c)
   264  }
   265  
   266  // #21213
   267  func testPullNoLayers(c *check.C) {
   268  	repoName := fmt.Sprintf("%v/dockercli/scratch", privateRegistryURL)
   269  
   270  	buildImageSuccessfully(c, repoName, build.WithDockerfile(`
   271  	FROM scratch
   272  	ENV foo bar`))
   273  	dockerCmd(c, "push", repoName)
   274  	dockerCmd(c, "rmi", repoName)
   275  	dockerCmd(c, "pull", repoName)
   276  }
   277  
   278  func (s *DockerRegistrySuite) TestPullNoLayers(c *check.C) {
   279  	testPullNoLayers(c)
   280  }
   281  
   282  func (s *DockerSchema1RegistrySuite) TestPullNoLayers(c *check.C) {
   283  	testPullNoLayers(c)
   284  }
   285  
   286  func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
   287  	testRequires(c, NotArm)
   288  	pushDigest, err := setupImage(c)
   289  	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
   290  
   291  	// Inject a manifest list into the registry
   292  	manifestList := &manifestlist.ManifestList{
   293  		Versioned: manifest.Versioned{
   294  			SchemaVersion: 2,
   295  			MediaType:     manifestlist.MediaTypeManifestList,
   296  		},
   297  		Manifests: []manifestlist.ManifestDescriptor{
   298  			{
   299  				Descriptor: distribution.Descriptor{
   300  					Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
   301  					Size:      3253,
   302  					MediaType: schema2.MediaTypeManifest,
   303  				},
   304  				Platform: manifestlist.PlatformSpec{
   305  					Architecture: "bogus_arch",
   306  					OS:           "bogus_os",
   307  				},
   308  			},
   309  			{
   310  				Descriptor: distribution.Descriptor{
   311  					Digest:    pushDigest,
   312  					Size:      3253,
   313  					MediaType: schema2.MediaTypeManifest,
   314  				},
   315  				Platform: manifestlist.PlatformSpec{
   316  					Architecture: runtime.GOARCH,
   317  					OS:           runtime.GOOS,
   318  				},
   319  			},
   320  		},
   321  	}
   322  
   323  	manifestListJSON, err := json.MarshalIndent(manifestList, "", "   ")
   324  	c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list"))
   325  
   326  	manifestListDigest := digest.FromBytes(manifestListJSON)
   327  	hexDigest := manifestListDigest.Hex()
   328  
   329  	registryV2Path := s.reg.Path()
   330  
   331  	// Write manifest list to blob store
   332  	blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest)
   333  	err = os.MkdirAll(blobDir, 0755)
   334  	c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir"))
   335  	blobPath := filepath.Join(blobDir, "data")
   336  	err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644)
   337  	c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list"))
   338  
   339  	// Add to revision store
   340  	revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest)
   341  	err = os.Mkdir(revisionDir, 0755)
   342  	c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir"))
   343  	revisionPath := filepath.Join(revisionDir, "link")
   344  	err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644)
   345  	c.Assert(err, checker.IsNil, check.Commentf("error writing revision link"))
   346  
   347  	// Update tag
   348  	tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link")
   349  	err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644)
   350  	c.Assert(err, checker.IsNil, check.Commentf("error writing tag link"))
   351  
   352  	// Verify that the image can be pulled through the manifest list.
   353  	out, _ := dockerCmd(c, "pull", repoName)
   354  
   355  	// The pull output includes "Digest: <digest>", so find that
   356  	matches := digestRegex.FindStringSubmatch(out)
   357  	c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out))
   358  	pullDigest := matches[1]
   359  
   360  	// Make sure the pushed and pull digests match
   361  	c.Assert(manifestListDigest.String(), checker.Equals, pullDigest)
   362  
   363  	// Was the image actually created?
   364  	dockerCmd(c, "inspect", repoName)
   365  
   366  	dockerCmd(c, "rmi", repoName)
   367  }
   368  
   369  // #23100
   370  func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuthLoginWithScheme(c *check.C) {
   371  	osPath := os.Getenv("PATH")
   372  	defer os.Setenv("PATH", osPath)
   373  
   374  	workingDir, err := os.Getwd()
   375  	c.Assert(err, checker.IsNil)
   376  	absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
   377  	c.Assert(err, checker.IsNil)
   378  	testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
   379  
   380  	os.Setenv("PATH", testPath)
   381  
   382  	repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
   383  
   384  	tmp, err := ioutil.TempDir("", "integration-cli-")
   385  	c.Assert(err, checker.IsNil)
   386  
   387  	externalAuthConfig := `{ "credsStore": "shell-test" }`
   388  
   389  	configPath := filepath.Join(tmp, "config.json")
   390  	err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
   391  	c.Assert(err, checker.IsNil)
   392  
   393  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL)
   394  
   395  	b, err := ioutil.ReadFile(configPath)
   396  	c.Assert(err, checker.IsNil)
   397  	c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
   398  
   399  	dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
   400  	dockerCmd(c, "--config", tmp, "push", repoName)
   401  
   402  	dockerCmd(c, "--config", tmp, "logout", privateRegistryURL)
   403  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), "https://"+privateRegistryURL)
   404  	dockerCmd(c, "--config", tmp, "pull", repoName)
   405  
   406  	// likewise push should work
   407  	repoName2 := fmt.Sprintf("%v/dockercli/busybox:nocreds", privateRegistryURL)
   408  	dockerCmd(c, "tag", repoName, repoName2)
   409  	dockerCmd(c, "--config", tmp, "push", repoName2)
   410  
   411  	// logout should work w scheme also because it will be stripped
   412  	dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL)
   413  }
   414  
   415  func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) {
   416  	osPath := os.Getenv("PATH")
   417  	defer os.Setenv("PATH", osPath)
   418  
   419  	workingDir, err := os.Getwd()
   420  	c.Assert(err, checker.IsNil)
   421  	absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
   422  	c.Assert(err, checker.IsNil)
   423  	testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
   424  
   425  	os.Setenv("PATH", testPath)
   426  
   427  	repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
   428  
   429  	tmp, err := ioutil.TempDir("", "integration-cli-")
   430  	c.Assert(err, checker.IsNil)
   431  
   432  	externalAuthConfig := `{ "credsStore": "shell-test" }`
   433  
   434  	configPath := filepath.Join(tmp, "config.json")
   435  	err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
   436  	c.Assert(err, checker.IsNil)
   437  
   438  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.Username(), "-p", s.reg.Password(), privateRegistryURL)
   439  
   440  	b, err := ioutil.ReadFile(configPath)
   441  	c.Assert(err, checker.IsNil)
   442  	c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
   443  
   444  	dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
   445  	dockerCmd(c, "--config", tmp, "push", repoName)
   446  
   447  	dockerCmd(c, "--config", tmp, "pull", repoName)
   448  }
   449  
   450  // TestRunImplicitPullWithNoTag should pull implicitly only the default tag (latest)
   451  func (s *DockerRegistrySuite) TestRunImplicitPullWithNoTag(c *check.C) {
   452  	testRequires(c, DaemonIsLinux)
   453  	repo := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
   454  	repoTag1 := fmt.Sprintf("%v:latest", repo)
   455  	repoTag2 := fmt.Sprintf("%v:t1", repo)
   456  	// tag the image and upload it to the private registry
   457  	dockerCmd(c, "tag", "busybox", repoTag1)
   458  	dockerCmd(c, "tag", "busybox", repoTag2)
   459  	dockerCmd(c, "push", repo)
   460  	dockerCmd(c, "rmi", repoTag1)
   461  	dockerCmd(c, "rmi", repoTag2)
   462  
   463  	out, _ := dockerCmd(c, "run", repo)
   464  	c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo))
   465  
   466  	// There should be only one line for repo, the one with repo:latest
   467  	outImageCmd, _ := dockerCmd(c, "images", repo)
   468  	splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n")
   469  	c.Assert(splitOutImageCmd, checker.HasLen, 2)
   470  }