github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration-cli/docker_cli_save_load_test.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"reflect"
    13  	"regexp"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/docker/docker/integration-cli/checker"
    19  	"github.com/docker/docker/integration-cli/cli/build"
    20  	"github.com/go-check/check"
    21  	"github.com/opencontainers/go-digest"
    22  	"gotest.tools/assert"
    23  	is "gotest.tools/assert/cmp"
    24  	"gotest.tools/icmd"
    25  )
    26  
    27  // save a repo using gz compression and try to load it using stdout
    28  func (s *DockerSuite) TestSaveXzAndLoadRepoStdout(c *check.C) {
    29  	testRequires(c, DaemonIsLinux)
    30  	name := "test-save-xz-and-load-repo-stdout"
    31  	dockerCmd(c, "run", "--name", name, "busybox", "true")
    32  
    33  	repoName := "foobar-save-load-test-xz-gz"
    34  	out, _ := dockerCmd(c, "commit", name, repoName)
    35  
    36  	dockerCmd(c, "inspect", repoName)
    37  
    38  	repoTarball, err := RunCommandPipelineWithOutput(
    39  		exec.Command(dockerBinary, "save", repoName),
    40  		exec.Command("xz", "-c"),
    41  		exec.Command("gzip", "-c"))
    42  	assert.NilError(c, err, "failed to save repo: %v %v", out, err)
    43  	deleteImages(repoName)
    44  
    45  	icmd.RunCmd(icmd.Cmd{
    46  		Command: []string{dockerBinary, "load"},
    47  		Stdin:   strings.NewReader(repoTarball),
    48  	}).Assert(c, icmd.Expected{
    49  		ExitCode: 1,
    50  	})
    51  
    52  	after, _, err := dockerCmdWithError("inspect", repoName)
    53  	assert.ErrorContains(c, err, "", "the repo should not exist: %v", after)
    54  }
    55  
    56  // save a repo using xz+gz compression and try to load it using stdout
    57  func (s *DockerSuite) TestSaveXzGzAndLoadRepoStdout(c *check.C) {
    58  	testRequires(c, DaemonIsLinux)
    59  	name := "test-save-xz-gz-and-load-repo-stdout"
    60  	dockerCmd(c, "run", "--name", name, "busybox", "true")
    61  
    62  	repoName := "foobar-save-load-test-xz-gz"
    63  	dockerCmd(c, "commit", name, repoName)
    64  
    65  	dockerCmd(c, "inspect", repoName)
    66  
    67  	out, err := RunCommandPipelineWithOutput(
    68  		exec.Command(dockerBinary, "save", repoName),
    69  		exec.Command("xz", "-c"),
    70  		exec.Command("gzip", "-c"))
    71  	assert.NilError(c, err, "failed to save repo: %v %v", out, err)
    72  
    73  	deleteImages(repoName)
    74  
    75  	icmd.RunCmd(icmd.Cmd{
    76  		Command: []string{dockerBinary, "load"},
    77  		Stdin:   strings.NewReader(out),
    78  	}).Assert(c, icmd.Expected{
    79  		ExitCode: 1,
    80  	})
    81  
    82  	after, _, err := dockerCmdWithError("inspect", repoName)
    83  	assert.ErrorContains(c, err, "", "the repo should not exist: %v", after)
    84  }
    85  
    86  func (s *DockerSuite) TestSaveSingleTag(c *check.C) {
    87  	testRequires(c, DaemonIsLinux)
    88  	repoName := "foobar-save-single-tag-test"
    89  	dockerCmd(c, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName))
    90  
    91  	out, _ := dockerCmd(c, "images", "-q", "--no-trunc", repoName)
    92  	cleanedImageID := strings.TrimSpace(out)
    93  
    94  	out, err := RunCommandPipelineWithOutput(
    95  		exec.Command(dockerBinary, "save", fmt.Sprintf("%v:latest", repoName)),
    96  		exec.Command("tar", "t"),
    97  		exec.Command("grep", "-E", fmt.Sprintf("(^repositories$|%v)", cleanedImageID)))
    98  	assert.NilError(c, err, "failed to save repo with image ID and 'repositories' file: %s, %v", out, err)
    99  }
   100  
   101  func (s *DockerSuite) TestSaveCheckTimes(c *check.C) {
   102  	testRequires(c, DaemonIsLinux)
   103  	repoName := "busybox:latest"
   104  	out, _ := dockerCmd(c, "inspect", repoName)
   105  	var data []struct {
   106  		ID      string
   107  		Created time.Time
   108  	}
   109  	err := json.Unmarshal([]byte(out), &data)
   110  	assert.NilError(c, err, "failed to marshal from %q: err %v", repoName, err)
   111  	assert.Assert(c, len(data) != 0, "failed to marshal the data from %q", repoName)
   112  	tarTvTimeFormat := "2006-01-02 15:04"
   113  	out, err = RunCommandPipelineWithOutput(
   114  		exec.Command(dockerBinary, "save", repoName),
   115  		exec.Command("tar", "tv"),
   116  		exec.Command("grep", "-E", fmt.Sprintf("%s %s", data[0].Created.Format(tarTvTimeFormat), digest.Digest(data[0].ID).Hex())))
   117  	assert.NilError(c, err, "failed to save repo with image ID and 'repositories' file: %s, %v", out, err)
   118  }
   119  
   120  func (s *DockerSuite) TestSaveImageId(c *check.C) {
   121  	testRequires(c, DaemonIsLinux)
   122  	repoName := "foobar-save-image-id-test"
   123  	dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName))
   124  
   125  	out, _ := dockerCmd(c, "images", "-q", "--no-trunc", repoName)
   126  	cleanedLongImageID := strings.TrimPrefix(strings.TrimSpace(out), "sha256:")
   127  
   128  	out, _ = dockerCmd(c, "images", "-q", repoName)
   129  	cleanedShortImageID := strings.TrimSpace(out)
   130  
   131  	// Make sure IDs are not empty
   132  	c.Assert(cleanedLongImageID, checker.Not(check.Equals), "", check.Commentf("Id should not be empty."))
   133  	c.Assert(cleanedShortImageID, checker.Not(check.Equals), "", check.Commentf("Id should not be empty."))
   134  
   135  	saveCmd := exec.Command(dockerBinary, "save", cleanedShortImageID)
   136  	tarCmd := exec.Command("tar", "t")
   137  
   138  	var err error
   139  	tarCmd.Stdin, err = saveCmd.StdoutPipe()
   140  	c.Assert(err, checker.IsNil, check.Commentf("cannot set stdout pipe for tar: %v", err))
   141  	grepCmd := exec.Command("grep", cleanedLongImageID)
   142  	grepCmd.Stdin, err = tarCmd.StdoutPipe()
   143  	c.Assert(err, checker.IsNil, check.Commentf("cannot set stdout pipe for grep: %v", err))
   144  
   145  	c.Assert(tarCmd.Start(), checker.IsNil, check.Commentf("tar failed with error: %v", err))
   146  	c.Assert(saveCmd.Start(), checker.IsNil, check.Commentf("docker save failed with error: %v", err))
   147  	defer func() {
   148  		saveCmd.Wait()
   149  		tarCmd.Wait()
   150  		dockerCmd(c, "rmi", repoName)
   151  	}()
   152  
   153  	out, _, err = runCommandWithOutput(grepCmd)
   154  
   155  	c.Assert(err, checker.IsNil, check.Commentf("failed to save repo with image ID: %s, %v", out, err))
   156  }
   157  
   158  // save a repo and try to load it using flags
   159  func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) {
   160  	testRequires(c, DaemonIsLinux)
   161  	name := "test-save-and-load-repo-flags"
   162  	dockerCmd(c, "run", "--name", name, "busybox", "true")
   163  
   164  	repoName := "foobar-save-load-test"
   165  
   166  	deleteImages(repoName)
   167  	dockerCmd(c, "commit", name, repoName)
   168  
   169  	before, _ := dockerCmd(c, "inspect", repoName)
   170  
   171  	out, err := RunCommandPipelineWithOutput(
   172  		exec.Command(dockerBinary, "save", repoName),
   173  		exec.Command(dockerBinary, "load"))
   174  	assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err)
   175  
   176  	after, _ := dockerCmd(c, "inspect", repoName)
   177  	assert.Equal(c, before, after, "inspect is not the same after a save / load")
   178  }
   179  
   180  func (s *DockerSuite) TestSaveWithNoExistImage(c *check.C) {
   181  	testRequires(c, DaemonIsLinux)
   182  
   183  	imgName := "foobar-non-existing-image"
   184  
   185  	out, _, err := dockerCmdWithError("save", "-o", "test-img.tar", imgName)
   186  	assert.ErrorContains(c, err, "", "save image should fail for non-existing image")
   187  	assert.Assert(c, strings.Contains(out, fmt.Sprintf("No such image: %s", imgName)))
   188  }
   189  
   190  func (s *DockerSuite) TestSaveMultipleNames(c *check.C) {
   191  	testRequires(c, DaemonIsLinux)
   192  	repoName := "foobar-save-multi-name-test"
   193  
   194  	// Make one image
   195  	dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v-one:latest", repoName))
   196  
   197  	// Make two images
   198  	dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName))
   199  
   200  	out, err := RunCommandPipelineWithOutput(
   201  		exec.Command(dockerBinary, "save", fmt.Sprintf("%v-one", repoName), fmt.Sprintf("%v-two:latest", repoName)),
   202  		exec.Command("tar", "xO", "repositories"),
   203  		exec.Command("grep", "-q", "-E", "(-one|-two)"),
   204  	)
   205  	assert.NilError(c, err, "failed to save multiple repos: %s, %v", out, err)
   206  }
   207  
   208  func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) {
   209  	testRequires(c, DaemonIsLinux)
   210  	makeImage := func(from string, tag string) string {
   211  		var (
   212  			out string
   213  		)
   214  		out, _ = dockerCmd(c, "run", "-d", from, "true")
   215  		cleanedContainerID := strings.TrimSpace(out)
   216  
   217  		out, _ = dockerCmd(c, "commit", cleanedContainerID, tag)
   218  		imageID := strings.TrimSpace(out)
   219  		return imageID
   220  	}
   221  
   222  	repoName := "foobar-save-multi-images-test"
   223  	tagFoo := repoName + ":foo"
   224  	tagBar := repoName + ":bar"
   225  
   226  	idFoo := makeImage("busybox:latest", tagFoo)
   227  	idBar := makeImage("busybox:latest", tagBar)
   228  
   229  	deleteImages(repoName)
   230  
   231  	// create the archive
   232  	out, err := RunCommandPipelineWithOutput(
   233  		exec.Command(dockerBinary, "save", repoName, "busybox:latest"),
   234  		exec.Command("tar", "t"))
   235  	assert.NilError(c, err, "failed to save multiple images: %s, %v", out, err)
   236  
   237  	lines := strings.Split(strings.TrimSpace(out), "\n")
   238  	var actual []string
   239  	for _, l := range lines {
   240  		if regexp.MustCompile("^[a-f0-9]{64}\\.json$").Match([]byte(l)) {
   241  			actual = append(actual, strings.TrimSuffix(l, ".json"))
   242  		}
   243  	}
   244  
   245  	// make the list of expected layers
   246  	out = inspectField(c, "busybox:latest", "Id")
   247  	expected := []string{strings.TrimSpace(out), idFoo, idBar}
   248  
   249  	// prefixes are not in tar
   250  	for i := range expected {
   251  		expected[i] = digest.Digest(expected[i]).Hex()
   252  	}
   253  
   254  	sort.Strings(actual)
   255  	sort.Strings(expected)
   256  	assert.Assert(c, is.DeepEqual(actual, expected), "archive does not contains the right layers: got %v, expected %v, output: %q", actual, expected, out)
   257  }
   258  
   259  // Issue #6722 #5892 ensure directories are included in changes
   260  func (s *DockerSuite) TestSaveDirectoryPermissions(c *check.C) {
   261  	testRequires(c, DaemonIsLinux)
   262  	layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
   263  	layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
   264  
   265  	name := "save-directory-permissions"
   266  	tmpDir, err := ioutil.TempDir("", "save-layers-with-directories")
   267  	c.Assert(err, checker.IsNil, check.Commentf("failed to create temporary directory: %s", err))
   268  	extractionDirectory := filepath.Join(tmpDir, "image-extraction-dir")
   269  	os.Mkdir(extractionDirectory, 0777)
   270  
   271  	defer os.RemoveAll(tmpDir)
   272  	buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox
   273  	RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a
   274  	RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`))
   275  
   276  	out, err := RunCommandPipelineWithOutput(
   277  		exec.Command(dockerBinary, "save", name),
   278  		exec.Command("tar", "-xf", "-", "-C", extractionDirectory),
   279  	)
   280  	assert.NilError(c, err, "failed to save and extract image: %s", out)
   281  
   282  	dirs, err := ioutil.ReadDir(extractionDirectory)
   283  	assert.NilError(c, err, "failed to get a listing of the layer directories: %s", err)
   284  
   285  	found := false
   286  	for _, entry := range dirs {
   287  		var entriesSansDev []string
   288  		if entry.IsDir() {
   289  			layerPath := filepath.Join(extractionDirectory, entry.Name(), "layer.tar")
   290  
   291  			f, err := os.Open(layerPath)
   292  			assert.NilError(c, err, "failed to open %s: %s", layerPath, err)
   293  
   294  			defer f.Close()
   295  
   296  			entries, err := listTar(f)
   297  			for _, e := range entries {
   298  				if !strings.Contains(e, "dev/") {
   299  					entriesSansDev = append(entriesSansDev, e)
   300  				}
   301  			}
   302  			assert.NilError(c, err, "encountered error while listing tar entries: %s", err)
   303  
   304  			if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) {
   305  				found = true
   306  				break
   307  			}
   308  		}
   309  	}
   310  
   311  	assert.Assert(c, found, "failed to find the layer with the right content listing")
   312  }
   313  
   314  func listTar(f io.Reader) ([]string, error) {
   315  	tr := tar.NewReader(f)
   316  	var entries []string
   317  
   318  	for {
   319  		th, err := tr.Next()
   320  		if err == io.EOF {
   321  			// end of tar archive
   322  			return entries, nil
   323  		}
   324  		if err != nil {
   325  			return entries, err
   326  		}
   327  		entries = append(entries, th.Name)
   328  	}
   329  }
   330  
   331  // Test loading a weird image where one of the layers is of zero size.
   332  // The layer.tar file is actually zero bytes, no padding or anything else.
   333  // See issue: 18170
   334  func (s *DockerSuite) TestLoadZeroSizeLayer(c *check.C) {
   335  	// this will definitely not work if using remote daemon
   336  	// very weird test
   337  	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
   338  
   339  	dockerCmd(c, "load", "-i", "testdata/emptyLayer.tar")
   340  }
   341  
   342  func (s *DockerSuite) TestSaveLoadParents(c *check.C) {
   343  	testRequires(c, DaemonIsLinux)
   344  
   345  	makeImage := func(from string, addfile string) string {
   346  		var (
   347  			out string
   348  		)
   349  		out, _ = dockerCmd(c, "run", "-d", from, "touch", addfile)
   350  		cleanedContainerID := strings.TrimSpace(out)
   351  
   352  		out, _ = dockerCmd(c, "commit", cleanedContainerID)
   353  		imageID := strings.TrimSpace(out)
   354  
   355  		dockerCmd(c, "rm", "-f", cleanedContainerID)
   356  		return imageID
   357  	}
   358  
   359  	idFoo := makeImage("busybox", "foo")
   360  	idBar := makeImage(idFoo, "bar")
   361  
   362  	tmpDir, err := ioutil.TempDir("", "save-load-parents")
   363  	assert.NilError(c, err)
   364  	defer os.RemoveAll(tmpDir)
   365  
   366  	c.Log("tmpdir", tmpDir)
   367  
   368  	outfile := filepath.Join(tmpDir, "out.tar")
   369  
   370  	dockerCmd(c, "save", "-o", outfile, idBar, idFoo)
   371  	dockerCmd(c, "rmi", idBar)
   372  	dockerCmd(c, "load", "-i", outfile)
   373  
   374  	inspectOut := inspectField(c, idBar, "Parent")
   375  	assert.Equal(c, inspectOut, idFoo)
   376  
   377  	inspectOut = inspectField(c, idFoo, "Parent")
   378  	assert.Equal(c, inspectOut, "")
   379  }
   380  
   381  func (s *DockerSuite) TestSaveLoadNoTag(c *check.C) {
   382  	testRequires(c, DaemonIsLinux)
   383  
   384  	name := "saveloadnotag"
   385  
   386  	buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENV foo=bar"))
   387  	id := inspectField(c, name, "Id")
   388  
   389  	// Test to make sure that save w/o name just shows imageID during load
   390  	out, err := RunCommandPipelineWithOutput(
   391  		exec.Command(dockerBinary, "save", id),
   392  		exec.Command(dockerBinary, "load"))
   393  	assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err)
   394  
   395  	// Should not show 'name' but should show the image ID during the load
   396  	c.Assert(out, checker.Not(checker.Contains), "Loaded image: ")
   397  	c.Assert(out, checker.Contains, "Loaded image ID:")
   398  	c.Assert(out, checker.Contains, id)
   399  
   400  	// Test to make sure that save by name shows that name during load
   401  	out, err = RunCommandPipelineWithOutput(
   402  		exec.Command(dockerBinary, "save", name),
   403  		exec.Command(dockerBinary, "load"))
   404  	assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err)
   405  
   406  	c.Assert(out, checker.Contains, "Loaded image: "+name+":latest")
   407  	c.Assert(out, checker.Not(checker.Contains), "Loaded image ID:")
   408  }