dispatchers_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. package dockerfile // import "github.com/docker/docker/builder/dockerfile"
  2. import (
  3. "bytes"
  4. "context"
  5. "runtime"
  6. "testing"
  7. "github.com/containerd/containerd/platforms"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/api/types/backend"
  10. "github.com/docker/docker/api/types/container"
  11. "github.com/docker/docker/api/types/strslice"
  12. "github.com/docker/docker/builder"
  13. "github.com/docker/docker/image"
  14. "github.com/docker/docker/pkg/system"
  15. "github.com/docker/go-connections/nat"
  16. "github.com/moby/buildkit/frontend/dockerfile/instructions"
  17. "github.com/moby/buildkit/frontend/dockerfile/shell"
  18. "gotest.tools/assert"
  19. is "gotest.tools/assert/cmp"
  20. )
  21. func newBuilderWithMockBackend() *Builder {
  22. mockBackend := &MockBackend{}
  23. defaultPlatform := platforms.DefaultSpec()
  24. opts := &types.ImageBuildOptions{Platform: &defaultPlatform}
  25. ctx := context.Background()
  26. b := &Builder{
  27. options: opts,
  28. docker: mockBackend,
  29. Stdout: new(bytes.Buffer),
  30. clientCtx: ctx,
  31. disableCommit: true,
  32. imageSources: newImageSources(ctx, builderOptions{
  33. Options: opts,
  34. Backend: mockBackend,
  35. }),
  36. imageProber: newImageProber(mockBackend, nil, false),
  37. containerManager: newContainerManager(mockBackend),
  38. }
  39. return b
  40. }
  41. func TestEnv2Variables(t *testing.T) {
  42. b := newBuilderWithMockBackend()
  43. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  44. envCommand := &instructions.EnvCommand{
  45. Env: instructions.KeyValuePairs{
  46. instructions.KeyValuePair{Key: "var1", Value: "val1"},
  47. instructions.KeyValuePair{Key: "var2", Value: "val2"},
  48. },
  49. }
  50. err := dispatch(sb, envCommand)
  51. assert.NilError(t, err)
  52. expected := []string{
  53. "var1=val1",
  54. "var2=val2",
  55. }
  56. assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
  57. }
  58. func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
  59. b := newBuilderWithMockBackend()
  60. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  61. sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
  62. envCommand := &instructions.EnvCommand{
  63. Env: instructions.KeyValuePairs{
  64. instructions.KeyValuePair{Key: "var1", Value: "val1"},
  65. },
  66. }
  67. err := dispatch(sb, envCommand)
  68. assert.NilError(t, err)
  69. expected := []string{
  70. "var1=val1",
  71. "var2=fromenv",
  72. }
  73. assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
  74. }
  75. func TestMaintainer(t *testing.T) {
  76. maintainerEntry := "Some Maintainer <maintainer@example.com>"
  77. b := newBuilderWithMockBackend()
  78. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  79. cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry}
  80. err := dispatch(sb, cmd)
  81. assert.NilError(t, err)
  82. assert.Check(t, is.Equal(maintainerEntry, sb.state.maintainer))
  83. }
  84. func TestLabel(t *testing.T) {
  85. labelName := "label"
  86. labelValue := "value"
  87. b := newBuilderWithMockBackend()
  88. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  89. cmd := &instructions.LabelCommand{
  90. Labels: instructions.KeyValuePairs{
  91. instructions.KeyValuePair{Key: labelName, Value: labelValue},
  92. },
  93. }
  94. err := dispatch(sb, cmd)
  95. assert.NilError(t, err)
  96. assert.Assert(t, is.Contains(sb.state.runConfig.Labels, labelName))
  97. assert.Check(t, is.Equal(sb.state.runConfig.Labels[labelName], labelValue))
  98. }
  99. func TestFromScratch(t *testing.T) {
  100. b := newBuilderWithMockBackend()
  101. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  102. cmd := &instructions.Stage{
  103. BaseName: "scratch",
  104. }
  105. err := initializeStage(sb, cmd)
  106. if runtime.GOOS == "windows" && !system.LCOWSupported() {
  107. assert.Check(t, is.Error(err, "Windows does not support FROM scratch"))
  108. return
  109. }
  110. assert.NilError(t, err)
  111. assert.Check(t, sb.state.hasFromImage())
  112. assert.Check(t, is.Equal("", sb.state.imageID))
  113. expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
  114. assert.Check(t, is.DeepEqual([]string{expected}, sb.state.runConfig.Env))
  115. }
  116. func TestFromWithArg(t *testing.T) {
  117. tag, expected := ":sometag", "expectedthisid"
  118. getImage := func(name string) (builder.Image, builder.ROLayer, error) {
  119. assert.Check(t, is.Equal("alpine"+tag, name))
  120. return &mockImage{id: "expectedthisid"}, nil, nil
  121. }
  122. b := newBuilderWithMockBackend()
  123. b.docker.(*MockBackend).getImageFunc = getImage
  124. args := NewBuildArgs(make(map[string]*string))
  125. val := "sometag"
  126. metaArg := instructions.ArgCommand{
  127. Key: "THETAG",
  128. Value: &val,
  129. }
  130. cmd := &instructions.Stage{
  131. BaseName: "alpine:${THETAG}",
  132. }
  133. err := processMetaArg(metaArg, shell.NewLex('\\'), args)
  134. sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
  135. assert.NilError(t, err)
  136. err = initializeStage(sb, cmd)
  137. assert.NilError(t, err)
  138. assert.Check(t, is.Equal(expected, sb.state.imageID))
  139. assert.Check(t, is.Equal(expected, sb.state.baseImage.ImageID()))
  140. assert.Check(t, is.Len(sb.state.buildArgs.GetAllAllowed(), 0))
  141. assert.Check(t, is.Len(sb.state.buildArgs.GetAllMeta(), 1))
  142. }
  143. func TestFromWithUndefinedArg(t *testing.T) {
  144. tag, expected := "sometag", "expectedthisid"
  145. getImage := func(name string) (builder.Image, builder.ROLayer, error) {
  146. assert.Check(t, is.Equal("alpine", name))
  147. return &mockImage{id: "expectedthisid"}, nil, nil
  148. }
  149. b := newBuilderWithMockBackend()
  150. b.docker.(*MockBackend).getImageFunc = getImage
  151. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  152. b.options.BuildArgs = map[string]*string{"THETAG": &tag}
  153. cmd := &instructions.Stage{
  154. BaseName: "alpine${THETAG}",
  155. }
  156. err := initializeStage(sb, cmd)
  157. assert.NilError(t, err)
  158. assert.Check(t, is.Equal(expected, sb.state.imageID))
  159. }
  160. func TestFromMultiStageWithNamedStage(t *testing.T) {
  161. b := newBuilderWithMockBackend()
  162. firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
  163. secondFrom := &instructions.Stage{BaseName: "base"}
  164. previousResults := newStagesBuildResults()
  165. firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
  166. secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
  167. err := initializeStage(firstSB, firstFrom)
  168. assert.NilError(t, err)
  169. assert.Check(t, firstSB.state.hasFromImage())
  170. previousResults.indexed["base"] = firstSB.state.runConfig
  171. previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
  172. err = initializeStage(secondSB, secondFrom)
  173. assert.NilError(t, err)
  174. assert.Check(t, secondSB.state.hasFromImage())
  175. }
  176. func TestOnbuild(t *testing.T) {
  177. b := newBuilderWithMockBackend()
  178. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  179. cmd := &instructions.OnbuildCommand{
  180. Expression: "ADD . /app/src",
  181. }
  182. err := dispatch(sb, cmd)
  183. assert.NilError(t, err)
  184. assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0]))
  185. }
  186. func TestWorkdir(t *testing.T) {
  187. b := newBuilderWithMockBackend()
  188. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  189. sb.state.baseImage = &mockImage{}
  190. workingDir := "/app"
  191. if runtime.GOOS == "windows" {
  192. workingDir = "C:\\app"
  193. }
  194. cmd := &instructions.WorkdirCommand{
  195. Path: workingDir,
  196. }
  197. err := dispatch(sb, cmd)
  198. assert.NilError(t, err)
  199. assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir))
  200. }
  201. func TestCmd(t *testing.T) {
  202. b := newBuilderWithMockBackend()
  203. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  204. sb.state.baseImage = &mockImage{}
  205. command := "./executable"
  206. cmd := &instructions.CmdCommand{
  207. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  208. CmdLine: strslice.StrSlice{command},
  209. PrependShell: true,
  210. },
  211. }
  212. err := dispatch(sb, cmd)
  213. assert.NilError(t, err)
  214. var expectedCommand strslice.StrSlice
  215. if runtime.GOOS == "windows" {
  216. expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
  217. } else {
  218. expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
  219. }
  220. assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd))
  221. assert.Check(t, sb.state.cmdSet)
  222. }
  223. func TestHealthcheckNone(t *testing.T) {
  224. b := newBuilderWithMockBackend()
  225. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  226. cmd := &instructions.HealthCheckCommand{
  227. Health: &container.HealthConfig{
  228. Test: []string{"NONE"},
  229. },
  230. }
  231. err := dispatch(sb, cmd)
  232. assert.NilError(t, err)
  233. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  234. assert.Check(t, is.DeepEqual([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test))
  235. }
  236. func TestHealthcheckCmd(t *testing.T) {
  237. b := newBuilderWithMockBackend()
  238. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  239. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  240. cmd := &instructions.HealthCheckCommand{
  241. Health: &container.HealthConfig{
  242. Test: expectedTest,
  243. },
  244. }
  245. err := dispatch(sb, cmd)
  246. assert.NilError(t, err)
  247. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  248. assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
  249. }
  250. func TestEntrypoint(t *testing.T) {
  251. b := newBuilderWithMockBackend()
  252. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  253. sb.state.baseImage = &mockImage{}
  254. entrypointCmd := "/usr/sbin/nginx"
  255. cmd := &instructions.EntrypointCommand{
  256. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  257. CmdLine: strslice.StrSlice{entrypointCmd},
  258. PrependShell: true,
  259. },
  260. }
  261. err := dispatch(sb, cmd)
  262. assert.NilError(t, err)
  263. assert.Assert(t, sb.state.runConfig.Entrypoint != nil)
  264. var expectedEntrypoint strslice.StrSlice
  265. if runtime.GOOS == "windows" {
  266. expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
  267. } else {
  268. expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
  269. }
  270. assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint))
  271. }
  272. func TestExpose(t *testing.T) {
  273. b := newBuilderWithMockBackend()
  274. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  275. exposedPort := "80"
  276. cmd := &instructions.ExposeCommand{
  277. Ports: []string{exposedPort},
  278. }
  279. err := dispatch(sb, cmd)
  280. assert.NilError(t, err)
  281. assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
  282. assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
  283. portsMapping, err := nat.ParsePortSpec(exposedPort)
  284. assert.NilError(t, err)
  285. assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port))
  286. }
  287. func TestUser(t *testing.T) {
  288. b := newBuilderWithMockBackend()
  289. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  290. cmd := &instructions.UserCommand{
  291. User: "test",
  292. }
  293. err := dispatch(sb, cmd)
  294. assert.NilError(t, err)
  295. assert.Check(t, is.Equal("test", sb.state.runConfig.User))
  296. }
  297. func TestVolume(t *testing.T) {
  298. b := newBuilderWithMockBackend()
  299. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  300. exposedVolume := "/foo"
  301. cmd := &instructions.VolumeCommand{
  302. Volumes: []string{exposedVolume},
  303. }
  304. err := dispatch(sb, cmd)
  305. assert.NilError(t, err)
  306. assert.Assert(t, sb.state.runConfig.Volumes != nil)
  307. assert.Check(t, is.Len(sb.state.runConfig.Volumes, 1))
  308. assert.Check(t, is.Contains(sb.state.runConfig.Volumes, exposedVolume))
  309. }
  310. func TestStopSignal(t *testing.T) {
  311. if runtime.GOOS == "windows" {
  312. t.Skip("Windows does not support stopsignal")
  313. return
  314. }
  315. b := newBuilderWithMockBackend()
  316. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  317. sb.state.baseImage = &mockImage{}
  318. signal := "SIGKILL"
  319. cmd := &instructions.StopSignalCommand{
  320. Signal: signal,
  321. }
  322. err := dispatch(sb, cmd)
  323. assert.NilError(t, err)
  324. assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal))
  325. }
  326. func TestArg(t *testing.T) {
  327. b := newBuilderWithMockBackend()
  328. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  329. argName := "foo"
  330. argVal := "bar"
  331. cmd := &instructions.ArgCommand{Key: argName, Value: &argVal}
  332. err := dispatch(sb, cmd)
  333. assert.NilError(t, err)
  334. expected := map[string]string{argName: argVal}
  335. assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed()))
  336. }
  337. func TestShell(t *testing.T) {
  338. b := newBuilderWithMockBackend()
  339. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  340. shellCmd := "powershell"
  341. cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
  342. err := dispatch(sb, cmd)
  343. assert.NilError(t, err)
  344. expectedShell := strslice.StrSlice([]string{shellCmd})
  345. assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell))
  346. }
  347. func TestPrependEnvOnCmd(t *testing.T) {
  348. buildArgs := NewBuildArgs(nil)
  349. buildArgs.AddArg("NO_PROXY", nil)
  350. args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
  351. cmd := []string{"foo", "bar"}
  352. cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
  353. expected := strslice.StrSlice([]string{
  354. "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
  355. assert.Check(t, is.DeepEqual(expected, cmdWithEnv))
  356. }
  357. func TestRunWithBuildArgs(t *testing.T) {
  358. b := newBuilderWithMockBackend()
  359. args := NewBuildArgs(make(map[string]*string))
  360. args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
  361. b.disableCommit = false
  362. sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
  363. runConfig := &container.Config{}
  364. origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
  365. cmdWithShell := strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
  366. envVars := []string{"|1", "one=two"}
  367. cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
  368. imageCache := &mockImageCache{
  369. getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
  370. // Check the runConfig.Cmd sent to probeCache()
  371. assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd))
  372. assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint))
  373. return "", nil
  374. },
  375. }
  376. mockBackend := b.docker.(*MockBackend)
  377. mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
  378. return imageCache
  379. }
  380. b.imageProber = newImageProber(mockBackend, nil, false)
  381. mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
  382. return &mockImage{
  383. id: "abcdef",
  384. config: &container.Config{Cmd: origCmd},
  385. }, nil, nil
  386. }
  387. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  388. // Check the runConfig.Cmd sent to create()
  389. assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd))
  390. assert.Check(t, is.Contains(config.Config.Env, "one=two"))
  391. assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint))
  392. return container.ContainerCreateCreatedBody{ID: "12345"}, nil
  393. }
  394. mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
  395. // Check the runConfig.Cmd sent to commit()
  396. assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd))
  397. assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd))
  398. assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint))
  399. return "", nil
  400. }
  401. from := &instructions.Stage{BaseName: "abcdef"}
  402. err := initializeStage(sb, from)
  403. assert.NilError(t, err)
  404. sb.state.buildArgs.AddArg("one", strPtr("two"))
  405. run := &instructions.RunCommand{
  406. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  407. CmdLine: strslice.StrSlice{"echo foo"},
  408. PrependShell: true,
  409. },
  410. }
  411. assert.NilError(t, dispatch(sb, run))
  412. // Check that runConfig.Cmd has not been modified by run
  413. assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd))
  414. }