github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/pkg/archive/archive_test.go (about)

     1  package archive
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  var tmp string
    22  
    23  func init() {
    24  	tmp = "/tmp/"
    25  	if runtime.GOOS == "windows" {
    26  		tmp = os.Getenv("TEMP") + `\`
    27  	}
    28  }
    29  
    30  var defaultArchiver = NewDefaultArchiver()
    31  
    32  func defaultTarUntar(src, dst string) error {
    33  	return defaultArchiver.TarUntar(src, dst)
    34  }
    35  
    36  func defaultUntarPath(src, dst string) error {
    37  	return defaultArchiver.UntarPath(src, dst)
    38  }
    39  
    40  func defaultCopyFileWithTar(src, dst string) (err error) {
    41  	return defaultArchiver.CopyFileWithTar(src, dst)
    42  }
    43  
    44  func defaultCopyWithTar(src, dst string) error {
    45  	return defaultArchiver.CopyWithTar(src, dst)
    46  }
    47  
    48  func TestIsArchivePathDir(t *testing.T) {
    49  	cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir")
    50  	output, err := cmd.CombinedOutput()
    51  	if err != nil {
    52  		t.Fatalf("Fail to create an archive file for test : %s.", output)
    53  	}
    54  	if IsArchivePath(tmp + "archivedir") {
    55  		t.Fatalf("Incorrectly recognised directory as an archive")
    56  	}
    57  }
    58  
    59  func TestIsArchivePathInvalidFile(t *testing.T) {
    60  	cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz")
    61  	output, err := cmd.CombinedOutput()
    62  	if err != nil {
    63  		t.Fatalf("Fail to create an archive file for test : %s.", output)
    64  	}
    65  	if IsArchivePath(tmp + "archive") {
    66  		t.Fatalf("Incorrectly recognised invalid tar path as archive")
    67  	}
    68  	if IsArchivePath(tmp + "archive.gz") {
    69  		t.Fatalf("Incorrectly recognised invalid compressed tar path as archive")
    70  	}
    71  }
    72  
    73  func TestIsArchivePathTar(t *testing.T) {
    74  	var whichTar string
    75  	if runtime.GOOS == "solaris" {
    76  		whichTar = "gtar"
    77  	} else {
    78  		whichTar = "tar"
    79  	}
    80  	cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar)
    81  	cmd := exec.Command("sh", "-c", cmdStr)
    82  	output, err := cmd.CombinedOutput()
    83  	if err != nil {
    84  		t.Fatalf("Fail to create an archive file for test : %s.", output)
    85  	}
    86  	if !IsArchivePath(tmp + "/archive") {
    87  		t.Fatalf("Did not recognise valid tar path as archive")
    88  	}
    89  	if !IsArchivePath(tmp + "archive.gz") {
    90  		t.Fatalf("Did not recognise valid compressed tar path as archive")
    91  	}
    92  }
    93  
    94  func testDecompressStream(t *testing.T, ext, compressCommand string) {
    95  	cmd := exec.Command("sh", "-c",
    96  		fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand))
    97  	output, err := cmd.CombinedOutput()
    98  	if err != nil {
    99  		t.Fatalf("Failed to create an archive file for test : %s.", output)
   100  	}
   101  	filename := "archive." + ext
   102  	archive, err := os.Open(tmp + filename)
   103  	if err != nil {
   104  		t.Fatalf("Failed to open file %s: %v", filename, err)
   105  	}
   106  	defer archive.Close()
   107  
   108  	r, err := DecompressStream(archive)
   109  	if err != nil {
   110  		t.Fatalf("Failed to decompress %s: %v", filename, err)
   111  	}
   112  	if _, err = ioutil.ReadAll(r); err != nil {
   113  		t.Fatalf("Failed to read the decompressed stream: %v ", err)
   114  	}
   115  	if err = r.Close(); err != nil {
   116  		t.Fatalf("Failed to close the decompressed stream: %v ", err)
   117  	}
   118  }
   119  
   120  func TestDecompressStreamGzip(t *testing.T) {
   121  	testDecompressStream(t, "gz", "gzip -f")
   122  }
   123  
   124  func TestDecompressStreamBzip2(t *testing.T) {
   125  	testDecompressStream(t, "bz2", "bzip2 -f")
   126  }
   127  
   128  func TestDecompressStreamXz(t *testing.T) {
   129  	if runtime.GOOS == "windows" {
   130  		t.Skip("Xz not present in msys2")
   131  	}
   132  	testDecompressStream(t, "xz", "xz -f")
   133  }
   134  
   135  func TestCompressStreamXzUnsupported(t *testing.T) {
   136  	dest, err := os.Create(tmp + "dest")
   137  	if err != nil {
   138  		t.Fatalf("Fail to create the destination file")
   139  	}
   140  	defer dest.Close()
   141  
   142  	_, err = CompressStream(dest, Xz)
   143  	if err == nil {
   144  		t.Fatalf("Should fail as xz is unsupported for compression format.")
   145  	}
   146  }
   147  
   148  func TestCompressStreamBzip2Unsupported(t *testing.T) {
   149  	dest, err := os.Create(tmp + "dest")
   150  	if err != nil {
   151  		t.Fatalf("Fail to create the destination file")
   152  	}
   153  	defer dest.Close()
   154  
   155  	_, err = CompressStream(dest, Xz)
   156  	if err == nil {
   157  		t.Fatalf("Should fail as xz is unsupported for compression format.")
   158  	}
   159  }
   160  
   161  func TestCompressStreamInvalid(t *testing.T) {
   162  	dest, err := os.Create(tmp + "dest")
   163  	if err != nil {
   164  		t.Fatalf("Fail to create the destination file")
   165  	}
   166  	defer dest.Close()
   167  
   168  	_, err = CompressStream(dest, -1)
   169  	if err == nil {
   170  		t.Fatalf("Should fail as xz is unsupported for compression format.")
   171  	}
   172  }
   173  
   174  func TestExtensionInvalid(t *testing.T) {
   175  	compression := Compression(-1)
   176  	output := compression.Extension()
   177  	if output != "" {
   178  		t.Fatalf("The extension of an invalid compression should be an empty string.")
   179  	}
   180  }
   181  
   182  func TestExtensionUncompressed(t *testing.T) {
   183  	compression := Uncompressed
   184  	output := compression.Extension()
   185  	if output != "tar" {
   186  		t.Fatalf("The extension of an uncompressed archive should be 'tar'.")
   187  	}
   188  }
   189  func TestExtensionBzip2(t *testing.T) {
   190  	compression := Bzip2
   191  	output := compression.Extension()
   192  	if output != "tar.bz2" {
   193  		t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
   194  	}
   195  }
   196  func TestExtensionGzip(t *testing.T) {
   197  	compression := Gzip
   198  	output := compression.Extension()
   199  	if output != "tar.gz" {
   200  		t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'")
   201  	}
   202  }
   203  func TestExtensionXz(t *testing.T) {
   204  	compression := Xz
   205  	output := compression.Extension()
   206  	if output != "tar.xz" {
   207  		t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'")
   208  	}
   209  }
   210  
   211  func TestCmdStreamLargeStderr(t *testing.T) {
   212  	cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
   213  	out, _, err := cmdStream(cmd, nil)
   214  	if err != nil {
   215  		t.Fatalf("Failed to start command: %s", err)
   216  	}
   217  	errCh := make(chan error)
   218  	go func() {
   219  		_, err := io.Copy(ioutil.Discard, out)
   220  		errCh <- err
   221  	}()
   222  	select {
   223  	case err := <-errCh:
   224  		if err != nil {
   225  			t.Fatalf("Command should not have failed (err=%.100s...)", err)
   226  		}
   227  	case <-time.After(5 * time.Second):
   228  		t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
   229  	}
   230  }
   231  
   232  func TestCmdStreamBad(t *testing.T) {
   233  	// TODO Windows: Figure out why this is failing in CI but not locally
   234  	if runtime.GOOS == "windows" {
   235  		t.Skip("Failing on Windows CI machines")
   236  	}
   237  	badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
   238  	out, _, err := cmdStream(badCmd, nil)
   239  	if err != nil {
   240  		t.Fatalf("Failed to start command: %s", err)
   241  	}
   242  	if output, err := ioutil.ReadAll(out); err == nil {
   243  		t.Fatalf("Command should have failed")
   244  	} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
   245  		t.Fatalf("Wrong error value (%s)", err)
   246  	} else if s := string(output); s != "hello\n" {
   247  		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
   248  	}
   249  }
   250  
   251  func TestCmdStreamGood(t *testing.T) {
   252  	cmd := exec.Command("sh", "-c", "echo hello; exit 0")
   253  	out, _, err := cmdStream(cmd, nil)
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	if output, err := ioutil.ReadAll(out); err != nil {
   258  		t.Fatalf("Command should not have failed (err=%s)", err)
   259  	} else if s := string(output); s != "hello\n" {
   260  		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
   261  	}
   262  }
   263  
   264  func TestUntarPathWithInvalidDest(t *testing.T) {
   265  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	defer os.RemoveAll(tempFolder)
   270  	invalidDestFolder := filepath.Join(tempFolder, "invalidDest")
   271  	// Create a src file
   272  	srcFile := filepath.Join(tempFolder, "src")
   273  	tarFile := filepath.Join(tempFolder, "src.tar")
   274  	os.Create(srcFile)
   275  	os.Create(invalidDestFolder) // being a file (not dir) should cause an error
   276  
   277  	// Translate back to Unix semantics as next exec.Command is run under sh
   278  	srcFileU := srcFile
   279  	tarFileU := tarFile
   280  	if runtime.GOOS == "windows" {
   281  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   282  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   283  	}
   284  
   285  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   286  	_, err = cmd.CombinedOutput()
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  
   291  	err = defaultUntarPath(tarFile, invalidDestFolder)
   292  	if err == nil {
   293  		t.Fatalf("UntarPath with invalid destination path should throw an error.")
   294  	}
   295  }
   296  
   297  func TestUntarPathWithInvalidSrc(t *testing.T) {
   298  	dest, err := ioutil.TempDir("", "docker-archive-test")
   299  	if err != nil {
   300  		t.Fatalf("Fail to create the destination file")
   301  	}
   302  	defer os.RemoveAll(dest)
   303  	err = defaultUntarPath("/invalid/path", dest)
   304  	if err == nil {
   305  		t.Fatalf("UntarPath with invalid src path should throw an error.")
   306  	}
   307  }
   308  
   309  func TestUntarPath(t *testing.T) {
   310  	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	defer os.RemoveAll(tmpFolder)
   315  	srcFile := filepath.Join(tmpFolder, "src")
   316  	tarFile := filepath.Join(tmpFolder, "src.tar")
   317  	os.Create(filepath.Join(tmpFolder, "src"))
   318  
   319  	destFolder := filepath.Join(tmpFolder, "dest")
   320  	err = os.MkdirAll(destFolder, 0740)
   321  	if err != nil {
   322  		t.Fatalf("Fail to create the destination file")
   323  	}
   324  
   325  	// Translate back to Unix semantics as next exec.Command is run under sh
   326  	srcFileU := srcFile
   327  	tarFileU := tarFile
   328  	if runtime.GOOS == "windows" {
   329  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   330  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   331  	}
   332  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   333  	_, err = cmd.CombinedOutput()
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  
   338  	err = defaultUntarPath(tarFile, destFolder)
   339  	if err != nil {
   340  		t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
   341  	}
   342  	expectedFile := filepath.Join(destFolder, srcFileU)
   343  	_, err = os.Stat(expectedFile)
   344  	if err != nil {
   345  		t.Fatalf("Destination folder should contain the source file but did not.")
   346  	}
   347  }
   348  
   349  // Do the same test as above but with the destination as file, it should fail
   350  func TestUntarPathWithDestinationFile(t *testing.T) {
   351  	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	defer os.RemoveAll(tmpFolder)
   356  	srcFile := filepath.Join(tmpFolder, "src")
   357  	tarFile := filepath.Join(tmpFolder, "src.tar")
   358  	os.Create(filepath.Join(tmpFolder, "src"))
   359  
   360  	// Translate back to Unix semantics as next exec.Command is run under sh
   361  	srcFileU := srcFile
   362  	tarFileU := tarFile
   363  	if runtime.GOOS == "windows" {
   364  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   365  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   366  	}
   367  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   368  	_, err = cmd.CombinedOutput()
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	destFile := filepath.Join(tmpFolder, "dest")
   373  	_, err = os.Create(destFile)
   374  	if err != nil {
   375  		t.Fatalf("Fail to create the destination file")
   376  	}
   377  	err = defaultUntarPath(tarFile, destFile)
   378  	if err == nil {
   379  		t.Fatalf("UntarPath should throw an error if the destination if a file")
   380  	}
   381  }
   382  
   383  // Do the same test as above but with the destination folder already exists
   384  // and the destination file is a directory
   385  // It's working, see https://github.com/docker/docker/issues/10040
   386  func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
   387  	tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  	defer os.RemoveAll(tmpFolder)
   392  	srcFile := filepath.Join(tmpFolder, "src")
   393  	tarFile := filepath.Join(tmpFolder, "src.tar")
   394  	os.Create(srcFile)
   395  
   396  	// Translate back to Unix semantics as next exec.Command is run under sh
   397  	srcFileU := srcFile
   398  	tarFileU := tarFile
   399  	if runtime.GOOS == "windows" {
   400  		tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
   401  		srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
   402  	}
   403  
   404  	cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
   405  	_, err = cmd.CombinedOutput()
   406  	if err != nil {
   407  		t.Fatal(err)
   408  	}
   409  	destFolder := filepath.Join(tmpFolder, "dest")
   410  	err = os.MkdirAll(destFolder, 0740)
   411  	if err != nil {
   412  		t.Fatalf("Fail to create the destination folder")
   413  	}
   414  	// Let's create a folder that will has the same path as the extracted file (from tar)
   415  	destSrcFileAsFolder := filepath.Join(destFolder, srcFileU)
   416  	err = os.MkdirAll(destSrcFileAsFolder, 0740)
   417  	if err != nil {
   418  		t.Fatal(err)
   419  	}
   420  	err = defaultUntarPath(tarFile, destFolder)
   421  	if err != nil {
   422  		t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
   423  	}
   424  }
   425  
   426  func TestCopyWithTarInvalidSrc(t *testing.T) {
   427  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   428  	if err != nil {
   429  		t.Fatal(nil)
   430  	}
   431  	destFolder := filepath.Join(tempFolder, "dest")
   432  	invalidSrc := filepath.Join(tempFolder, "doesnotexists")
   433  	err = os.MkdirAll(destFolder, 0740)
   434  	if err != nil {
   435  		t.Fatal(err)
   436  	}
   437  	err = defaultCopyWithTar(invalidSrc, destFolder)
   438  	if err == nil {
   439  		t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
   440  	}
   441  }
   442  
   443  func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
   444  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   445  	if err != nil {
   446  		t.Fatal(nil)
   447  	}
   448  	srcFolder := filepath.Join(tempFolder, "src")
   449  	inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
   450  	err = os.MkdirAll(srcFolder, 0740)
   451  	if err != nil {
   452  		t.Fatal(err)
   453  	}
   454  	err = defaultCopyWithTar(srcFolder, inexistentDestFolder)
   455  	if err != nil {
   456  		t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
   457  	}
   458  	_, err = os.Stat(inexistentDestFolder)
   459  	if err != nil {
   460  		t.Fatalf("CopyWithTar with an inexistent folder should create it.")
   461  	}
   462  }
   463  
   464  // Test CopyWithTar with a file as src
   465  func TestCopyWithTarSrcFile(t *testing.T) {
   466  	folder, err := ioutil.TempDir("", "docker-archive-test")
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  	defer os.RemoveAll(folder)
   471  	dest := filepath.Join(folder, "dest")
   472  	srcFolder := filepath.Join(folder, "src")
   473  	src := filepath.Join(folder, filepath.Join("src", "src"))
   474  	err = os.MkdirAll(srcFolder, 0740)
   475  	if err != nil {
   476  		t.Fatal(err)
   477  	}
   478  	err = os.MkdirAll(dest, 0740)
   479  	if err != nil {
   480  		t.Fatal(err)
   481  	}
   482  	ioutil.WriteFile(src, []byte("content"), 0777)
   483  	err = defaultCopyWithTar(src, dest)
   484  	if err != nil {
   485  		t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
   486  	}
   487  	_, err = os.Stat(dest)
   488  	// FIXME Check the content
   489  	if err != nil {
   490  		t.Fatalf("Destination file should be the same as the source.")
   491  	}
   492  }
   493  
   494  // Test CopyWithTar with a folder as src
   495  func TestCopyWithTarSrcFolder(t *testing.T) {
   496  	folder, err := ioutil.TempDir("", "docker-archive-test")
   497  	if err != nil {
   498  		t.Fatal(err)
   499  	}
   500  	defer os.RemoveAll(folder)
   501  	dest := filepath.Join(folder, "dest")
   502  	src := filepath.Join(folder, filepath.Join("src", "folder"))
   503  	err = os.MkdirAll(src, 0740)
   504  	if err != nil {
   505  		t.Fatal(err)
   506  	}
   507  	err = os.MkdirAll(dest, 0740)
   508  	if err != nil {
   509  		t.Fatal(err)
   510  	}
   511  	ioutil.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777)
   512  	err = defaultCopyWithTar(src, dest)
   513  	if err != nil {
   514  		t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
   515  	}
   516  	_, err = os.Stat(dest)
   517  	// FIXME Check the content (the file inside)
   518  	if err != nil {
   519  		t.Fatalf("Destination folder should contain the source file but did not.")
   520  	}
   521  }
   522  
   523  func TestCopyFileWithTarInvalidSrc(t *testing.T) {
   524  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   525  	if err != nil {
   526  		t.Fatal(err)
   527  	}
   528  	defer os.RemoveAll(tempFolder)
   529  	destFolder := filepath.Join(tempFolder, "dest")
   530  	err = os.MkdirAll(destFolder, 0740)
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	invalidFile := filepath.Join(tempFolder, "doesnotexists")
   535  	err = defaultCopyFileWithTar(invalidFile, destFolder)
   536  	if err == nil {
   537  		t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
   538  	}
   539  }
   540  
   541  func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
   542  	tempFolder, err := ioutil.TempDir("", "docker-archive-test")
   543  	if err != nil {
   544  		t.Fatal(nil)
   545  	}
   546  	defer os.RemoveAll(tempFolder)
   547  	srcFile := filepath.Join(tempFolder, "src")
   548  	inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
   549  	_, err = os.Create(srcFile)
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  	err = defaultCopyFileWithTar(srcFile, inexistentDestFolder)
   554  	if err != nil {
   555  		t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
   556  	}
   557  	_, err = os.Stat(inexistentDestFolder)
   558  	if err != nil {
   559  		t.Fatalf("CopyWithTar with an inexistent folder should create it.")
   560  	}
   561  	// FIXME Test the src file and content
   562  }
   563  
   564  func TestCopyFileWithTarSrcFolder(t *testing.T) {
   565  	folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test")
   566  	if err != nil {
   567  		t.Fatal(err)
   568  	}
   569  	defer os.RemoveAll(folder)
   570  	dest := filepath.Join(folder, "dest")
   571  	src := filepath.Join(folder, "srcfolder")
   572  	err = os.MkdirAll(src, 0740)
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  	err = os.MkdirAll(dest, 0740)
   577  	if err != nil {
   578  		t.Fatal(err)
   579  	}
   580  	err = defaultCopyFileWithTar(src, dest)
   581  	if err == nil {
   582  		t.Fatalf("CopyFileWithTar should throw an error with a folder.")
   583  	}
   584  }
   585  
   586  func TestCopyFileWithTarSrcFile(t *testing.T) {
   587  	folder, err := ioutil.TempDir("", "docker-archive-test")
   588  	if err != nil {
   589  		t.Fatal(err)
   590  	}
   591  	defer os.RemoveAll(folder)
   592  	dest := filepath.Join(folder, "dest")
   593  	srcFolder := filepath.Join(folder, "src")
   594  	src := filepath.Join(folder, filepath.Join("src", "src"))
   595  	err = os.MkdirAll(srcFolder, 0740)
   596  	if err != nil {
   597  		t.Fatal(err)
   598  	}
   599  	err = os.MkdirAll(dest, 0740)
   600  	if err != nil {
   601  		t.Fatal(err)
   602  	}
   603  	ioutil.WriteFile(src, []byte("content"), 0777)
   604  	err = defaultCopyWithTar(src, dest+"/")
   605  	if err != nil {
   606  		t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
   607  	}
   608  	_, err = os.Stat(dest)
   609  	if err != nil {
   610  		t.Fatalf("Destination folder should contain the source file but did not.")
   611  	}
   612  }
   613  
   614  func TestTarFiles(t *testing.T) {
   615  	// TODO Windows: Figure out how to port this test.
   616  	if runtime.GOOS == "windows" {
   617  		t.Skip("Failing on Windows")
   618  	}
   619  	// try without hardlinks
   620  	if err := checkNoChanges(1000, false); err != nil {
   621  		t.Fatal(err)
   622  	}
   623  	// try with hardlinks
   624  	if err := checkNoChanges(1000, true); err != nil {
   625  		t.Fatal(err)
   626  	}
   627  }
   628  
   629  func checkNoChanges(fileNum int, hardlinks bool) error {
   630  	srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
   631  	if err != nil {
   632  		return err
   633  	}
   634  	defer os.RemoveAll(srcDir)
   635  
   636  	destDir, err := ioutil.TempDir("", "docker-test-destDir")
   637  	if err != nil {
   638  		return err
   639  	}
   640  	defer os.RemoveAll(destDir)
   641  
   642  	_, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
   643  	if err != nil {
   644  		return err
   645  	}
   646  
   647  	err = defaultTarUntar(srcDir, destDir)
   648  	if err != nil {
   649  		return err
   650  	}
   651  
   652  	changes, err := ChangesDirs(destDir, srcDir)
   653  	if err != nil {
   654  		return err
   655  	}
   656  	if len(changes) > 0 {
   657  		return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
   658  	}
   659  	return nil
   660  }
   661  
   662  func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
   663  	archive, err := TarWithOptions(origin, options)
   664  	if err != nil {
   665  		t.Fatal(err)
   666  	}
   667  	defer archive.Close()
   668  
   669  	buf := make([]byte, 10)
   670  	if _, err := archive.Read(buf); err != nil {
   671  		return nil, err
   672  	}
   673  	wrap := io.MultiReader(bytes.NewReader(buf), archive)
   674  
   675  	detectedCompression := DetectCompression(buf)
   676  	compression := options.Compression
   677  	if detectedCompression.Extension() != compression.Extension() {
   678  		return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
   679  	}
   680  
   681  	tmp, err := ioutil.TempDir("", "docker-test-untar")
   682  	if err != nil {
   683  		return nil, err
   684  	}
   685  	defer os.RemoveAll(tmp)
   686  	if err := Untar(wrap, tmp, nil); err != nil {
   687  		return nil, err
   688  	}
   689  	if _, err := os.Stat(tmp); err != nil {
   690  		return nil, err
   691  	}
   692  
   693  	return ChangesDirs(origin, tmp)
   694  }
   695  
   696  func TestTarUntar(t *testing.T) {
   697  	// TODO Windows: Figure out how to fix this test.
   698  	if runtime.GOOS == "windows" {
   699  		t.Skip("Failing on Windows")
   700  	}
   701  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   702  	if err != nil {
   703  		t.Fatal(err)
   704  	}
   705  	defer os.RemoveAll(origin)
   706  	if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   707  		t.Fatal(err)
   708  	}
   709  	if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
   710  		t.Fatal(err)
   711  	}
   712  	if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
   713  		t.Fatal(err)
   714  	}
   715  
   716  	for _, c := range []Compression{
   717  		Uncompressed,
   718  		Gzip,
   719  	} {
   720  		changes, err := tarUntar(t, origin, &TarOptions{
   721  			Compression:     c,
   722  			ExcludePatterns: []string{"3"},
   723  		})
   724  
   725  		if err != nil {
   726  			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
   727  		}
   728  
   729  		if len(changes) != 1 || changes[0].Path != "/3" {
   730  			t.Fatalf("Unexpected differences after tarUntar: %v", changes)
   731  		}
   732  	}
   733  }
   734  
   735  func TestTarWithOptions(t *testing.T) {
   736  	// TODO Windows: Figure out how to fix this test.
   737  	if runtime.GOOS == "windows" {
   738  		t.Skip("Failing on Windows")
   739  	}
   740  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   741  	if err != nil {
   742  		t.Fatal(err)
   743  	}
   744  	if _, err := ioutil.TempDir(origin, "folder"); err != nil {
   745  		t.Fatal(err)
   746  	}
   747  	defer os.RemoveAll(origin)
   748  	if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   749  		t.Fatal(err)
   750  	}
   751  	if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
   752  		t.Fatal(err)
   753  	}
   754  
   755  	cases := []struct {
   756  		opts       *TarOptions
   757  		numChanges int
   758  	}{
   759  		{&TarOptions{IncludeFiles: []string{"1"}}, 2},
   760  		{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
   761  		{&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
   762  		{&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
   763  		{&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
   764  	}
   765  	for _, testCase := range cases {
   766  		changes, err := tarUntar(t, origin, testCase.opts)
   767  		if err != nil {
   768  			t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
   769  		}
   770  		if len(changes) != testCase.numChanges {
   771  			t.Errorf("Expected %d changes, got %d for %+v:",
   772  				testCase.numChanges, len(changes), testCase.opts)
   773  		}
   774  	}
   775  }
   776  
   777  // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
   778  // use PAX Global Extended Headers.
   779  // Failing prevents the archives from being uncompressed during ADD
   780  func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
   781  	hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
   782  	tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test")
   783  	if err != nil {
   784  		t.Fatal(err)
   785  	}
   786  	defer os.RemoveAll(tmpDir)
   787  	err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false)
   788  	if err != nil {
   789  		t.Fatal(err)
   790  	}
   791  }
   792  
   793  // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
   794  // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
   795  func TestUntarUstarGnuConflict(t *testing.T) {
   796  	f, err := os.Open("testdata/broken.tar")
   797  	if err != nil {
   798  		t.Fatal(err)
   799  	}
   800  	defer f.Close()
   801  
   802  	found := false
   803  	tr := tar.NewReader(f)
   804  	// Iterate through the files in the archive.
   805  	for {
   806  		hdr, err := tr.Next()
   807  		if err == io.EOF {
   808  			// end of tar archive
   809  			break
   810  		}
   811  		if err != nil {
   812  			t.Fatal(err)
   813  		}
   814  		if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
   815  			found = true
   816  			break
   817  		}
   818  	}
   819  	if !found {
   820  		t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
   821  	}
   822  }
   823  
   824  func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
   825  	fileData := []byte("fooo")
   826  	for n := 0; n < numberOfFiles; n++ {
   827  		fileName := fmt.Sprintf("file-%d", n)
   828  		if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
   829  			return 0, err
   830  		}
   831  		if makeLinks {
   832  			if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
   833  				return 0, err
   834  			}
   835  		}
   836  	}
   837  	totalSize := numberOfFiles * len(fileData)
   838  	return totalSize, nil
   839  }
   840  
   841  func BenchmarkTarUntar(b *testing.B) {
   842  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   843  	if err != nil {
   844  		b.Fatal(err)
   845  	}
   846  	tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
   847  	if err != nil {
   848  		b.Fatal(err)
   849  	}
   850  	target := filepath.Join(tempDir, "dest")
   851  	n, err := prepareUntarSourceDirectory(100, origin, false)
   852  	if err != nil {
   853  		b.Fatal(err)
   854  	}
   855  	defer os.RemoveAll(origin)
   856  	defer os.RemoveAll(tempDir)
   857  
   858  	b.ResetTimer()
   859  	b.SetBytes(int64(n))
   860  	for n := 0; n < b.N; n++ {
   861  		err := defaultTarUntar(origin, target)
   862  		if err != nil {
   863  			b.Fatal(err)
   864  		}
   865  		os.RemoveAll(target)
   866  	}
   867  }
   868  
   869  func BenchmarkTarUntarWithLinks(b *testing.B) {
   870  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   871  	if err != nil {
   872  		b.Fatal(err)
   873  	}
   874  	tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
   875  	if err != nil {
   876  		b.Fatal(err)
   877  	}
   878  	target := filepath.Join(tempDir, "dest")
   879  	n, err := prepareUntarSourceDirectory(100, origin, true)
   880  	if err != nil {
   881  		b.Fatal(err)
   882  	}
   883  	defer os.RemoveAll(origin)
   884  	defer os.RemoveAll(tempDir)
   885  
   886  	b.ResetTimer()
   887  	b.SetBytes(int64(n))
   888  	for n := 0; n < b.N; n++ {
   889  		err := defaultTarUntar(origin, target)
   890  		if err != nil {
   891  			b.Fatal(err)
   892  		}
   893  		os.RemoveAll(target)
   894  	}
   895  }
   896  
   897  func TestUntarInvalidFilenames(t *testing.T) {
   898  	// TODO Windows: Figure out how to fix this test.
   899  	if runtime.GOOS == "windows" {
   900  		t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
   901  	}
   902  	for i, headers := range [][]*tar.Header{
   903  		{
   904  			{
   905  				Name:     "../victim/dotdot",
   906  				Typeflag: tar.TypeReg,
   907  				Mode:     0644,
   908  			},
   909  		},
   910  		{
   911  			{
   912  				// Note the leading slash
   913  				Name:     "/../victim/slash-dotdot",
   914  				Typeflag: tar.TypeReg,
   915  				Mode:     0644,
   916  			},
   917  		},
   918  	} {
   919  		if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
   920  			t.Fatalf("i=%d. %v", i, err)
   921  		}
   922  	}
   923  }
   924  
   925  func TestUntarHardlinkToSymlink(t *testing.T) {
   926  	// TODO Windows. There may be a way of running this, but turning off for now
   927  	if runtime.GOOS == "windows" {
   928  		t.Skip("hardlinks on Windows")
   929  	}
   930  	for i, headers := range [][]*tar.Header{
   931  		{
   932  			{
   933  				Name:     "symlink1",
   934  				Typeflag: tar.TypeSymlink,
   935  				Linkname: "regfile",
   936  				Mode:     0644,
   937  			},
   938  			{
   939  				Name:     "symlink2",
   940  				Typeflag: tar.TypeLink,
   941  				Linkname: "symlink1",
   942  				Mode:     0644,
   943  			},
   944  			{
   945  				Name:     "regfile",
   946  				Typeflag: tar.TypeReg,
   947  				Mode:     0644,
   948  			},
   949  		},
   950  	} {
   951  		if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
   952  			t.Fatalf("i=%d. %v", i, err)
   953  		}
   954  	}
   955  }
   956  
   957  func TestUntarInvalidHardlink(t *testing.T) {
   958  	// TODO Windows. There may be a way of running this, but turning off for now
   959  	if runtime.GOOS == "windows" {
   960  		t.Skip("hardlinks on Windows")
   961  	}
   962  	for i, headers := range [][]*tar.Header{
   963  		{ // try reading victim/hello (../)
   964  			{
   965  				Name:     "dotdot",
   966  				Typeflag: tar.TypeLink,
   967  				Linkname: "../victim/hello",
   968  				Mode:     0644,
   969  			},
   970  		},
   971  		{ // try reading victim/hello (/../)
   972  			{
   973  				Name:     "slash-dotdot",
   974  				Typeflag: tar.TypeLink,
   975  				// Note the leading slash
   976  				Linkname: "/../victim/hello",
   977  				Mode:     0644,
   978  			},
   979  		},
   980  		{ // try writing victim/file
   981  			{
   982  				Name:     "loophole-victim",
   983  				Typeflag: tar.TypeLink,
   984  				Linkname: "../victim",
   985  				Mode:     0755,
   986  			},
   987  			{
   988  				Name:     "loophole-victim/file",
   989  				Typeflag: tar.TypeReg,
   990  				Mode:     0644,
   991  			},
   992  		},
   993  		{ // try reading victim/hello (hardlink, symlink)
   994  			{
   995  				Name:     "loophole-victim",
   996  				Typeflag: tar.TypeLink,
   997  				Linkname: "../victim",
   998  				Mode:     0755,
   999  			},
  1000  			{
  1001  				Name:     "symlink",
  1002  				Typeflag: tar.TypeSymlink,
  1003  				Linkname: "loophole-victim/hello",
  1004  				Mode:     0644,
  1005  			},
  1006  		},
  1007  		{ // Try reading victim/hello (hardlink, hardlink)
  1008  			{
  1009  				Name:     "loophole-victim",
  1010  				Typeflag: tar.TypeLink,
  1011  				Linkname: "../victim",
  1012  				Mode:     0755,
  1013  			},
  1014  			{
  1015  				Name:     "hardlink",
  1016  				Typeflag: tar.TypeLink,
  1017  				Linkname: "loophole-victim/hello",
  1018  				Mode:     0644,
  1019  			},
  1020  		},
  1021  		{ // Try removing victim directory (hardlink)
  1022  			{
  1023  				Name:     "loophole-victim",
  1024  				Typeflag: tar.TypeLink,
  1025  				Linkname: "../victim",
  1026  				Mode:     0755,
  1027  			},
  1028  			{
  1029  				Name:     "loophole-victim",
  1030  				Typeflag: tar.TypeReg,
  1031  				Mode:     0644,
  1032  			},
  1033  		},
  1034  	} {
  1035  		if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
  1036  			t.Fatalf("i=%d. %v", i, err)
  1037  		}
  1038  	}
  1039  }
  1040  
  1041  func TestUntarInvalidSymlink(t *testing.T) {
  1042  	// TODO Windows. There may be a way of running this, but turning off for now
  1043  	if runtime.GOOS == "windows" {
  1044  		t.Skip("hardlinks on Windows")
  1045  	}
  1046  	for i, headers := range [][]*tar.Header{
  1047  		{ // try reading victim/hello (../)
  1048  			{
  1049  				Name:     "dotdot",
  1050  				Typeflag: tar.TypeSymlink,
  1051  				Linkname: "../victim/hello",
  1052  				Mode:     0644,
  1053  			},
  1054  		},
  1055  		{ // try reading victim/hello (/../)
  1056  			{
  1057  				Name:     "slash-dotdot",
  1058  				Typeflag: tar.TypeSymlink,
  1059  				// Note the leading slash
  1060  				Linkname: "/../victim/hello",
  1061  				Mode:     0644,
  1062  			},
  1063  		},
  1064  		{ // try writing victim/file
  1065  			{
  1066  				Name:     "loophole-victim",
  1067  				Typeflag: tar.TypeSymlink,
  1068  				Linkname: "../victim",
  1069  				Mode:     0755,
  1070  			},
  1071  			{
  1072  				Name:     "loophole-victim/file",
  1073  				Typeflag: tar.TypeReg,
  1074  				Mode:     0644,
  1075  			},
  1076  		},
  1077  		{ // try reading victim/hello (symlink, symlink)
  1078  			{
  1079  				Name:     "loophole-victim",
  1080  				Typeflag: tar.TypeSymlink,
  1081  				Linkname: "../victim",
  1082  				Mode:     0755,
  1083  			},
  1084  			{
  1085  				Name:     "symlink",
  1086  				Typeflag: tar.TypeSymlink,
  1087  				Linkname: "loophole-victim/hello",
  1088  				Mode:     0644,
  1089  			},
  1090  		},
  1091  		{ // try reading victim/hello (symlink, hardlink)
  1092  			{
  1093  				Name:     "loophole-victim",
  1094  				Typeflag: tar.TypeSymlink,
  1095  				Linkname: "../victim",
  1096  				Mode:     0755,
  1097  			},
  1098  			{
  1099  				Name:     "hardlink",
  1100  				Typeflag: tar.TypeLink,
  1101  				Linkname: "loophole-victim/hello",
  1102  				Mode:     0644,
  1103  			},
  1104  		},
  1105  		{ // try removing victim directory (symlink)
  1106  			{
  1107  				Name:     "loophole-victim",
  1108  				Typeflag: tar.TypeSymlink,
  1109  				Linkname: "../victim",
  1110  				Mode:     0755,
  1111  			},
  1112  			{
  1113  				Name:     "loophole-victim",
  1114  				Typeflag: tar.TypeReg,
  1115  				Mode:     0644,
  1116  			},
  1117  		},
  1118  		{ // try writing to victim/newdir/newfile with a symlink in the path
  1119  			{
  1120  				// this header needs to be before the next one, or else there is an error
  1121  				Name:     "dir/loophole",
  1122  				Typeflag: tar.TypeSymlink,
  1123  				Linkname: "../../victim",
  1124  				Mode:     0755,
  1125  			},
  1126  			{
  1127  				Name:     "dir/loophole/newdir/newfile",
  1128  				Typeflag: tar.TypeReg,
  1129  				Mode:     0644,
  1130  			},
  1131  		},
  1132  	} {
  1133  		if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
  1134  			t.Fatalf("i=%d. %v", i, err)
  1135  		}
  1136  	}
  1137  }
  1138  
  1139  func TestTempArchiveCloseMultipleTimes(t *testing.T) {
  1140  	reader := ioutil.NopCloser(strings.NewReader("hello"))
  1141  	tempArchive, err := NewTempArchive(reader, "")
  1142  	buf := make([]byte, 10)
  1143  	n, err := tempArchive.Read(buf)
  1144  	if n != 5 {
  1145  		t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
  1146  	}
  1147  	for i := 0; i < 3; i++ {
  1148  		if err = tempArchive.Close(); err != nil {
  1149  			t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
  1150  		}
  1151  	}
  1152  }
  1153  
  1154  func TestReplaceFileTarWrapper(t *testing.T) {
  1155  	filesInArchive := 20
  1156  	testcases := []struct {
  1157  		doc       string
  1158  		filename  string
  1159  		modifier  TarModifierFunc
  1160  		expected  string
  1161  		fileCount int
  1162  	}{
  1163  		{
  1164  			doc:       "Modifier creates a new file",
  1165  			filename:  "newfile",
  1166  			modifier:  createModifier(t),
  1167  			expected:  "the new content",
  1168  			fileCount: filesInArchive + 1,
  1169  		},
  1170  		{
  1171  			doc:       "Modifier replaces a file",
  1172  			filename:  "file-2",
  1173  			modifier:  createOrReplaceModifier,
  1174  			expected:  "the new content",
  1175  			fileCount: filesInArchive,
  1176  		},
  1177  		{
  1178  			doc:       "Modifier replaces the last file",
  1179  			filename:  fmt.Sprintf("file-%d", filesInArchive-1),
  1180  			modifier:  createOrReplaceModifier,
  1181  			expected:  "the new content",
  1182  			fileCount: filesInArchive,
  1183  		},
  1184  		{
  1185  			doc:       "Modifier appends to a file",
  1186  			filename:  "file-3",
  1187  			modifier:  appendModifier,
  1188  			expected:  "fooo\nnext line",
  1189  			fileCount: filesInArchive,
  1190  		},
  1191  	}
  1192  
  1193  	for _, testcase := range testcases {
  1194  		sourceArchive, cleanup := buildSourceArchive(t, filesInArchive)
  1195  		defer cleanup()
  1196  
  1197  		resultArchive := ReplaceFileTarWrapper(
  1198  			sourceArchive,
  1199  			map[string]TarModifierFunc{testcase.filename: testcase.modifier})
  1200  
  1201  		actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc)
  1202  		assert.Equal(t, testcase.expected, actual, testcase.doc)
  1203  	}
  1204  }
  1205  
  1206  func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) {
  1207  	srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
  1208  	require.NoError(t, err)
  1209  
  1210  	_, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false)
  1211  	require.NoError(t, err)
  1212  
  1213  	sourceArchive, err := TarWithOptions(srcDir, &TarOptions{})
  1214  	require.NoError(t, err)
  1215  	return sourceArchive, func() {
  1216  		os.RemoveAll(srcDir)
  1217  		sourceArchive.Close()
  1218  	}
  1219  }
  1220  
  1221  func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1222  	return &tar.Header{
  1223  		Mode:     0600,
  1224  		Typeflag: tar.TypeReg,
  1225  	}, []byte("the new content"), nil
  1226  }
  1227  
  1228  func createModifier(t *testing.T) TarModifierFunc {
  1229  	return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1230  		assert.Nil(t, content)
  1231  		return createOrReplaceModifier(path, header, content)
  1232  	}
  1233  }
  1234  
  1235  func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1236  	buffer := bytes.Buffer{}
  1237  	if content != nil {
  1238  		if _, err := buffer.ReadFrom(content); err != nil {
  1239  			return nil, nil, err
  1240  		}
  1241  	}
  1242  	buffer.WriteString("\nnext line")
  1243  	return &tar.Header{Mode: 0600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil
  1244  }
  1245  
  1246  func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
  1247  	destDir, err := ioutil.TempDir("", "docker-test-destDir")
  1248  	require.NoError(t, err)
  1249  	defer os.RemoveAll(destDir)
  1250  
  1251  	err = Untar(archive, destDir, nil)
  1252  	require.NoError(t, err)
  1253  
  1254  	files, _ := ioutil.ReadDir(destDir)
  1255  	assert.Len(t, files, expectedCount, doc)
  1256  
  1257  	content, err := ioutil.ReadFile(filepath.Join(destDir, name))
  1258  	assert.NoError(t, err)
  1259  	return string(content)
  1260  }