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