github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration-cli/docker_cli_pull_local_test.go (about)

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