build_test.go 8.1 KB

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