github.com/reds/docker@v1.11.2-rc1/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  func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) {
   391  	osPath := os.Getenv("PATH")
   392  	defer os.Setenv("PATH", osPath)
   393  
   394  	workingDir, err := os.Getwd()
   395  	c.Assert(err, checker.IsNil)
   396  	absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
   397  	c.Assert(err, checker.IsNil)
   398  	testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
   399  
   400  	os.Setenv("PATH", testPath)
   401  
   402  	repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
   403  
   404  	tmp, err := ioutil.TempDir("", "integration-cli-")
   405  	c.Assert(err, checker.IsNil)
   406  
   407  	externalAuthConfig := `{ "credsStore": "shell-test" }`
   408  
   409  	configPath := filepath.Join(tmp, "config.json")
   410  	err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
   411  	c.Assert(err, checker.IsNil)
   412  
   413  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
   414  
   415  	b, err := ioutil.ReadFile(configPath)
   416  	c.Assert(err, checker.IsNil)
   417  	c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
   418  
   419  	dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
   420  	dockerCmd(c, "--config", tmp, "push", repoName)
   421  
   422  	dockerCmd(c, "--config", tmp, "pull", repoName)
   423  }