buildfile_test.go 12 KB

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