github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/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  func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
   283  	testRequires(c, NotArm)
   284  	pushDigest, err := setupImage(c)
   285  	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
   286  
   287  	// Inject a manifest list into the registry
   288  	manifestList := &manifestlist.ManifestList{
   289  		Versioned: manifest.Versioned{
   290  			SchemaVersion: 2,
   291  			MediaType:     manifestlist.MediaTypeManifestList,
   292  		},
   293  		Manifests: []manifestlist.ManifestDescriptor{
   294  			{
   295  				Descriptor: distribution.Descriptor{
   296  					Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
   297  					Size:      3253,
   298  					MediaType: schema2.MediaTypeManifest,
   299  				},
   300  				Platform: manifestlist.PlatformSpec{
   301  					Architecture: "bogus_arch",
   302  					OS:           "bogus_os",
   303  				},
   304  			},
   305  			{
   306  				Descriptor: distribution.Descriptor{
   307  					Digest:    pushDigest,
   308  					Size:      3253,
   309  					MediaType: schema2.MediaTypeManifest,
   310  				},
   311  				Platform: manifestlist.PlatformSpec{
   312  					Architecture: runtime.GOARCH,
   313  					OS:           runtime.GOOS,
   314  				},
   315  			},
   316  		},
   317  	}
   318  
   319  	manifestListJSON, err := json.MarshalIndent(manifestList, "", "   ")
   320  	c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list"))
   321  
   322  	manifestListDigest := digest.FromBytes(manifestListJSON)
   323  	hexDigest := manifestListDigest.Hex()
   324  
   325  	registryV2Path := filepath.Join(s.reg.dir, "docker", "registry", "v2")
   326  
   327  	// Write manifest list to blob store
   328  	blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest)
   329  	err = os.MkdirAll(blobDir, 0755)
   330  	c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir"))
   331  	blobPath := filepath.Join(blobDir, "data")
   332  	err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644)
   333  	c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list"))
   334  
   335  	// Add to revision store
   336  	revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest)
   337  	err = os.Mkdir(revisionDir, 0755)
   338  	c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir"))
   339  	revisionPath := filepath.Join(revisionDir, "link")
   340  	err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644)
   341  	c.Assert(err, checker.IsNil, check.Commentf("error writing revision link"))
   342  
   343  	// Update tag
   344  	tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link")
   345  	err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644)
   346  	c.Assert(err, checker.IsNil, check.Commentf("error writing tag link"))
   347  
   348  	// Verify that the image can be pulled through the manifest list.
   349  	out, _ := dockerCmd(c, "pull", repoName)
   350  
   351  	// The pull output includes "Digest: <digest>", so find that
   352  	matches := digestRegex.FindStringSubmatch(out)
   353  	c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out))
   354  	pullDigest := matches[1]
   355  
   356  	// Make sure the pushed and pull digests match
   357  	c.Assert(manifestListDigest.String(), checker.Equals, pullDigest)
   358  
   359  	// Was the image actually created?
   360  	dockerCmd(c, "inspect", repoName)
   361  
   362  	dockerCmd(c, "rmi", repoName)
   363  }
   364  
   365  func (s *DockerRegistryAuthSuite) TestPullWithExternalAuth(c *check.C) {
   366  	osPath := os.Getenv("PATH")
   367  	defer os.Setenv("PATH", osPath)
   368  
   369  	workingDir, err := os.Getwd()
   370  	c.Assert(err, checker.IsNil)
   371  	absolute, err := filepath.Abs(filepath.Join(workingDir, "fixtures", "auth"))
   372  	c.Assert(err, checker.IsNil)
   373  	testPath := fmt.Sprintf("%s%c%s", osPath, filepath.ListSeparator, absolute)
   374  
   375  	os.Setenv("PATH", testPath)
   376  
   377  	repoName := fmt.Sprintf("%v/dockercli/busybox:authtest", privateRegistryURL)
   378  
   379  	tmp, err := ioutil.TempDir("", "integration-cli-")
   380  	c.Assert(err, checker.IsNil)
   381  
   382  	externalAuthConfig := `{ "credsStore": "shell-test" }`
   383  
   384  	configPath := filepath.Join(tmp, "config.json")
   385  	err = ioutil.WriteFile(configPath, []byte(externalAuthConfig), 0644)
   386  	c.Assert(err, checker.IsNil)
   387  
   388  	dockerCmd(c, "--config", tmp, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
   389  
   390  	b, err := ioutil.ReadFile(configPath)
   391  	c.Assert(err, checker.IsNil)
   392  	c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
   393  
   394  	dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
   395  	dockerCmd(c, "--config", tmp, "push", repoName)
   396  
   397  	dockerCmd(c, "--config", tmp, "pull", repoName)
   398  }