archive_test.go 37 KB

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