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