build_test.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. package build // import "github.com/docker/docker/integration/build"
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "context"
  6. "encoding/json"
  7. "io"
  8. "io/ioutil"
  9. "strings"
  10. "testing"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/api/types/filters"
  13. "github.com/docker/docker/api/types/versions"
  14. "github.com/docker/docker/internal/test/fakecontext"
  15. "github.com/docker/docker/internal/test/request"
  16. "github.com/docker/docker/pkg/jsonmessage"
  17. "github.com/gotestyourself/gotestyourself/assert"
  18. is "github.com/gotestyourself/gotestyourself/assert/cmp"
  19. "github.com/gotestyourself/gotestyourself/skip"
  20. )
  21. func TestBuildWithRemoveAndForceRemove(t *testing.T) {
  22. defer setupTest(t)()
  23. t.Parallel()
  24. cases := []struct {
  25. name string
  26. dockerfile string
  27. numberOfIntermediateContainers int
  28. rm bool
  29. forceRm bool
  30. }{
  31. {
  32. name: "successful build with no removal",
  33. dockerfile: `FROM busybox
  34. RUN exit 0
  35. RUN exit 0`,
  36. numberOfIntermediateContainers: 2,
  37. rm: false,
  38. forceRm: false,
  39. },
  40. {
  41. name: "successful build with remove",
  42. dockerfile: `FROM busybox
  43. RUN exit 0
  44. RUN exit 0`,
  45. numberOfIntermediateContainers: 0,
  46. rm: true,
  47. forceRm: false,
  48. },
  49. {
  50. name: "successful build with remove and force remove",
  51. dockerfile: `FROM busybox
  52. RUN exit 0
  53. RUN exit 0`,
  54. numberOfIntermediateContainers: 0,
  55. rm: true,
  56. forceRm: true,
  57. },
  58. {
  59. name: "failed build with no removal",
  60. dockerfile: `FROM busybox
  61. RUN exit 0
  62. RUN exit 1`,
  63. numberOfIntermediateContainers: 2,
  64. rm: false,
  65. forceRm: false,
  66. },
  67. {
  68. name: "failed build with remove",
  69. dockerfile: `FROM busybox
  70. RUN exit 0
  71. RUN exit 1`,
  72. numberOfIntermediateContainers: 1,
  73. rm: true,
  74. forceRm: false,
  75. },
  76. {
  77. name: "failed build with remove and force remove",
  78. dockerfile: `FROM busybox
  79. RUN exit 0
  80. RUN exit 1`,
  81. numberOfIntermediateContainers: 0,
  82. rm: true,
  83. forceRm: true,
  84. },
  85. }
  86. client := request.NewAPIClient(t)
  87. ctx := context.Background()
  88. for _, c := range cases {
  89. t.Run(c.name, func(t *testing.T) {
  90. t.Parallel()
  91. dockerfile := []byte(c.dockerfile)
  92. buff := bytes.NewBuffer(nil)
  93. tw := tar.NewWriter(buff)
  94. assert.NilError(t, tw.WriteHeader(&tar.Header{
  95. Name: "Dockerfile",
  96. Size: int64(len(dockerfile)),
  97. }))
  98. _, err := tw.Write(dockerfile)
  99. assert.NilError(t, err)
  100. assert.NilError(t, tw.Close())
  101. resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
  102. assert.NilError(t, err)
  103. defer resp.Body.Close()
  104. filter, err := buildContainerIdsFilter(resp.Body)
  105. assert.NilError(t, err)
  106. remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
  107. assert.NilError(t, err)
  108. assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
  109. })
  110. }
  111. }
  112. func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
  113. const intermediateContainerPrefix = " ---> Running in "
  114. filter := filters.NewArgs()
  115. dec := json.NewDecoder(buildOutput)
  116. for {
  117. m := jsonmessage.JSONMessage{}
  118. err := dec.Decode(&m)
  119. if err == io.EOF {
  120. return filter, nil
  121. }
  122. if err != nil {
  123. return filter, err
  124. }
  125. if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
  126. filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
  127. }
  128. }
  129. }
  130. func TestBuildMultiStageParentConfig(t *testing.T) {
  131. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
  132. dockerfile := `
  133. FROM busybox AS stage0
  134. ENV WHO=parent
  135. WORKDIR /foo
  136. FROM stage0
  137. ENV WHO=sibling1
  138. WORKDIR sub1
  139. FROM stage0
  140. WORKDIR sub2
  141. `
  142. ctx := context.Background()
  143. source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
  144. defer source.Close()
  145. apiclient := testEnv.APIClient()
  146. resp, err := apiclient.ImageBuild(ctx,
  147. source.AsTarReader(t),
  148. types.ImageBuildOptions{
  149. Remove: true,
  150. ForceRemove: true,
  151. Tags: []string{"build1"},
  152. })
  153. assert.NilError(t, err)
  154. _, err = io.Copy(ioutil.Discard, resp.Body)
  155. resp.Body.Close()
  156. assert.NilError(t, err)
  157. image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
  158. assert.NilError(t, err)
  159. assert.Check(t, is.Equal("/foo/sub2", image.Config.WorkingDir))
  160. assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
  161. }
  162. func TestBuildWithEmptyLayers(t *testing.T) {
  163. dockerfile := `
  164. FROM busybox
  165. COPY 1/ /target/
  166. COPY 2/ /target/
  167. COPY 3/ /target/
  168. `
  169. ctx := context.Background()
  170. source := fakecontext.New(t, "",
  171. fakecontext.WithDockerfile(dockerfile),
  172. fakecontext.WithFile("1/a", "asdf"),
  173. fakecontext.WithFile("2/a", "asdf"),
  174. fakecontext.WithFile("3/a", "asdf"))
  175. defer source.Close()
  176. apiclient := testEnv.APIClient()
  177. resp, err := apiclient.ImageBuild(ctx,
  178. source.AsTarReader(t),
  179. types.ImageBuildOptions{
  180. Remove: true,
  181. ForceRemove: true,
  182. })
  183. assert.NilError(t, err)
  184. _, err = io.Copy(ioutil.Discard, resp.Body)
  185. resp.Body.Close()
  186. assert.NilError(t, err)
  187. }
  188. // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
  189. // multiple subsequent stages
  190. // #35652
  191. func TestBuildMultiStageOnBuild(t *testing.T) {
  192. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
  193. defer setupTest(t)()
  194. // test both metadata and layer based commands as they may be implemented differently
  195. dockerfile := `FROM busybox AS stage1
  196. ONBUILD RUN echo 'foo' >somefile
  197. ONBUILD ENV bar=baz
  198. FROM stage1
  199. RUN cat somefile # fails if ONBUILD RUN fails
  200. FROM stage1
  201. RUN cat somefile`
  202. ctx := context.Background()
  203. source := fakecontext.New(t, "",
  204. fakecontext.WithDockerfile(dockerfile))
  205. defer source.Close()
  206. apiclient := testEnv.APIClient()
  207. resp, err := apiclient.ImageBuild(ctx,
  208. source.AsTarReader(t),
  209. types.ImageBuildOptions{
  210. Remove: true,
  211. ForceRemove: true,
  212. })
  213. out := bytes.NewBuffer(nil)
  214. assert.NilError(t, err)
  215. _, err = io.Copy(out, resp.Body)
  216. resp.Body.Close()
  217. assert.NilError(t, err)
  218. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  219. imageIDs, err := getImageIDsFromBuild(out.Bytes())
  220. assert.NilError(t, err)
  221. assert.Check(t, is.Equal(3, len(imageIDs)))
  222. image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
  223. assert.NilError(t, err)
  224. assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
  225. }
  226. // #35403 #36122
  227. func TestBuildUncleanTarFilenames(t *testing.T) {
  228. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  229. ctx := context.TODO()
  230. defer setupTest(t)()
  231. dockerfile := `FROM scratch
  232. COPY foo /
  233. FROM scratch
  234. COPY bar /`
  235. buf := bytes.NewBuffer(nil)
  236. w := tar.NewWriter(buf)
  237. writeTarRecord(t, w, "Dockerfile", dockerfile)
  238. writeTarRecord(t, w, "../foo", "foocontents0")
  239. writeTarRecord(t, w, "/bar", "barcontents0")
  240. err := w.Close()
  241. assert.NilError(t, err)
  242. apiclient := testEnv.APIClient()
  243. resp, err := apiclient.ImageBuild(ctx,
  244. buf,
  245. types.ImageBuildOptions{
  246. Remove: true,
  247. ForceRemove: true,
  248. })
  249. out := bytes.NewBuffer(nil)
  250. assert.NilError(t, err)
  251. _, err = io.Copy(out, resp.Body)
  252. resp.Body.Close()
  253. assert.NilError(t, err)
  254. // repeat with changed data should not cause cache hits
  255. buf = bytes.NewBuffer(nil)
  256. w = tar.NewWriter(buf)
  257. writeTarRecord(t, w, "Dockerfile", dockerfile)
  258. writeTarRecord(t, w, "../foo", "foocontents1")
  259. writeTarRecord(t, w, "/bar", "barcontents1")
  260. err = w.Close()
  261. assert.NilError(t, err)
  262. resp, err = apiclient.ImageBuild(ctx,
  263. buf,
  264. types.ImageBuildOptions{
  265. Remove: true,
  266. ForceRemove: true,
  267. })
  268. out = bytes.NewBuffer(nil)
  269. assert.NilError(t, err)
  270. _, err = io.Copy(out, resp.Body)
  271. resp.Body.Close()
  272. assert.NilError(t, err)
  273. assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
  274. }
  275. // docker/for-linux#135
  276. // #35641
  277. func TestBuildMultiStageLayerLeak(t *testing.T) {
  278. skip.IfCondition(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  279. ctx := context.TODO()
  280. defer setupTest(t)()
  281. // all commands need to match until COPY
  282. dockerfile := `FROM busybox
  283. WORKDIR /foo
  284. COPY foo .
  285. FROM busybox
  286. WORKDIR /foo
  287. COPY bar .
  288. RUN [ -f bar ]
  289. RUN [ ! -f foo ]
  290. `
  291. source := fakecontext.New(t, "",
  292. fakecontext.WithFile("foo", "0"),
  293. fakecontext.WithFile("bar", "1"),
  294. fakecontext.WithDockerfile(dockerfile))
  295. defer source.Close()
  296. apiclient := testEnv.APIClient()
  297. resp, err := apiclient.ImageBuild(ctx,
  298. source.AsTarReader(t),
  299. types.ImageBuildOptions{
  300. Remove: true,
  301. ForceRemove: true,
  302. })
  303. out := bytes.NewBuffer(nil)
  304. assert.NilError(t, err)
  305. _, err = io.Copy(out, resp.Body)
  306. resp.Body.Close()
  307. assert.NilError(t, err)
  308. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  309. }
  310. func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
  311. err := w.WriteHeader(&tar.Header{
  312. Name: fn,
  313. Mode: 0600,
  314. Size: int64(len(contents)),
  315. Typeflag: '0',
  316. })
  317. assert.NilError(t, err)
  318. _, err = w.Write([]byte(contents))
  319. assert.NilError(t, err)
  320. }
  321. type buildLine struct {
  322. Stream string
  323. Aux struct {
  324. ID string
  325. }
  326. }
  327. func getImageIDsFromBuild(output []byte) ([]string, error) {
  328. ids := []string{}
  329. for _, line := range bytes.Split(output, []byte("\n")) {
  330. if len(line) == 0 {
  331. continue
  332. }
  333. entry := buildLine{}
  334. if err := json.Unmarshal(line, &entry); err != nil {
  335. return nil, err
  336. }
  337. if entry.Aux.ID != "" {
  338. ids = append(ids, entry.Aux.ID)
  339. }
  340. }
  341. return ids, nil
  342. }