github.com/opsramp/moby@v1.13.1/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  	"os/exec"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"github.com/docker/distribution"
    14  	"github.com/docker/distribution/digest"
    15  	"github.com/docker/distribution/manifest"
    16  	"github.com/docker/distribution/manifest/manifestlist"
    17  	"github.com/docker/distribution/manifest/schema2"
    18  	"github.com/docker/docker/pkg/integration/checker"
    19  	"github.com/go-check/check"
    20  )
    21  
    22  // testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other
    23  // tags for the same image) are not also pulled down.
    24  //
    25  // Ref: docker/docker#8141
    26  func testPullImageWithAliases(c *check.C) {
    27  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
    28  
    29  	repos := []string{}
    30  	for _, tag := range []string{"recent", "fresh"} {
    31  		repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag))
    32  	}
    33  
    34  	// Tag and push the same image multiple times.
    35  	for _, repo := range repos {
    36  		dockerCmd(c, "tag", "busybox", repo)
    37  		dockerCmd(c, "push", repo)
    38  	}
    39  
    40  	// Clear local images store.
    41  	args := append([]string{"rmi"}, repos...)
    42  	dockerCmd(c, args...)
    43  
    44  	// Pull a single tag and verify it doesn't bring down all aliases.
    45  	dockerCmd(c, "pull", repos[0])
    46  	dockerCmd(c, "inspect", repos[0])
    47  	for _, repo := range repos[1:] {
    48  		_, _, err := dockerCmdWithError("inspect", repo)
    49  		c.Assert(err, checker.NotNil, check.Commentf("Image %v shouldn't have been pulled down", repo))
    50  	}
    51  }
    52  
    53  func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
    54  	testPullImageWithAliases(c)
    55  }
    56  
    57  func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) {
    58  	testPullImageWithAliases(c)
    59  }
    60  
    61  // testConcurrentPullWholeRepo pulls the same repo concurrently.
    62  func testConcurrentPullWholeRepo(c *check.C) {
    63  	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
    64  
    65  	repos := []string{}
    66  	for _, tag := range []string{"recent", "fresh", "todays"} {
    67  		repo := fmt.Sprintf("%v:%v", repoName, tag)
    68  		_, err := buildImage(repo, fmt.Sprintf(`
    69  		    FROM busybox
    70  		    ENTRYPOINT ["/bin/echo"]
    71  		    ENV FOO foo
    72  		    ENV BAR bar
    73  		    CMD echo %s
    74  		`, repo), true)
    75  		c.Assert(err, checker.IsNil)
    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  			_, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", "-a", repoName))
    91  			results <- err
    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  			_, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repoName+":asdfasdf"))
   129  			results <- err
   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  		_, err := buildImage(repo, fmt.Sprintf(`
   158  		    FROM busybox
   159  		    ENTRYPOINT ["/bin/echo"]
   160  		    ENV FOO foo
   161  		    ENV BAR bar
   162  		    CMD echo %s
   163  		`, repo), true)
   164  		c.Assert(err, checker.IsNil)
   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  			_, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repo))
   179  			results <- err
   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  		c.Assert(err, checker.IsNil, check.Commentf("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  		c.Assert(strings.TrimSpace(out), checker.Equals, "/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  	_, err := buildImage(derivedImage, 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), true)
   219  	if err != nil {
   220  		c.Fatal(err)
   221  	}
   222  
   223  	originalID, err := getIDByName(derivedImage)
   224  	if err != nil {
   225  		c.Fatalf("error inspecting: %v", err)
   226  	}
   227  	dockerCmd(c, "push", derivedImage)
   228  
   229  	// Pull
   230  	out, _ := dockerCmd(c, "pull", derivedImage)
   231  	if strings.Contains(out, "Pull complete") {
   232  		c.Fatalf("repull redownloaded a layer: %s", out)
   233  	}
   234  
   235  	derivedIDAfterPull, err := getIDByName(derivedImage)
   236  	if err != nil {
   237  		c.Fatalf("error inspecting: %v", err)
   238  	}
   239  
   240  	if derivedIDAfterPull != originalID {
   241  		c.Fatal("image's ID unexpectedly changed after a repush/repull")
   242  	}
   243  
   244  	// Make sure the image runs correctly
   245  	out, _ = dockerCmd(c, "run", "--rm", derivedImage)
   246  	if strings.TrimSpace(out) != derivedImage {
   247  		c.Fatalf("expected %s; got %s", derivedImage, out)
   248  	}
   249  
   250  	// Confirm that repushing and repulling does not change the computed ID
   251  	dockerCmd(c, "push", derivedImage)
   252  	dockerCmd(c, "rmi", derivedImage)
   253  	dockerCmd(c, "pull", derivedImage)
   254  
   255  	derivedIDAfterPull, err = getIDByName(derivedImage)
   256  	if err != nil {
   257  		c.Fatalf("error inspecting: %v", err)
   258  	}
   259  
   260  	if derivedIDAfterPull != originalID {
   261  		c.Fatal("image's ID unexpectedly changed after a repush/repull")
   262  	}
   263  	if err != nil {
   264  		c.Fatalf("error inspecting: %v", err)
   265  	}
   266  
   267  	// Make sure the image still runs
   268  	out, _ = dockerCmd(c, "run", "--rm", derivedImage)
   269  	if strings.TrimSpace(out) != derivedImage {
   270  		c.Fatalf("expected %s; got %s", derivedImage, out)
   271  	}
   272  }
   273  
   274  func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
   275  	testPullIDStability(c)
   276  }
   277  
   278  func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) {
   279  	testPullIDStability(c)
   280  }
   281  
   282  // #21213
   283  func testPullNoLayers(c *check.C) {
   284  	repoName := fmt.Sprintf("%v/dockercli/scratch", privateRegistryURL)
   285  
   286  	_, err := buildImage(repoName, `
   287  	FROM scratch
   288  	ENV foo bar`,
   289  		true)
   290  	if err != nil {
   291  		c.Fatal(err)
   292  	}
   293  
   294  	dockerCmd(c, "push", repoName)
   295  	dockerCmd(c, "rmi", repoName)
   296  	dockerCmd(c, "pull", repoName)
   297  }
   298  
   299  func (s *DockerRegistrySuite) TestPullNoLayers(c *check.C) {
   300  	testPullNoLayers(c)
   301  }
   302  
   303  func (s *DockerSchema1RegistrySuite) TestPullNoLayers(c *check.C) {
   304  	testPullNoLayers(c)
   305  }
   306  
   307  func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
   308  	testRequires(c, NotArm)
   309  	pushDigest, err := setupImage(c)
   310  	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
   311  
   312  	// Inject a manifest list into the registry
   313  	manifestList := &manifestlist.ManifestList{
   314  		Versioned: manifest.Versioned{
   315  			SchemaVersion: 2,
   316  			MediaType:     manifestlist.MediaTypeManifestList,
   317  		},
   318  		Manifests: []manifestlist.ManifestDescriptor{
   319  			{
   320  				Descriptor: distribution.Descriptor{
   321  					Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
   322  					Size:      3253,
   323  					MediaType: schema2.MediaTypeManifest,
   324  				},
   325  				Platform: manifestlist.PlatformSpec{
   326  					Architecture: "bogus_arch",
   327  					OS:           "bogus_os",
   328  				},
   329  			},
   330  			{
   331  				Descriptor: distribution.Descriptor{
   332  					Digest:    pushDigest,
   333  					Size:      3253,
   334  					MediaType: schema2.MediaTypeManifest,
   335  				},
   336  				Platform: manifestlist.PlatformSpec{
   337  					Architecture: runtime.GOARCH,
   338  					OS:           runtime.GOOS,
   339  				},
   340  			},
   341  		},
   342  	}
   343  
   344  	manifestListJSON, err := json.MarshalIndent(manifestList, "", "   ")
   345  	c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list"))
   346  
   347  	manifestListDigest := digest.FromBytes(manifestListJSON)
   348  	hexDigest := manifestListDigest.Hex()
   349  
   350  	registryV2Path := filepath.Join(s.reg.dir, "docker", "registry", "v2")
   351  
   352  	// Write manifest list to blob store
   353  	blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest)
   354  	err = os.MkdirAll(blobDir, 0755)
   355  	c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir"))
   356  	blobPath := filepath.Join(blobDir, "data")
   357  	err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644)
   358  	c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list"))
   359  
   360  	// Add to revision store
   361  	revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest)
   362  	err = os.Mkdir(revisionDir, 0755)
   363  	c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir"))
   364  	revisionPath := filepath.Join(revisionDir, "link")
   365  	err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644)
   366  	c.Assert(err, checker.IsNil, check.Commentf("error writing revision link"))
   367  
   368  	// Update tag
   369  	tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link")
   370  	err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644)
   371  	c.Assert(err, checker.IsNil, check.Commentf("error writing tag link"))
   372  
   373  	// Verify that the image can be pulled through the manifest list.
   374  	out, _ := dockerCmd(c, "pull", repoName)
   375  
   376  	// The pull output includes "Digest: <digest>", so find that
   377  	matches := digestRegex.FindStringSubmatch(out)
   378  	c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out))
   379  	pullDigest := matches[1]
   380  
   381  	// Make sure the pushed and pull digests match
   382  	c.Assert(manifestListDigest.String(), checker.Equals, pullDigest)
   383  
   384  	// Was the image actually created?
   385  	dockerCmd(c, "inspect", repoName)
   386  
   387  	dockerCmd(c, "rmi", repoName)
   388  }
   389  
   390  // #23100
   391  func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuthLoginWithScheme(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, "logout", privateRegistryURL)
   424  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, "https://"+privateRegistryURL)
   425  	dockerCmd(c, "--config", tmp, "pull", repoName)
   426  
   427  	// likewise push should work
   428  	repoName2 := fmt.Sprintf("%v/dockercli/busybox:nocreds", privateRegistryURL)
   429  	dockerCmd(c, "tag", repoName, repoName2)
   430  	dockerCmd(c, "--config", tmp, "push", repoName2)
   431  
   432  	// logout should work w scheme also because it will be stripped
   433  	dockerCmd(c, "--config", tmp, "logout", "https://"+privateRegistryURL)
   434  }
   435  
   436  func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) {
   437  	osPath := os.Getenv("PATH")
   438  	defer os.Setenv("PATH", osPath)
   439  
   440  	workingDir, err := os.Getwd()
   441  	c.Assert(err, checker.IsNil)
   442  	absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
   443  	c.Assert(err, checker.IsNil)
   444  	testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
   445  
   446  	os.Setenv("PATH", testPath)
   447  
   448  	repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
   449  
   450  	tmp, err := ioutil.TempDir("", "integration-cli-")
   451  	c.Assert(err, checker.IsNil)
   452  
   453  	externalAuthConfig := `{ "credsStore": "shell-test" }`
   454  
   455  	configPath := filepath.Join(tmp, "config.json")
   456  	err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
   457  	c.Assert(err, checker.IsNil)
   458  
   459  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
   460  
   461  	b, err := ioutil.ReadFile(configPath)
   462  	c.Assert(err, checker.IsNil)
   463  	c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
   464  
   465  	dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
   466  	dockerCmd(c, "--config", tmp, "push", repoName)
   467  
   468  	dockerCmd(c, "--config", tmp, "pull", repoName)
   469  }
   470  
   471  // TestRunImplicitPullWithNoTag should pull implicitly only the default tag (latest)
   472  func (s *DockerRegistrySuite) TestRunImplicitPullWithNoTag(c *check.C) {
   473  	testRequires(c, DaemonIsLinux)
   474  	repo := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
   475  	repoTag1 := fmt.Sprintf("%v:latest", repo)
   476  	repoTag2 := fmt.Sprintf("%v:t1", repo)
   477  	// tag the image and upload it to the private registry
   478  	dockerCmd(c, "tag", "busybox", repoTag1)
   479  	dockerCmd(c, "tag", "busybox", repoTag2)
   480  	dockerCmd(c, "push", repo)
   481  	dockerCmd(c, "rmi", repoTag1)
   482  	dockerCmd(c, "rmi", repoTag2)
   483  
   484  	out, _, err := dockerCmdWithError("run", repo)
   485  	c.Assert(err, check.IsNil)
   486  	c.Assert(out, checker.Contains, fmt.Sprintf("Unable to find image '%s:latest' locally", repo))
   487  
   488  	// There should be only one line for repo, the one with repo:latest
   489  	outImageCmd, _, err := dockerCmdWithError("images", repo)
   490  	splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n")
   491  	c.Assert(splitOutImageCmd, checker.HasLen, 2)
   492  }