buildfile_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. package docker
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "net"
  8. "net/http"
  9. "net/http/httptest"
  10. "strings"
  11. "testing"
  12. "github.com/dotcloud/docker/archive"
  13. "github.com/dotcloud/docker/engine"
  14. "github.com/dotcloud/docker/image"
  15. "github.com/dotcloud/docker/server"
  16. "github.com/dotcloud/docker/utils"
  17. )
  18. // A testContextTemplate describes a build context and how to test it
  19. type testContextTemplate struct {
  20. // Contents of the Dockerfile
  21. dockerfile string
  22. // Additional files in the context, eg [][2]string{"./passwd", "gordon"}
  23. files [][2]string
  24. // Additional remote files to host on a local HTTP server.
  25. remoteFiles [][2]string
  26. }
  27. func (context testContextTemplate) Archive(dockerfile string, t *testing.T) archive.Archive {
  28. input := []string{"Dockerfile", dockerfile}
  29. for _, pair := range context.files {
  30. input = append(input, pair[0], pair[1])
  31. }
  32. a, err := archive.Generate(input...)
  33. if err != nil {
  34. t.Fatal(err)
  35. }
  36. return a
  37. }
  38. // A table of all the contexts to build and test.
  39. // A new docker runtime will be created and torn down for each context.
  40. var testContexts = []testContextTemplate{
  41. {
  42. `
  43. from {IMAGE}
  44. run sh -c 'echo root:testpass > /tmp/passwd'
  45. run mkdir -p /var/run/sshd
  46. run [ "$(cat /tmp/passwd)" = "root:testpass" ]
  47. run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
  48. `,
  49. nil,
  50. nil,
  51. },
  52. // Exactly the same as above, except uses a line split with a \ to test
  53. // multiline support.
  54. {
  55. `
  56. from {IMAGE}
  57. run sh -c 'echo root:testpass \
  58. > /tmp/passwd'
  59. run mkdir -p /var/run/sshd
  60. run [ "$(cat /tmp/passwd)" = "root:testpass" ]
  61. run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
  62. `,
  63. nil,
  64. nil,
  65. },
  66. // Line containing literal "\n"
  67. {
  68. `
  69. from {IMAGE}
  70. run sh -c 'echo root:testpass > /tmp/passwd'
  71. run echo "foo \n bar"; echo "baz"
  72. run mkdir -p /var/run/sshd
  73. run [ "$(cat /tmp/passwd)" = "root:testpass" ]
  74. run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
  75. `,
  76. nil,
  77. nil,
  78. },
  79. {
  80. `
  81. from {IMAGE}
  82. add foo /usr/lib/bla/bar
  83. run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
  84. add http://{SERVERADDR}/baz /usr/lib/baz/quux
  85. run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
  86. `,
  87. [][2]string{{"foo", "hello"}},
  88. [][2]string{{"/baz", "world!"}},
  89. },
  90. {
  91. `
  92. from {IMAGE}
  93. add f /
  94. run [ "$(cat /f)" = "hello" ]
  95. add f /abc
  96. run [ "$(cat /abc)" = "hello" ]
  97. add f /x/y/z
  98. run [ "$(cat /x/y/z)" = "hello" ]
  99. add f /x/y/d/
  100. run [ "$(cat /x/y/d/f)" = "hello" ]
  101. add d /
  102. run [ "$(cat /ga)" = "bu" ]
  103. add d /somewhere
  104. run [ "$(cat /somewhere/ga)" = "bu" ]
  105. add d /anotherplace/
  106. run [ "$(cat /anotherplace/ga)" = "bu" ]
  107. add d /somewheeeere/over/the/rainbooow
  108. run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
  109. `,
  110. [][2]string{
  111. {"f", "hello"},
  112. {"d/ga", "bu"},
  113. },
  114. nil,
  115. },
  116. {
  117. `
  118. from {IMAGE}
  119. add http://{SERVERADDR}/x /a/b/c
  120. run [ "$(cat /a/b/c)" = "hello" ]
  121. add http://{SERVERADDR}/x?foo=bar /
  122. run [ "$(cat /x)" = "hello" ]
  123. add http://{SERVERADDR}/x /d/
  124. run [ "$(cat /d/x)" = "hello" ]
  125. add http://{SERVERADDR} /e
  126. run [ "$(cat /e)" = "blah" ]
  127. `,
  128. nil,
  129. [][2]string{{"/x", "hello"}, {"/", "blah"}},
  130. },
  131. // Comments, shebangs, and executability, oh my!
  132. {
  133. `
  134. FROM {IMAGE}
  135. # This is an ordinary comment.
  136. RUN { echo '#!/bin/sh'; echo 'echo hello world'; } > /hello.sh
  137. RUN [ ! -x /hello.sh ]
  138. RUN chmod +x /hello.sh
  139. RUN [ -x /hello.sh ]
  140. RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ]
  141. RUN [ "$(/hello.sh)" = "hello world" ]
  142. `,
  143. nil,
  144. nil,
  145. },
  146. // Users and groups
  147. {
  148. `
  149. FROM {IMAGE}
  150. # Make sure our defaults work
  151. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ]
  152. # TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0)
  153. USER root
  154. RUN [ "$(id -G):$(id -Gn)" = '0:root' ]
  155. # Setup dockerio user and group
  156. RUN echo 'dockerio:x:1000:1000::/bin:/bin/false' >> /etc/passwd
  157. RUN echo 'dockerio:x:1000:' >> /etc/group
  158. # Make sure we can switch to our user and all the information is exactly as we expect it to be
  159. USER dockerio
  160. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
  161. # Switch back to root and double check that worked exactly as we might expect it to
  162. USER root
  163. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0:root' ]
  164. # Add a "supplementary" group for our dockerio user
  165. RUN echo 'supplementary:x:1001:dockerio' >> /etc/group
  166. # ... and then go verify that we get it like we expect
  167. USER dockerio
  168. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000 1001:dockerio supplementary' ]
  169. USER 1000
  170. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000 1001:dockerio supplementary' ]
  171. # super test the new "user:group" syntax
  172. USER dockerio:dockerio
  173. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
  174. USER 1000:dockerio
  175. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
  176. USER dockerio:1000
  177. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
  178. USER 1000:1000
  179. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
  180. USER dockerio:supplementary
  181. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
  182. USER dockerio:1001
  183. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
  184. USER 1000:supplementary
  185. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
  186. USER 1000:1001
  187. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
  188. # make sure unknown uid/gid still works properly
  189. USER 1042:1043
  190. RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]
  191. `,
  192. nil,
  193. nil,
  194. },
  195. // Environment variable
  196. {
  197. `
  198. from {IMAGE}
  199. env FOO BAR
  200. run [ "$FOO" = "BAR" ]
  201. `,
  202. nil,
  203. nil,
  204. },
  205. // Environment overwriting
  206. {
  207. `
  208. from {IMAGE}
  209. env FOO BAR
  210. run [ "$FOO" = "BAR" ]
  211. env FOO BAZ
  212. run [ "$FOO" = "BAZ" ]
  213. `,
  214. nil,
  215. nil,
  216. },
  217. {
  218. `
  219. from {IMAGE}
  220. ENTRYPOINT /bin/echo
  221. CMD Hello world
  222. `,
  223. nil,
  224. nil,
  225. },
  226. {
  227. `
  228. from {IMAGE}
  229. VOLUME /test
  230. CMD Hello world
  231. `,
  232. nil,
  233. nil,
  234. },
  235. {
  236. `
  237. from {IMAGE}
  238. env FOO /foo/baz
  239. env BAR /bar
  240. env BAZ $BAR
  241. env FOOPATH $PATH:$FOO
  242. run [ "$BAR" = "$BAZ" ]
  243. run [ "$FOOPATH" = "$PATH:/foo/baz" ]
  244. `,
  245. nil,
  246. nil,
  247. },
  248. {
  249. `
  250. from {IMAGE}
  251. env FOO /bar
  252. env TEST testdir
  253. env BAZ /foobar
  254. add testfile $BAZ/
  255. add $TEST $FOO
  256. run [ "$(cat /foobar/testfile)" = "test1" ]
  257. run [ "$(cat /bar/withfile)" = "test2" ]
  258. `,
  259. [][2]string{
  260. {"testfile", "test1"},
  261. {"testdir/withfile", "test2"},
  262. },
  263. nil,
  264. },
  265. // JSON!
  266. {
  267. `
  268. FROM {IMAGE}
  269. RUN ["/bin/echo","hello","world"]
  270. CMD ["/bin/true"]
  271. ENTRYPOINT ["/bin/echo","your command -->"]
  272. `,
  273. nil,
  274. nil,
  275. },
  276. {
  277. `
  278. FROM {IMAGE}
  279. ADD test /test
  280. RUN ["chmod","+x","/test"]
  281. RUN ["/test"]
  282. RUN [ "$(cat /testfile)" = 'test!' ]
  283. `,
  284. [][2]string{
  285. {"test", "#!/bin/sh\necho 'test!' > /testfile"},
  286. },
  287. nil,
  288. },
  289. {
  290. `
  291. FROM {IMAGE}
  292. # what \
  293. RUN mkdir /testing
  294. RUN touch /testing/other
  295. `,
  296. nil,
  297. nil,
  298. },
  299. }
  300. // FIXME: test building with 2 successive overlapping ADD commands
  301. func constructDockerfile(template string, ip net.IP, port string) string {
  302. serverAddr := fmt.Sprintf("%s:%s", ip, port)
  303. replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
  304. return replacer.Replace(template)
  305. }
  306. func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
  307. mux := http.NewServeMux()
  308. for _, file := range files {
  309. name, contents := file[0], file[1]
  310. mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
  311. w.Write([]byte(contents))
  312. })
  313. }
  314. // This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
  315. // connections (from the container).
  316. listener, err := net.Listen("tcp", ":0")
  317. if err != nil {
  318. return nil, err
  319. }
  320. s := httptest.NewUnstartedServer(mux)
  321. s.Listener = listener
  322. s.Start()
  323. return s, nil
  324. }
  325. func TestBuild(t *testing.T) {
  326. for _, ctx := range testContexts {
  327. _, err := buildImage(ctx, t, nil, true)
  328. if err != nil {
  329. t.Fatal(err)
  330. }
  331. }
  332. }
  333. func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*image.Image, error) {
  334. if eng == nil {
  335. eng = NewTestEngine(t)
  336. runtime := mkDaemonFromEngine(eng, t)
  337. // FIXME: we might not need runtime, why not simply nuke
  338. // the engine?
  339. defer nuke(runtime)
  340. }
  341. srv := mkServerFromEngine(eng, t)
  342. httpServer, err := mkTestingFileServer(context.remoteFiles)
  343. if err != nil {
  344. t.Fatal(err)
  345. }
  346. defer httpServer.Close()
  347. idx := strings.LastIndex(httpServer.URL, ":")
  348. if idx < 0 {
  349. t.Fatalf("could not get port from test http server address %s", httpServer.URL)
  350. }
  351. port := httpServer.URL[idx+1:]
  352. iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
  353. if iIP == nil {
  354. t.Fatal("Legacy bridgeIP field not set in engine")
  355. }
  356. ip, ok := iIP.(net.IP)
  357. if !ok {
  358. panic("Legacy bridgeIP field in engine does not cast to net.IP")
  359. }
  360. dockerfile := constructDockerfile(context.dockerfile, ip, port)
  361. buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
  362. id, err := buildfile.Build(context.Archive(dockerfile, t))
  363. if err != nil {
  364. return nil, err
  365. }
  366. job := eng.Job("image_inspect", id)
  367. buffer := bytes.NewBuffer(nil)
  368. image := &image.Image{}
  369. job.Stdout.Add(buffer)
  370. if err := job.Run(); err != nil {
  371. return nil, err
  372. }
  373. err = json.NewDecoder(buffer).Decode(image)
  374. return image, err
  375. }
  376. func TestBuildFailsDockerfileEmpty(t *testing.T) {
  377. _, err := buildImage(testContextTemplate{``, nil, nil}, t, nil, true)
  378. if err != server.ErrDockerfileEmpty {
  379. t.Fatal("Expected: %v, got: %v", server.ErrDockerfileEmpty, err)
  380. }
  381. }
  382. func TestBuildOnBuildTrigger(t *testing.T) {
  383. _, err := buildImage(testContextTemplate{`
  384. from {IMAGE}
  385. onbuild run echo here is the trigger
  386. onbuild run touch foobar
  387. `,
  388. nil, nil,
  389. },
  390. t, nil, true,
  391. )
  392. if err != nil {
  393. t.Fatal(err)
  394. }
  395. // FIXME: test that the 'foobar' file was created in the final build.
  396. }
  397. func TestBuildOnBuildForbiddenChainedTrigger(t *testing.T) {
  398. _, err := buildImage(testContextTemplate{`
  399. from {IMAGE}
  400. onbuild onbuild run echo test
  401. `,
  402. nil, nil,
  403. },
  404. t, nil, true,
  405. )
  406. if err == nil {
  407. t.Fatal("Error should not be nil")
  408. }
  409. }
  410. func TestBuildOnBuildForbiddenFromTrigger(t *testing.T) {
  411. _, err := buildImage(testContextTemplate{`
  412. from {IMAGE}
  413. onbuild from {IMAGE}
  414. `,
  415. nil, nil,
  416. },
  417. t, nil, true,
  418. )
  419. if err == nil {
  420. t.Fatal("Error should not be nil")
  421. }
  422. }
  423. func TestBuildOnBuildForbiddenMaintainerTrigger(t *testing.T) {
  424. _, err := buildImage(testContextTemplate{`
  425. from {IMAGE}
  426. onbuild maintainer test
  427. `,
  428. nil, nil,
  429. },
  430. t, nil, true,
  431. )
  432. if err == nil {
  433. t.Fatal("Error should not be nil")
  434. }
  435. }
  436. // gh #2446
  437. func TestBuildAddToSymlinkDest(t *testing.T) {
  438. eng := NewTestEngine(t)
  439. defer nuke(mkDaemonFromEngine(eng, t))
  440. _, err := buildImage(testContextTemplate{`
  441. from {IMAGE}
  442. run mkdir /foo
  443. run ln -s /foo /bar
  444. add foo /bar/
  445. run stat /bar/foo
  446. `,
  447. [][2]string{{"foo", "HEYO"}}, nil}, t, eng, true)
  448. if err != nil {
  449. t.Fatal(err)
  450. }
  451. }