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