docker_api_build_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. package main
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "net/http"
  10. "regexp"
  11. "strings"
  12. "github.com/docker/docker/api/types"
  13. "github.com/docker/docker/integration-cli/checker"
  14. "github.com/docker/docker/integration-cli/cli/build/fakecontext"
  15. "github.com/docker/docker/integration-cli/cli/build/fakegit"
  16. "github.com/docker/docker/integration-cli/cli/build/fakestorage"
  17. "github.com/docker/docker/integration-cli/request"
  18. "github.com/go-check/check"
  19. "github.com/moby/buildkit/session"
  20. "github.com/moby/buildkit/session/filesync"
  21. "github.com/stretchr/testify/assert"
  22. "github.com/stretchr/testify/require"
  23. "golang.org/x/net/context"
  24. "golang.org/x/sync/errgroup"
  25. )
  26. func (s *DockerSuite) TestBuildAPIDockerFileRemote(c *check.C) {
  27. testRequires(c, NotUserNamespace)
  28. var testD string
  29. if testEnv.DaemonPlatform() == "windows" {
  30. testD = `FROM busybox
  31. RUN find / -name ba*
  32. RUN find /tmp/`
  33. } else {
  34. // -xdev is required because sysfs can cause EPERM
  35. testD = `FROM busybox
  36. RUN find / -xdev -name ba*
  37. RUN find /tmp/`
  38. }
  39. server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{"testD": testD}))
  40. defer server.Close()
  41. res, body, err := request.Post("/build?dockerfile=baz&remote="+server.URL()+"/testD", request.JSON)
  42. c.Assert(err, checker.IsNil)
  43. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  44. buf, err := request.ReadBody(body)
  45. c.Assert(err, checker.IsNil)
  46. // Make sure Dockerfile exists.
  47. // Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL
  48. out := string(buf)
  49. c.Assert(out, checker.Contains, "RUN find /tmp")
  50. c.Assert(out, checker.Not(checker.Contains), "baz")
  51. }
  52. func (s *DockerSuite) TestBuildAPIRemoteTarballContext(c *check.C) {
  53. buffer := new(bytes.Buffer)
  54. tw := tar.NewWriter(buffer)
  55. defer tw.Close()
  56. dockerfile := []byte("FROM busybox")
  57. err := tw.WriteHeader(&tar.Header{
  58. Name: "Dockerfile",
  59. Size: int64(len(dockerfile)),
  60. })
  61. // failed to write tar file header
  62. c.Assert(err, checker.IsNil)
  63. _, err = tw.Write(dockerfile)
  64. // failed to write tar file content
  65. c.Assert(err, checker.IsNil)
  66. // failed to close tar archive
  67. c.Assert(tw.Close(), checker.IsNil)
  68. server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
  69. "testT.tar": buffer,
  70. }))
  71. defer server.Close()
  72. res, b, err := request.Post("/build?remote="+server.URL()+"/testT.tar", request.ContentType("application/tar"))
  73. c.Assert(err, checker.IsNil)
  74. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  75. b.Close()
  76. }
  77. func (s *DockerSuite) TestBuildAPIRemoteTarballContextWithCustomDockerfile(c *check.C) {
  78. buffer := new(bytes.Buffer)
  79. tw := tar.NewWriter(buffer)
  80. defer tw.Close()
  81. dockerfile := []byte(`FROM busybox
  82. RUN echo 'wrong'`)
  83. err := tw.WriteHeader(&tar.Header{
  84. Name: "Dockerfile",
  85. Size: int64(len(dockerfile)),
  86. })
  87. // failed to write tar file header
  88. c.Assert(err, checker.IsNil)
  89. _, err = tw.Write(dockerfile)
  90. // failed to write tar file content
  91. c.Assert(err, checker.IsNil)
  92. custom := []byte(`FROM busybox
  93. RUN echo 'right'
  94. `)
  95. err = tw.WriteHeader(&tar.Header{
  96. Name: "custom",
  97. Size: int64(len(custom)),
  98. })
  99. // failed to write tar file header
  100. c.Assert(err, checker.IsNil)
  101. _, err = tw.Write(custom)
  102. // failed to write tar file content
  103. c.Assert(err, checker.IsNil)
  104. // failed to close tar archive
  105. c.Assert(tw.Close(), checker.IsNil)
  106. server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
  107. "testT.tar": buffer,
  108. }))
  109. defer server.Close()
  110. url := "/build?dockerfile=custom&remote=" + server.URL() + "/testT.tar"
  111. res, body, err := request.Post(url, request.ContentType("application/tar"))
  112. c.Assert(err, checker.IsNil)
  113. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  114. defer body.Close()
  115. content, err := request.ReadBody(body)
  116. c.Assert(err, checker.IsNil)
  117. // Build used the wrong dockerfile.
  118. c.Assert(string(content), checker.Not(checker.Contains), "wrong")
  119. }
  120. func (s *DockerSuite) TestBuildAPILowerDockerfile(c *check.C) {
  121. git := fakegit.New(c, "repo", map[string]string{
  122. "dockerfile": `FROM busybox
  123. RUN echo from dockerfile`,
  124. }, false)
  125. defer git.Close()
  126. res, body, err := request.Post("/build?remote="+git.RepoURL, request.JSON)
  127. c.Assert(err, checker.IsNil)
  128. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  129. buf, err := request.ReadBody(body)
  130. c.Assert(err, checker.IsNil)
  131. out := string(buf)
  132. c.Assert(out, checker.Contains, "from dockerfile")
  133. }
  134. func (s *DockerSuite) TestBuildAPIBuildGitWithF(c *check.C) {
  135. git := fakegit.New(c, "repo", map[string]string{
  136. "baz": `FROM busybox
  137. RUN echo from baz`,
  138. "Dockerfile": `FROM busybox
  139. RUN echo from Dockerfile`,
  140. }, false)
  141. defer git.Close()
  142. // Make sure it tries to 'dockerfile' query param value
  143. res, body, err := request.Post("/build?dockerfile=baz&remote="+git.RepoURL, request.JSON)
  144. c.Assert(err, checker.IsNil)
  145. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  146. buf, err := request.ReadBody(body)
  147. c.Assert(err, checker.IsNil)
  148. out := string(buf)
  149. c.Assert(out, checker.Contains, "from baz")
  150. }
  151. func (s *DockerSuite) TestBuildAPIDoubleDockerfile(c *check.C) {
  152. testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows
  153. git := fakegit.New(c, "repo", map[string]string{
  154. "Dockerfile": `FROM busybox
  155. RUN echo from Dockerfile`,
  156. "dockerfile": `FROM busybox
  157. RUN echo from dockerfile`,
  158. }, false)
  159. defer git.Close()
  160. // Make sure it tries to 'dockerfile' query param value
  161. res, body, err := request.Post("/build?remote="+git.RepoURL, request.JSON)
  162. c.Assert(err, checker.IsNil)
  163. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  164. buf, err := request.ReadBody(body)
  165. c.Assert(err, checker.IsNil)
  166. out := string(buf)
  167. c.Assert(out, checker.Contains, "from Dockerfile")
  168. }
  169. func (s *DockerSuite) TestBuildAPIUnnormalizedTarPaths(c *check.C) {
  170. // Make sure that build context tars with entries of the form
  171. // x/./y don't cause caching false positives.
  172. buildFromTarContext := func(fileContents []byte) string {
  173. buffer := new(bytes.Buffer)
  174. tw := tar.NewWriter(buffer)
  175. defer tw.Close()
  176. dockerfile := []byte(`FROM busybox
  177. COPY dir /dir/`)
  178. err := tw.WriteHeader(&tar.Header{
  179. Name: "Dockerfile",
  180. Size: int64(len(dockerfile)),
  181. })
  182. //failed to write tar file header
  183. c.Assert(err, checker.IsNil)
  184. _, err = tw.Write(dockerfile)
  185. // failed to write Dockerfile in tar file content
  186. c.Assert(err, checker.IsNil)
  187. err = tw.WriteHeader(&tar.Header{
  188. Name: "dir/./file",
  189. Size: int64(len(fileContents)),
  190. })
  191. //failed to write tar file header
  192. c.Assert(err, checker.IsNil)
  193. _, err = tw.Write(fileContents)
  194. // failed to write file contents in tar file content
  195. c.Assert(err, checker.IsNil)
  196. // failed to close tar archive
  197. c.Assert(tw.Close(), checker.IsNil)
  198. res, body, err := request.Post("/build", request.RawContent(ioutil.NopCloser(buffer)), request.ContentType("application/x-tar"))
  199. c.Assert(err, checker.IsNil)
  200. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  201. out, err := request.ReadBody(body)
  202. c.Assert(err, checker.IsNil)
  203. lines := strings.Split(string(out), "\n")
  204. c.Assert(len(lines), checker.GreaterThan, 1)
  205. c.Assert(lines[len(lines)-2], checker.Matches, ".*Successfully built [0-9a-f]{12}.*")
  206. re := regexp.MustCompile("Successfully built ([0-9a-f]{12})")
  207. matches := re.FindStringSubmatch(lines[len(lines)-2])
  208. return matches[1]
  209. }
  210. imageA := buildFromTarContext([]byte("abc"))
  211. imageB := buildFromTarContext([]byte("def"))
  212. c.Assert(imageA, checker.Not(checker.Equals), imageB)
  213. }
  214. func (s *DockerSuite) TestBuildOnBuildWithCopy(c *check.C) {
  215. dockerfile := `
  216. FROM ` + minimalBaseImage() + ` as onbuildbase
  217. ONBUILD COPY file /file
  218. FROM onbuildbase
  219. `
  220. ctx := fakecontext.New(c, "",
  221. fakecontext.WithDockerfile(dockerfile),
  222. fakecontext.WithFile("file", "some content"),
  223. )
  224. defer ctx.Close()
  225. res, body, err := request.Post(
  226. "/build",
  227. request.RawContent(ctx.AsTarReader(c)),
  228. request.ContentType("application/x-tar"))
  229. c.Assert(err, checker.IsNil)
  230. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  231. out, err := request.ReadBody(body)
  232. c.Assert(err, checker.IsNil)
  233. c.Assert(string(out), checker.Contains, "Successfully built")
  234. }
  235. func (s *DockerSuite) TestBuildOnBuildCache(c *check.C) {
  236. build := func(dockerfile string) []byte {
  237. ctx := fakecontext.New(c, "",
  238. fakecontext.WithDockerfile(dockerfile),
  239. )
  240. defer ctx.Close()
  241. res, body, err := request.Post(
  242. "/build",
  243. request.RawContent(ctx.AsTarReader(c)),
  244. request.ContentType("application/x-tar"))
  245. require.NoError(c, err)
  246. assert.Equal(c, http.StatusOK, res.StatusCode)
  247. out, err := request.ReadBody(body)
  248. require.NoError(c, err)
  249. assert.Contains(c, string(out), "Successfully built")
  250. return out
  251. }
  252. dockerfile := `
  253. FROM ` + minimalBaseImage() + ` as onbuildbase
  254. ENV something=bar
  255. ONBUILD ENV foo=bar
  256. `
  257. build(dockerfile)
  258. dockerfile += "FROM onbuildbase"
  259. out := build(dockerfile)
  260. imageIDs := getImageIDsFromBuild(c, out)
  261. assert.Len(c, imageIDs, 2)
  262. parentID, childID := imageIDs[0], imageIDs[1]
  263. client, err := request.NewClient()
  264. require.NoError(c, err)
  265. // check parentID is correct
  266. image, _, err := client.ImageInspectWithRaw(context.Background(), childID)
  267. require.NoError(c, err)
  268. assert.Equal(c, parentID, image.Parent)
  269. }
  270. func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *check.C) {
  271. client, err := request.NewClient()
  272. require.NoError(c, err)
  273. repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
  274. // tag the image to upload it to the private registry
  275. err = client.ImageTag(context.TODO(), "busybox", repoName)
  276. assert.Nil(c, err)
  277. // push the image to the registry
  278. rc, err := client.ImagePush(context.TODO(), repoName, types.ImagePushOptions{RegistryAuth: "{}"})
  279. assert.Nil(c, err)
  280. _, err = io.Copy(ioutil.Discard, rc)
  281. assert.Nil(c, err)
  282. dockerfile := fmt.Sprintf(`
  283. FROM %s AS foo
  284. RUN touch abc
  285. FROM %s
  286. COPY --from=foo /abc /
  287. `, repoName, repoName)
  288. ctx := fakecontext.New(c, "",
  289. fakecontext.WithDockerfile(dockerfile),
  290. )
  291. defer ctx.Close()
  292. res, body, err := request.Post(
  293. "/build?pull=1",
  294. request.RawContent(ctx.AsTarReader(c)),
  295. request.ContentType("application/x-tar"))
  296. require.NoError(c, err)
  297. assert.Equal(c, http.StatusOK, res.StatusCode)
  298. out, err := request.ReadBody(body)
  299. require.NoError(c, err)
  300. assert.Contains(c, string(out), "Successfully built")
  301. }
  302. func (s *DockerSuite) TestBuildAddRemoteNoDecompress(c *check.C) {
  303. buffer := new(bytes.Buffer)
  304. tw := tar.NewWriter(buffer)
  305. dt := []byte("contents")
  306. err := tw.WriteHeader(&tar.Header{
  307. Name: "foo",
  308. Size: int64(len(dt)),
  309. Mode: 0600,
  310. Typeflag: tar.TypeReg,
  311. })
  312. require.NoError(c, err)
  313. _, err = tw.Write(dt)
  314. require.NoError(c, err)
  315. err = tw.Close()
  316. require.NoError(c, err)
  317. server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{
  318. "test.tar": buffer,
  319. }))
  320. defer server.Close()
  321. dockerfile := fmt.Sprintf(`
  322. FROM busybox
  323. ADD %s/test.tar /
  324. RUN [ -f test.tar ]
  325. `, server.URL())
  326. ctx := fakecontext.New(c, "",
  327. fakecontext.WithDockerfile(dockerfile),
  328. )
  329. defer ctx.Close()
  330. res, body, err := request.Post(
  331. "/build",
  332. request.RawContent(ctx.AsTarReader(c)),
  333. request.ContentType("application/x-tar"))
  334. require.NoError(c, err)
  335. assert.Equal(c, http.StatusOK, res.StatusCode)
  336. out, err := request.ReadBody(body)
  337. require.NoError(c, err)
  338. assert.Contains(c, string(out), "Successfully built")
  339. }
  340. func (s *DockerSuite) TestBuildChownOnCopy(c *check.C) {
  341. testRequires(c, DaemonIsLinux)
  342. dockerfile := `FROM busybox
  343. RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd
  344. RUN echo 'test1:x:1001:' >> /etc/group
  345. RUN echo 'test2:x:1002:' >> /etc/group
  346. COPY --chown=test1:1002 . /new_dir
  347. RUN ls -l /
  348. RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]
  349. RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ]
  350. `
  351. ctx := fakecontext.New(c, "",
  352. fakecontext.WithDockerfile(dockerfile),
  353. fakecontext.WithFile("test_file1", "some test content"),
  354. )
  355. defer ctx.Close()
  356. res, body, err := request.Post(
  357. "/build",
  358. request.RawContent(ctx.AsTarReader(c)),
  359. request.ContentType("application/x-tar"))
  360. c.Assert(err, checker.IsNil)
  361. c.Assert(res.StatusCode, checker.Equals, http.StatusOK)
  362. out, err := request.ReadBody(body)
  363. require.NoError(c, err)
  364. assert.Contains(c, string(out), "Successfully built")
  365. }
  366. func (s *DockerSuite) TestBuildWithSession(c *check.C) {
  367. testRequires(c, ExperimentalDaemon)
  368. dockerfile := `
  369. FROM busybox
  370. COPY file /
  371. RUN cat /file
  372. `
  373. fctx := fakecontext.New(c, "",
  374. fakecontext.WithFile("file", "some content"),
  375. )
  376. defer fctx.Close()
  377. out := testBuildWithSession(c, fctx.Dir, dockerfile)
  378. assert.Contains(c, out, "some content")
  379. fctx.Add("second", "contentcontent")
  380. dockerfile += `
  381. COPY second /
  382. RUN cat /second
  383. `
  384. out = testBuildWithSession(c, fctx.Dir, dockerfile)
  385. assert.Equal(c, strings.Count(out, "Using cache"), 2)
  386. assert.Contains(c, out, "contentcontent")
  387. client, err := request.NewClient()
  388. require.NoError(c, err)
  389. du, err := client.DiskUsage(context.TODO())
  390. assert.Nil(c, err)
  391. assert.True(c, du.BuilderSize > 10)
  392. out = testBuildWithSession(c, fctx.Dir, dockerfile)
  393. assert.Equal(c, strings.Count(out, "Using cache"), 4)
  394. du2, err := client.DiskUsage(context.TODO())
  395. assert.Nil(c, err)
  396. assert.Equal(c, du.BuilderSize, du2.BuilderSize)
  397. // rebuild with regular tar, confirm cache still applies
  398. fctx.Add("Dockerfile", dockerfile)
  399. res, body, err := request.Post(
  400. "/build",
  401. request.RawContent(fctx.AsTarReader(c)),
  402. request.ContentType("application/x-tar"))
  403. require.NoError(c, err)
  404. assert.Equal(c, http.StatusOK, res.StatusCode)
  405. outBytes, err := request.ReadBody(body)
  406. require.NoError(c, err)
  407. assert.Contains(c, string(outBytes), "Successfully built")
  408. assert.Equal(c, strings.Count(string(outBytes), "Using cache"), 4)
  409. _, err = client.BuildCachePrune(context.TODO())
  410. assert.Nil(c, err)
  411. du, err = client.DiskUsage(context.TODO())
  412. assert.Nil(c, err)
  413. assert.Equal(c, du.BuilderSize, int64(0))
  414. }
  415. func testBuildWithSession(c *check.C, dir, dockerfile string) (outStr string) {
  416. client, err := request.NewClient()
  417. require.NoError(c, err)
  418. sess, err := session.NewSession("foo1", "foo")
  419. assert.Nil(c, err)
  420. fsProvider := filesync.NewFSSyncProvider(dir, nil)
  421. sess.Allow(fsProvider)
  422. g, ctx := errgroup.WithContext(context.Background())
  423. g.Go(func() error {
  424. return sess.Run(ctx, client.DialSession)
  425. })
  426. g.Go(func() error {
  427. res, body, err := request.Post("/build?remote=client-session&session="+sess.UUID(), func(req *http.Request) error {
  428. req.Body = ioutil.NopCloser(strings.NewReader(dockerfile))
  429. return nil
  430. })
  431. if err != nil {
  432. return err
  433. }
  434. assert.Equal(c, res.StatusCode, http.StatusOK)
  435. out, err := request.ReadBody(body)
  436. require.NoError(c, err)
  437. assert.Contains(c, string(out), "Successfully built")
  438. sess.Close()
  439. outStr = string(out)
  440. return nil
  441. })
  442. err = g.Wait()
  443. assert.Nil(c, err)
  444. return
  445. }
  446. type buildLine struct {
  447. Stream string
  448. Aux struct {
  449. ID string
  450. }
  451. }
  452. func getImageIDsFromBuild(c *check.C, output []byte) []string {
  453. ids := []string{}
  454. for _, line := range bytes.Split(output, []byte("\n")) {
  455. if len(line) == 0 {
  456. continue
  457. }
  458. entry := buildLine{}
  459. require.NoError(c, json.Unmarshal(line, &entry))
  460. if entry.Aux.ID != "" {
  461. ids = append(ids, entry.Aux.ID)
  462. }
  463. }
  464. return ids
  465. }