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