build_test.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package 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/util/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. type buildLine struct {
  223. Stream string
  224. Aux struct {
  225. ID string
  226. }
  227. }
  228. func getImageIDsFromBuild(output []byte) ([]string, error) {
  229. ids := []string{}
  230. for _, line := range bytes.Split(output, []byte("\n")) {
  231. if len(line) == 0 {
  232. continue
  233. }
  234. entry := buildLine{}
  235. if err := json.Unmarshal(line, &entry); err != nil {
  236. return nil, err
  237. }
  238. if entry.Aux.ID != "" {
  239. ids = append(ids, entry.Aux.ID)
  240. }
  241. }
  242. return ids, nil
  243. }