buildfile_test.go 12 KB

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