github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/integration-cli/docker_cli_pull_test.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"regexp"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/opencontainers/go-digest"
    11  	"gotest.tools/v3/assert"
    12  	is "gotest.tools/v3/assert/cmp"
    13  	"gotest.tools/v3/skip"
    14  )
    15  
    16  type DockerCLIPullSuite struct {
    17  	ds *DockerSuite
    18  }
    19  
    20  func (s *DockerCLIPullSuite) TearDownTest(ctx context.Context, c *testing.T) {
    21  	s.ds.TearDownTest(ctx, c)
    22  }
    23  
    24  func (s *DockerCLIPullSuite) OnTimeout(c *testing.T) {
    25  	s.ds.OnTimeout(c)
    26  }
    27  
    28  // TestPullFromCentralRegistry pulls an image from the central registry and verifies that the client
    29  // prints all expected output.
    30  func (s *DockerHubPullSuite) TestPullFromCentralRegistry(c *testing.T) {
    31  	testRequires(c, DaemonIsLinux)
    32  	out := s.Cmd(c, "pull", "hello-world")
    33  	defer deleteImages("hello-world")
    34  
    35  	assert.Assert(c, strings.Contains(out, "Using default tag: latest"), "expected the 'latest' tag to be automatically assumed")
    36  	assert.Assert(c, strings.Contains(out, "Pulling from library/hello-world"), "expected the 'library/' prefix to be automatically assumed")
    37  	assert.Assert(c, strings.Contains(out, "Downloaded newer image for hello-world:latest"))
    38  
    39  	matches := regexp.MustCompile(`Digest: (.+)\n`).FindAllStringSubmatch(out, -1)
    40  	assert.Equal(c, len(matches), 1, "expected exactly one image digest in the output")
    41  	assert.Equal(c, len(matches[0]), 2, "unexpected number of submatches for the digest")
    42  	_, err := digest.Parse(matches[0][1])
    43  	assert.NilError(c, err, "invalid digest %q in output", matches[0][1])
    44  
    45  	// We should have a single entry in images.
    46  	img := strings.TrimSpace(s.Cmd(c, "images"))
    47  	splitImg := strings.Split(img, "\n")
    48  	assert.Equal(c, len(splitImg), 2)
    49  	match, _ := regexp.MatchString(`hello-world\s+latest.*?`, splitImg[1])
    50  	assert.Assert(c, match, "invalid output for `docker images` (expected image and tag name)")
    51  }
    52  
    53  // TestPullFromCentralRegistryImplicitRefParts pulls an image from the central registry and verifies
    54  // that pulling the same image with different combinations of implicit elements of the image
    55  // reference (tag, repository, central registry url, ...) doesn't trigger a new pull nor leads to
    56  // multiple images.
    57  func (s *DockerHubPullSuite) TestPullFromCentralRegistryImplicitRefParts(c *testing.T) {
    58  	testRequires(c, DaemonIsLinux)
    59  
    60  	// Pull hello-world from v2
    61  	pullFromV2 := func(ref string) (int, string) {
    62  		out := s.Cmd(c, "pull", "hello-world")
    63  		v1Retries := 0
    64  		for strings.Contains(out, "this image was pulled from a legacy registry") {
    65  			// Some network errors may cause fallbacks to the v1
    66  			// protocol, which would violate the test's assumption
    67  			// that it will get the same images. To make the test
    68  			// more robust against these network glitches, allow a
    69  			// few retries if we end up with a v1 pull.
    70  
    71  			if v1Retries > 2 {
    72  				c.Fatalf("too many v1 fallback incidents when pulling %s", ref)
    73  			}
    74  
    75  			s.Cmd(c, "rmi", ref)
    76  			out = s.Cmd(c, "pull", ref)
    77  
    78  			v1Retries++
    79  		}
    80  
    81  		return v1Retries, out
    82  	}
    83  
    84  	pullFromV2("hello-world")
    85  	defer deleteImages("hello-world")
    86  
    87  	s.Cmd(c, "tag", "hello-world", "hello-world-backup")
    88  
    89  	for _, ref := range []string{
    90  		"hello-world",
    91  		"hello-world:latest",
    92  		"library/hello-world",
    93  		"library/hello-world:latest",
    94  		"docker.io/library/hello-world",
    95  		"index.docker.io/library/hello-world",
    96  	} {
    97  		var out string
    98  		for {
    99  			var v1Retries int
   100  			v1Retries, out = pullFromV2(ref)
   101  
   102  			// Keep repeating the test case until we don't hit a v1
   103  			// fallback case. We won't get the right "Image is up
   104  			// to date" message if the local image was replaced
   105  			// with one pulled from v1.
   106  			if v1Retries == 0 {
   107  				break
   108  			}
   109  			s.Cmd(c, "rmi", ref)
   110  			s.Cmd(c, "tag", "hello-world-backup", "hello-world")
   111  		}
   112  		assert.Assert(c, strings.Contains(out, "Image is up to date for hello-world:latest"))
   113  	}
   114  
   115  	s.Cmd(c, "rmi", "hello-world-backup")
   116  
   117  	// We should have a single entry in images.
   118  	img := strings.TrimSpace(s.Cmd(c, "images"))
   119  	splitImg := strings.Split(img, "\n")
   120  	assert.Equal(c, len(splitImg), 2)
   121  	match, _ := regexp.MatchString(`hello-world\s+latest.*?`, splitImg[1])
   122  	assert.Assert(c, match, "invalid output for `docker images` (expected image and tag name)")
   123  }
   124  
   125  // TestPullScratchNotAllowed verifies that pulling 'scratch' is rejected.
   126  func (s *DockerHubPullSuite) TestPullScratchNotAllowed(c *testing.T) {
   127  	testRequires(c, DaemonIsLinux)
   128  	out, err := s.CmdWithError("pull", "scratch")
   129  	assert.ErrorContains(c, err, "", "expected pull of scratch to fail")
   130  	assert.Assert(c, strings.Contains(out, "'scratch' is a reserved name"))
   131  	assert.Assert(c, !strings.Contains(out, "Pulling repository scratch"))
   132  }
   133  
   134  // TestPullAllTagsFromCentralRegistry pulls using `all-tags` for a given image and verifies that it
   135  // results in more images than a naked pull.
   136  func (s *DockerHubPullSuite) TestPullAllTagsFromCentralRegistry(c *testing.T) {
   137  	// See https://github.com/moby/moby/issues/46632
   138  	skip.If(c, testEnv.UsingSnapshotter, "The image dockercore/engine-pull-all-test-fixture is a hand-made image that contains an error in the manifest, the size is reported as 424 but its real size is 524, containerd fails to pull it because it checks that the sizes reported are right")
   139  	testRequires(c, DaemonIsLinux)
   140  	s.Cmd(c, "pull", "dockercore/engine-pull-all-test-fixture")
   141  	outImageCmd := s.Cmd(c, "images", "dockercore/engine-pull-all-test-fixture")
   142  	splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n")
   143  	assert.Equal(c, len(splitOutImageCmd), 2)
   144  
   145  	s.Cmd(c, "pull", "--all-tags=true", "dockercore/engine-pull-all-test-fixture")
   146  	outImageAllTagCmd := s.Cmd(c, "images", "dockercore/engine-pull-all-test-fixture")
   147  	linesCount := strings.Count(outImageAllTagCmd, "\n")
   148  	assert.Assert(c, linesCount > 2, "pulling all tags should provide more than two images, got %s", outImageAllTagCmd)
   149  
   150  	// Verify that the line for 'dockercore/engine-pull-all-test-fixture:latest' is left unchanged.
   151  	var latestLine string
   152  	for _, line := range strings.Split(outImageAllTagCmd, "\n") {
   153  		if strings.HasPrefix(line, "dockercore/engine-pull-all-test-fixture") && strings.Contains(line, "latest") {
   154  			latestLine = line
   155  			break
   156  		}
   157  	}
   158  	assert.Assert(c, latestLine != "", "no entry for dockercore/engine-pull-all-test-fixture:latest found after pulling all tags")
   159  
   160  	splitLatest := strings.Fields(latestLine)
   161  	splitCurrent := strings.Fields(splitOutImageCmd[1])
   162  
   163  	// Clear relative creation times, since these can easily change between
   164  	// two invocations of "docker images". Without this, the test can fail
   165  	// like this:
   166  	// ... obtained []string = []string{"busybox", "latest", "d9551b4026f0", "27", "minutes", "ago", "1.113", "MB"}
   167  	// ... expected []string = []string{"busybox", "latest", "d9551b4026f0", "26", "minutes", "ago", "1.113", "MB"}
   168  	splitLatest[3] = ""
   169  	splitLatest[4] = ""
   170  	splitLatest[5] = ""
   171  	splitCurrent[3] = ""
   172  	splitCurrent[4] = ""
   173  	splitCurrent[5] = ""
   174  
   175  	assert.Assert(c, is.DeepEqual(splitLatest, splitCurrent), "dockercore/engine-pull-all-test-fixture:latest was changed after pulling all tags")
   176  }
   177  
   178  // TestPullClientDisconnect kills the client during a pull operation and verifies that the operation
   179  // gets cancelled.
   180  //
   181  // Ref: docker/docker#15589
   182  func (s *DockerHubPullSuite) TestPullClientDisconnect(c *testing.T) {
   183  	testRequires(c, DaemonIsLinux)
   184  	const imgRepo = "hello-world:latest"
   185  
   186  	pullCmd := s.MakeCmd("pull", imgRepo)
   187  	stdout, err := pullCmd.StdoutPipe()
   188  	assert.NilError(c, err)
   189  	err = pullCmd.Start()
   190  	assert.NilError(c, err)
   191  	go pullCmd.Wait()
   192  
   193  	// Cancel as soon as we get some output.
   194  	buf := make([]byte, 10)
   195  	_, err = stdout.Read(buf)
   196  	assert.NilError(c, err)
   197  
   198  	err = pullCmd.Process.Kill()
   199  	assert.NilError(c, err)
   200  
   201  	time.Sleep(2 * time.Second)
   202  	_, err = s.CmdWithError("inspect", imgRepo)
   203  	assert.ErrorContains(c, err, "", "image was pulled after client disconnected")
   204  }
   205  
   206  // Regression test for https://github.com/docker/docker/issues/26429
   207  func (s *DockerCLIPullSuite) TestPullLinuxImageFailsOnWindows(c *testing.T) {
   208  	testRequires(c, DaemonIsWindows, Network)
   209  	_, _, err := dockerCmdWithError("pull", "ubuntu")
   210  
   211  	errorMessage := "no matching manifest for windows"
   212  	if testEnv.UsingSnapshotter() {
   213  		errorMessage = "no match for platform in manifest"
   214  	}
   215  
   216  	assert.ErrorContains(c, err, errorMessage)
   217  }
   218  
   219  // Regression test for https://github.com/docker/docker/issues/28892
   220  func (s *DockerCLIPullSuite) TestPullWindowsImageFailsOnLinux(c *testing.T) {
   221  	testRequires(c, DaemonIsLinux, Network)
   222  	_, _, err := dockerCmdWithError("pull", "mcr.microsoft.com/windows/servercore:ltsc2022")
   223  
   224  	errorMessage := "no matching manifest for linux"
   225  	if testEnv.UsingSnapshotter() {
   226  		errorMessage = "no match for platform in manifest"
   227  	}
   228  
   229  	assert.ErrorContains(c, err, errorMessage)
   230  }