buildfile_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. package docker
  2. import (
  3. "fmt"
  4. "github.com/dotcloud/docker"
  5. "github.com/dotcloud/docker/archive"
  6. "github.com/dotcloud/docker/engine"
  7. "github.com/dotcloud/docker/utils"
  8. "io/ioutil"
  9. "net"
  10. "net/http"
  11. "net/http/httptest"
  12. "strings"
  13. "testing"
  14. )
  15. // mkTestContext generates a build context from the contents of the provided dockerfile.
  16. // This context is suitable for use as an argument to BuildFile.Build()
  17. func mkTestContext(dockerfile string, files [][2]string, t *testing.T) archive.Archive {
  18. context, err := docker.MkBuildContext(dockerfile, files)
  19. if err != nil {
  20. t.Fatal(err)
  21. }
  22. return context
  23. }
  24. // A testContextTemplate describes a build context and how to test it
  25. type testContextTemplate struct {
  26. // Contents of the Dockerfile
  27. dockerfile string
  28. // Additional files in the context, eg [][2]string{"./passwd", "gordon"}
  29. files [][2]string
  30. // Additional remote files to host on a local HTTP server.
  31. remoteFiles [][2]string
  32. }
  33. // A table of all the contexts to build and test.
  34. // A new docker runtime will be created and torn down for each context.
  35. var testContexts = []testContextTemplate{
  36. {
  37. `
  38. from {IMAGE}
  39. run sh -c 'echo root:testpass > /tmp/passwd'
  40. run mkdir -p /var/run/sshd
  41. run [ "$(cat /tmp/passwd)" = "root:testpass" ]
  42. run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
  43. `,
  44. nil,
  45. nil,
  46. },
  47. // Exactly the same as above, except uses a line split with a \ to test
  48. // multiline support.
  49. {
  50. `
  51. from {IMAGE}
  52. run sh -c 'echo root:testpass \
  53. > /tmp/passwd'
  54. run mkdir -p /var/run/sshd
  55. run [ "$(cat /tmp/passwd)" = "root:testpass" ]
  56. run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
  57. `,
  58. nil,
  59. nil,
  60. },
  61. // Line containing literal "\n"
  62. {
  63. `
  64. from {IMAGE}
  65. run sh -c 'echo root:testpass > /tmp/passwd'
  66. run echo "foo \n bar"; echo "baz"
  67. run mkdir -p /var/run/sshd
  68. run [ "$(cat /tmp/passwd)" = "root:testpass" ]
  69. run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
  70. `,
  71. nil,
  72. nil,
  73. },
  74. {
  75. `
  76. from {IMAGE}
  77. add foo /usr/lib/bla/bar
  78. run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
  79. add http://{SERVERADDR}/baz /usr/lib/baz/quux
  80. run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
  81. `,
  82. [][2]string{{"foo", "hello"}},
  83. [][2]string{{"/baz", "world!"}},
  84. },
  85. {
  86. `
  87. from {IMAGE}
  88. add f /
  89. run [ "$(cat /f)" = "hello" ]
  90. add f /abc
  91. run [ "$(cat /abc)" = "hello" ]
  92. add f /x/y/z
  93. run [ "$(cat /x/y/z)" = "hello" ]
  94. add f /x/y/d/
  95. run [ "$(cat /x/y/d/f)" = "hello" ]
  96. add d /
  97. run [ "$(cat /ga)" = "bu" ]
  98. add d /somewhere
  99. run [ "$(cat /somewhere/ga)" = "bu" ]
  100. add d /anotherplace/
  101. run [ "$(cat /anotherplace/ga)" = "bu" ]
  102. add d /somewheeeere/over/the/rainbooow
  103. run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
  104. `,
  105. [][2]string{
  106. {"f", "hello"},
  107. {"d/ga", "bu"},
  108. },
  109. nil,
  110. },
  111. {
  112. `
  113. from {IMAGE}
  114. add http://{SERVERADDR}/x /a/b/c
  115. run [ "$(cat /a/b/c)" = "hello" ]
  116. add http://{SERVERADDR}/x?foo=bar /
  117. run [ "$(cat /x)" = "hello" ]
  118. add http://{SERVERADDR}/x /d/
  119. run [ "$(cat /d/x)" = "hello" ]
  120. add http://{SERVERADDR} /e
  121. run [ "$(cat /e)" = "blah" ]
  122. `,
  123. nil,
  124. [][2]string{{"/x", "hello"}, {"/", "blah"}},
  125. },
  126. {
  127. `
  128. from {IMAGE}
  129. env FOO BAR
  130. run [ "$FOO" = "BAR" ]
  131. `,
  132. nil,
  133. nil,
  134. },
  135. {
  136. `
  137. from {IMAGE}
  138. ENTRYPOINT /bin/echo
  139. CMD Hello world
  140. `,
  141. nil,
  142. nil,
  143. },
  144. {
  145. `
  146. from {IMAGE}
  147. VOLUME /test
  148. CMD Hello world
  149. `,
  150. nil,
  151. nil,
  152. },
  153. {
  154. `
  155. from {IMAGE}
  156. env FOO /foo/baz
  157. env BAR /bar
  158. env BAZ $BAR
  159. env FOOPATH $PATH:$FOO
  160. run [ "$BAR" = "$BAZ" ]
  161. run [ "$FOOPATH" = "$PATH:/foo/baz" ]
  162. `,
  163. nil,
  164. nil,
  165. },
  166. {
  167. `
  168. from {IMAGE}
  169. env FOO /bar
  170. env TEST testdir
  171. env BAZ /foobar
  172. add testfile $BAZ/
  173. add $TEST $FOO
  174. run [ "$(cat /foobar/testfile)" = "test1" ]
  175. run [ "$(cat /bar/withfile)" = "test2" ]
  176. `,
  177. [][2]string{
  178. {"testfile", "test1"},
  179. {"testdir/withfile", "test2"},
  180. },
  181. nil,
  182. },
  183. }
  184. // FIXME: test building with 2 successive overlapping ADD commands
  185. func constructDockerfile(template string, ip net.IP, port string) string {
  186. serverAddr := fmt.Sprintf("%s:%s", ip, port)
  187. replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
  188. return replacer.Replace(template)
  189. }
  190. func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
  191. mux := http.NewServeMux()
  192. for _, file := range files {
  193. name, contents := file[0], file[1]
  194. mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
  195. w.Write([]byte(contents))
  196. })
  197. }
  198. // This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
  199. // connections (from the container).
  200. listener, err := net.Listen("tcp", ":0")
  201. if err != nil {
  202. return nil, err
  203. }
  204. s := httptest.NewUnstartedServer(mux)
  205. s.Listener = listener
  206. s.Start()
  207. return s, nil
  208. }
  209. func TestBuild(t *testing.T) {
  210. for _, ctx := range testContexts {
  211. _, err := buildImage(ctx, t, nil, true)
  212. if err != nil {
  213. t.Fatal(err)
  214. }
  215. }
  216. }
  217. func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*docker.Image, error) {
  218. if eng == nil {
  219. eng = NewTestEngine(t)
  220. runtime := mkRuntimeFromEngine(eng, t)
  221. // FIXME: we might not need runtime, why not simply nuke
  222. // the engine?
  223. defer nuke(runtime)
  224. }
  225. srv := mkServerFromEngine(eng, t)
  226. httpServer, err := mkTestingFileServer(context.remoteFiles)
  227. if err != nil {
  228. t.Fatal(err)
  229. }
  230. defer httpServer.Close()
  231. idx := strings.LastIndex(httpServer.URL, ":")
  232. if idx < 0 {
  233. t.Fatalf("could not get port from test http server address %s", httpServer.URL)
  234. }
  235. port := httpServer.URL[idx+1:]
  236. iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
  237. if iIP == nil {
  238. t.Fatal("Legacy bridgeIP field not set in engine")
  239. }
  240. ip, ok := iIP.(net.IP)
  241. if !ok {
  242. panic("Legacy bridgeIP field in engine does not cast to net.IP")
  243. }
  244. dockerfile := constructDockerfile(context.dockerfile, ip, port)
  245. buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil)
  246. id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
  247. if err != nil {
  248. return nil, err
  249. }
  250. return srv.ImageInspect(id)
  251. }
  252. func TestVolume(t *testing.T) {
  253. img, err := buildImage(testContextTemplate{`
  254. from {IMAGE}
  255. volume /test
  256. cmd Hello world
  257. `, nil, nil}, t, nil, true)
  258. if err != nil {
  259. t.Fatal(err)
  260. }
  261. if len(img.Config.Volumes) == 0 {
  262. t.Fail()
  263. }
  264. for key := range img.Config.Volumes {
  265. if key != "/test" {
  266. t.Fail()
  267. }
  268. }
  269. }
  270. func TestBuildMaintainer(t *testing.T) {
  271. img, err := buildImage(testContextTemplate{`
  272. from {IMAGE}
  273. maintainer dockerio
  274. `, nil, nil}, t, nil, true)
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. if img.Author != "dockerio" {
  279. t.Fail()
  280. }
  281. }
  282. func TestBuildUser(t *testing.T) {
  283. img, err := buildImage(testContextTemplate{`
  284. from {IMAGE}
  285. user dockerio
  286. `, nil, nil}, t, nil, true)
  287. if err != nil {
  288. t.Fatal(err)
  289. }
  290. if img.Config.User != "dockerio" {
  291. t.Fail()
  292. }
  293. }
  294. func TestBuildEnv(t *testing.T) {
  295. img, err := buildImage(testContextTemplate{`
  296. from {IMAGE}
  297. env port 4243
  298. `,
  299. nil, nil}, t, nil, true)
  300. if err != nil {
  301. t.Fatal(err)
  302. }
  303. hasEnv := false
  304. for _, envVar := range img.Config.Env {
  305. if envVar == "port=4243" {
  306. hasEnv = true
  307. break
  308. }
  309. }
  310. if !hasEnv {
  311. t.Fail()
  312. }
  313. }
  314. func TestBuildCmd(t *testing.T) {
  315. img, err := buildImage(testContextTemplate{`
  316. from {IMAGE}
  317. cmd ["/bin/echo", "Hello World"]
  318. `,
  319. nil, nil}, t, nil, true)
  320. if err != nil {
  321. t.Fatal(err)
  322. }
  323. if img.Config.Cmd[0] != "/bin/echo" {
  324. t.Log(img.Config.Cmd[0])
  325. t.Fail()
  326. }
  327. if img.Config.Cmd[1] != "Hello World" {
  328. t.Log(img.Config.Cmd[1])
  329. t.Fail()
  330. }
  331. }
  332. func TestBuildExpose(t *testing.T) {
  333. img, err := buildImage(testContextTemplate{`
  334. from {IMAGE}
  335. expose 4243
  336. `,
  337. nil, nil}, t, nil, true)
  338. if err != nil {
  339. t.Fatal(err)
  340. }
  341. if img.Config.PortSpecs[0] != "4243" {
  342. t.Fail()
  343. }
  344. }
  345. func TestBuildEntrypoint(t *testing.T) {
  346. img, err := buildImage(testContextTemplate{`
  347. from {IMAGE}
  348. entrypoint ["/bin/echo"]
  349. `,
  350. nil, nil}, t, nil, true)
  351. if err != nil {
  352. t.Fatal(err)
  353. }
  354. if img.Config.Entrypoint[0] != "/bin/echo" {
  355. }
  356. }
  357. // testing #1405 - config.Cmd does not get cleaned up if
  358. // utilizing cache
  359. func TestBuildEntrypointRunCleanup(t *testing.T) {
  360. eng := NewTestEngine(t)
  361. defer nuke(mkRuntimeFromEngine(eng, t))
  362. img, err := buildImage(testContextTemplate{`
  363. from {IMAGE}
  364. run echo "hello"
  365. `,
  366. nil, nil}, t, eng, true)
  367. if err != nil {
  368. t.Fatal(err)
  369. }
  370. img, err = buildImage(testContextTemplate{`
  371. from {IMAGE}
  372. run echo "hello"
  373. add foo /foo
  374. entrypoint ["/bin/echo"]
  375. `,
  376. [][2]string{{"foo", "HEYO"}}, nil}, t, eng, true)
  377. if err != nil {
  378. t.Fatal(err)
  379. }
  380. if len(img.Config.Cmd) != 0 {
  381. t.Fail()
  382. }
  383. }
  384. func TestBuildImageWithCache(t *testing.T) {
  385. eng := NewTestEngine(t)
  386. defer nuke(mkRuntimeFromEngine(eng, t))
  387. template := testContextTemplate{`
  388. from {IMAGE}
  389. maintainer dockerio
  390. `,
  391. nil, nil}
  392. img, err := buildImage(template, t, eng, true)
  393. if err != nil {
  394. t.Fatal(err)
  395. }
  396. imageId := img.ID
  397. img = nil
  398. img, err = buildImage(template, t, eng, true)
  399. if err != nil {
  400. t.Fatal(err)
  401. }
  402. if imageId != img.ID {
  403. t.Logf("Image ids should match: %s != %s", imageId, img.ID)
  404. t.Fail()
  405. }
  406. }
  407. func TestBuildImageWithoutCache(t *testing.T) {
  408. eng := NewTestEngine(t)
  409. defer nuke(mkRuntimeFromEngine(eng, t))
  410. template := testContextTemplate{`
  411. from {IMAGE}
  412. maintainer dockerio
  413. `,
  414. nil, nil}
  415. img, err := buildImage(template, t, eng, true)
  416. if err != nil {
  417. t.Fatal(err)
  418. }
  419. imageId := img.ID
  420. img = nil
  421. img, err = buildImage(template, t, eng, false)
  422. if err != nil {
  423. t.Fatal(err)
  424. }
  425. if imageId == img.ID {
  426. t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
  427. t.Fail()
  428. }
  429. }
  430. func TestForbiddenContextPath(t *testing.T) {
  431. eng := NewTestEngine(t)
  432. defer nuke(mkRuntimeFromEngine(eng, t))
  433. srv := mkServerFromEngine(eng, t)
  434. context := testContextTemplate{`
  435. from {IMAGE}
  436. maintainer dockerio
  437. add ../../ test/
  438. `,
  439. [][2]string{{"test.txt", "test1"}, {"other.txt", "other"}}, nil}
  440. httpServer, err := mkTestingFileServer(context.remoteFiles)
  441. if err != nil {
  442. t.Fatal(err)
  443. }
  444. defer httpServer.Close()
  445. idx := strings.LastIndex(httpServer.URL, ":")
  446. if idx < 0 {
  447. t.Fatalf("could not get port from test http server address %s", httpServer.URL)
  448. }
  449. port := httpServer.URL[idx+1:]
  450. iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
  451. if iIP == nil {
  452. t.Fatal("Legacy bridgeIP field not set in engine")
  453. }
  454. ip, ok := iIP.(net.IP)
  455. if !ok {
  456. panic("Legacy bridgeIP field in engine does not cast to net.IP")
  457. }
  458. dockerfile := constructDockerfile(context.dockerfile, ip, port)
  459. buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil)
  460. _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
  461. if err == nil {
  462. t.Log("Error should not be nil")
  463. t.Fail()
  464. }
  465. if err.Error() != "Forbidden path outside the build context: ../../ (/)" {
  466. t.Logf("Error message is not expected: %s", err.Error())
  467. t.Fail()
  468. }
  469. }
  470. func TestBuildADDFileNotFound(t *testing.T) {
  471. eng := NewTestEngine(t)
  472. defer nuke(mkRuntimeFromEngine(eng, t))
  473. context := testContextTemplate{`
  474. from {IMAGE}
  475. add foo /usr/local/bar
  476. `,
  477. nil, nil}
  478. httpServer, err := mkTestingFileServer(context.remoteFiles)
  479. if err != nil {
  480. t.Fatal(err)
  481. }
  482. defer httpServer.Close()
  483. idx := strings.LastIndex(httpServer.URL, ":")
  484. if idx < 0 {
  485. t.Fatalf("could not get port from test http server address %s", httpServer.URL)
  486. }
  487. port := httpServer.URL[idx+1:]
  488. iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
  489. if iIP == nil {
  490. t.Fatal("Legacy bridgeIP field not set in engine")
  491. }
  492. ip, ok := iIP.(net.IP)
  493. if !ok {
  494. panic("Legacy bridgeIP field in engine does not cast to net.IP")
  495. }
  496. dockerfile := constructDockerfile(context.dockerfile, ip, port)
  497. buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil)
  498. _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
  499. if err == nil {
  500. t.Log("Error should not be nil")
  501. t.Fail()
  502. }
  503. if err.Error() != "foo: no such file or directory" {
  504. t.Logf("Error message is not expected: %s", err.Error())
  505. t.Fail()
  506. }
  507. }
  508. func TestBuildInheritance(t *testing.T) {
  509. eng := NewTestEngine(t)
  510. defer nuke(mkRuntimeFromEngine(eng, t))
  511. img, err := buildImage(testContextTemplate{`
  512. from {IMAGE}
  513. expose 4243
  514. `,
  515. nil, nil}, t, eng, true)
  516. if err != nil {
  517. t.Fatal(err)
  518. }
  519. img2, _ := buildImage(testContextTemplate{fmt.Sprintf(`
  520. from %s
  521. entrypoint ["/bin/echo"]
  522. `, img.ID),
  523. nil, nil}, t, eng, true)
  524. if err != nil {
  525. t.Fatal(err)
  526. }
  527. // from child
  528. if img2.Config.Entrypoint[0] != "/bin/echo" {
  529. t.Fail()
  530. }
  531. // from parent
  532. if img.Config.PortSpecs[0] != "4243" {
  533. t.Fail()
  534. }
  535. }
  536. func TestBuildFails(t *testing.T) {
  537. _, err := buildImage(testContextTemplate{`
  538. from {IMAGE}
  539. run sh -c "exit 23"
  540. `,
  541. nil, nil}, t, nil, true)
  542. if err == nil {
  543. t.Fatal("Error should not be nil")
  544. }
  545. sterr, ok := err.(*utils.JSONError)
  546. if !ok {
  547. t.Fatalf("Error should be utils.JSONError")
  548. }
  549. if sterr.Code != 23 {
  550. t.Fatalf("StatusCode %d unexpected, should be 23", sterr.Code)
  551. }
  552. }