dispatchers_test.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. package dockerfile // import "github.com/docker/docker/builder/dockerfile"
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "runtime"
  7. "strings"
  8. "testing"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/backend"
  11. "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/api/types/strslice"
  13. "github.com/docker/docker/builder"
  14. "github.com/docker/docker/image"
  15. "github.com/docker/docker/pkg/system"
  16. "github.com/docker/go-connections/nat"
  17. "github.com/moby/buildkit/frontend/dockerfile/instructions"
  18. "github.com/moby/buildkit/frontend/dockerfile/parser"
  19. "github.com/moby/buildkit/frontend/dockerfile/shell"
  20. "gotest.tools/v3/assert"
  21. is "gotest.tools/v3/assert/cmp"
  22. )
  23. func newBuilderWithMockBackend() *Builder {
  24. mockBackend := &MockBackend{}
  25. opts := &types.ImageBuildOptions{}
  26. ctx := context.Background()
  27. b := &Builder{
  28. options: opts,
  29. docker: mockBackend,
  30. Stdout: new(bytes.Buffer),
  31. clientCtx: ctx,
  32. disableCommit: true,
  33. imageSources: newImageSources(ctx, builderOptions{
  34. Options: opts,
  35. Backend: mockBackend,
  36. }),
  37. imageProber: newImageProber(mockBackend, nil, false),
  38. containerManager: newContainerManager(mockBackend),
  39. }
  40. return b
  41. }
  42. func TestEnv2Variables(t *testing.T) {
  43. b := newBuilderWithMockBackend()
  44. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  45. envCommand := &instructions.EnvCommand{
  46. Env: instructions.KeyValuePairs{
  47. instructions.KeyValuePair{Key: "var1", Value: "val1"},
  48. instructions.KeyValuePair{Key: "var2", Value: "val2"},
  49. },
  50. }
  51. err := dispatch(sb, envCommand)
  52. assert.NilError(t, err)
  53. expected := []string{
  54. "var1=val1",
  55. "var2=val2",
  56. }
  57. assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
  58. }
  59. func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
  60. b := newBuilderWithMockBackend()
  61. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  62. sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
  63. envCommand := &instructions.EnvCommand{
  64. Env: instructions.KeyValuePairs{
  65. instructions.KeyValuePair{Key: "var1", Value: "val1"},
  66. },
  67. }
  68. err := dispatch(sb, envCommand)
  69. assert.NilError(t, err)
  70. expected := []string{
  71. "var1=val1",
  72. "var2=fromenv",
  73. }
  74. assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
  75. }
  76. func TestMaintainer(t *testing.T) {
  77. maintainerEntry := "Some Maintainer <maintainer@example.com>"
  78. b := newBuilderWithMockBackend()
  79. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  80. cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry}
  81. err := dispatch(sb, cmd)
  82. assert.NilError(t, err)
  83. assert.Check(t, is.Equal(maintainerEntry, sb.state.maintainer))
  84. }
  85. func TestLabel(t *testing.T) {
  86. labelName := "label"
  87. labelValue := "value"
  88. b := newBuilderWithMockBackend()
  89. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  90. cmd := &instructions.LabelCommand{
  91. Labels: instructions.KeyValuePairs{
  92. instructions.KeyValuePair{Key: labelName, Value: labelValue},
  93. },
  94. }
  95. err := dispatch(sb, cmd)
  96. assert.NilError(t, err)
  97. assert.Assert(t, is.Contains(sb.state.runConfig.Labels, labelName))
  98. assert.Check(t, is.Equal(sb.state.runConfig.Labels[labelName], labelValue))
  99. }
  100. func TestFromScratch(t *testing.T) {
  101. b := newBuilderWithMockBackend()
  102. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  103. cmd := &instructions.Stage{
  104. BaseName: "scratch",
  105. }
  106. err := initializeStage(sb, cmd)
  107. if runtime.GOOS == "windows" {
  108. assert.Check(t, is.Error(err, "Linux containers are not supported on this system"))
  109. return
  110. }
  111. assert.NilError(t, err)
  112. assert.Check(t, sb.state.hasFromImage())
  113. assert.Check(t, is.Equal("", sb.state.imageID))
  114. expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
  115. assert.Check(t, is.DeepEqual([]string{expected}, sb.state.runConfig.Env))
  116. }
  117. func TestFromWithArg(t *testing.T) {
  118. tag, expected := ":sometag", "expectedthisid"
  119. getImage := func(name string) (builder.Image, builder.ROLayer, error) {
  120. assert.Check(t, is.Equal("alpine"+tag, name))
  121. return &mockImage{id: "expectedthisid"}, nil, nil
  122. }
  123. b := newBuilderWithMockBackend()
  124. b.docker.(*MockBackend).getImageFunc = getImage
  125. args := NewBuildArgs(make(map[string]*string))
  126. val := "sometag"
  127. metaArg := instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{
  128. Key: "THETAG",
  129. Value: &val,
  130. }}}
  131. cmd := &instructions.Stage{
  132. BaseName: "alpine:${THETAG}",
  133. }
  134. err := processMetaArg(metaArg, shell.NewLex('\\'), args)
  135. sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
  136. assert.NilError(t, err)
  137. err = initializeStage(sb, cmd)
  138. assert.NilError(t, err)
  139. assert.Check(t, is.Equal(expected, sb.state.imageID))
  140. assert.Check(t, is.Equal(expected, sb.state.baseImage.ImageID()))
  141. assert.Check(t, is.Len(sb.state.buildArgs.GetAllAllowed(), 0))
  142. assert.Check(t, is.Len(sb.state.buildArgs.GetAllMeta(), 1))
  143. }
  144. func TestFromWithArgButBuildArgsNotGiven(t *testing.T) {
  145. b := newBuilderWithMockBackend()
  146. args := NewBuildArgs(make(map[string]*string))
  147. metaArg := instructions.ArgCommand{}
  148. cmd := &instructions.Stage{
  149. BaseName: "${THETAG}",
  150. }
  151. err := processMetaArg(metaArg, shell.NewLex('\\'), args)
  152. sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
  153. assert.NilError(t, err)
  154. err = initializeStage(sb, cmd)
  155. assert.Error(t, err, "base name (${THETAG}) should not be blank")
  156. }
  157. func TestFromWithUndefinedArg(t *testing.T) {
  158. tag, expected := "sometag", "expectedthisid"
  159. getImage := func(name string) (builder.Image, builder.ROLayer, error) {
  160. assert.Check(t, is.Equal("alpine", name))
  161. return &mockImage{id: "expectedthisid"}, nil, nil
  162. }
  163. b := newBuilderWithMockBackend()
  164. b.docker.(*MockBackend).getImageFunc = getImage
  165. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  166. b.options.BuildArgs = map[string]*string{"THETAG": &tag}
  167. cmd := &instructions.Stage{
  168. BaseName: "alpine${THETAG}",
  169. }
  170. err := initializeStage(sb, cmd)
  171. assert.NilError(t, err)
  172. assert.Check(t, is.Equal(expected, sb.state.imageID))
  173. }
  174. func TestFromMultiStageWithNamedStage(t *testing.T) {
  175. b := newBuilderWithMockBackend()
  176. firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
  177. secondFrom := &instructions.Stage{BaseName: "base"}
  178. previousResults := newStagesBuildResults()
  179. firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
  180. secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
  181. err := initializeStage(firstSB, firstFrom)
  182. assert.NilError(t, err)
  183. assert.Check(t, firstSB.state.hasFromImage())
  184. previousResults.indexed["base"] = firstSB.state.runConfig
  185. previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
  186. err = initializeStage(secondSB, secondFrom)
  187. assert.NilError(t, err)
  188. assert.Check(t, secondSB.state.hasFromImage())
  189. }
  190. func TestOnbuild(t *testing.T) {
  191. b := newBuilderWithMockBackend()
  192. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  193. cmd := &instructions.OnbuildCommand{
  194. Expression: "ADD . /app/src",
  195. }
  196. err := dispatch(sb, cmd)
  197. assert.NilError(t, err)
  198. assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0]))
  199. }
  200. func TestWorkdir(t *testing.T) {
  201. b := newBuilderWithMockBackend()
  202. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  203. sb.state.baseImage = &mockImage{}
  204. workingDir := "/app"
  205. if runtime.GOOS == "windows" {
  206. workingDir = "C:\\app"
  207. }
  208. cmd := &instructions.WorkdirCommand{
  209. Path: workingDir,
  210. }
  211. err := dispatch(sb, cmd)
  212. assert.NilError(t, err)
  213. assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir))
  214. }
  215. func TestCmd(t *testing.T) {
  216. b := newBuilderWithMockBackend()
  217. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  218. sb.state.baseImage = &mockImage{}
  219. command := "./executable"
  220. cmd := &instructions.CmdCommand{
  221. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  222. CmdLine: strslice.StrSlice{command},
  223. PrependShell: true,
  224. },
  225. }
  226. err := dispatch(sb, cmd)
  227. assert.NilError(t, err)
  228. var expectedCommand strslice.StrSlice
  229. if runtime.GOOS == "windows" {
  230. expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
  231. } else {
  232. expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
  233. }
  234. assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd))
  235. assert.Check(t, sb.state.cmdSet)
  236. }
  237. func TestHealthcheckNone(t *testing.T) {
  238. b := newBuilderWithMockBackend()
  239. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  240. cmd := &instructions.HealthCheckCommand{
  241. Health: &container.HealthConfig{
  242. Test: []string{"NONE"},
  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([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test))
  249. }
  250. func TestHealthcheckCmd(t *testing.T) {
  251. b := newBuilderWithMockBackend()
  252. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  253. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  254. cmd := &instructions.HealthCheckCommand{
  255. Health: &container.HealthConfig{
  256. Test: expectedTest,
  257. },
  258. }
  259. err := dispatch(sb, cmd)
  260. assert.NilError(t, err)
  261. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  262. assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
  263. }
  264. func TestEntrypoint(t *testing.T) {
  265. b := newBuilderWithMockBackend()
  266. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  267. sb.state.baseImage = &mockImage{}
  268. entrypointCmd := "/usr/sbin/nginx"
  269. cmd := &instructions.EntrypointCommand{
  270. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  271. CmdLine: strslice.StrSlice{entrypointCmd},
  272. PrependShell: true,
  273. },
  274. }
  275. err := dispatch(sb, cmd)
  276. assert.NilError(t, err)
  277. assert.Assert(t, sb.state.runConfig.Entrypoint != nil)
  278. var expectedEntrypoint strslice.StrSlice
  279. if runtime.GOOS == "windows" {
  280. expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
  281. } else {
  282. expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
  283. }
  284. assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint))
  285. }
  286. func TestExpose(t *testing.T) {
  287. b := newBuilderWithMockBackend()
  288. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  289. exposedPort := "80"
  290. cmd := &instructions.ExposeCommand{
  291. Ports: []string{exposedPort},
  292. }
  293. err := dispatch(sb, cmd)
  294. assert.NilError(t, err)
  295. assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
  296. assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
  297. portsMapping, err := nat.ParsePortSpec(exposedPort)
  298. assert.NilError(t, err)
  299. assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port))
  300. }
  301. func TestUser(t *testing.T) {
  302. b := newBuilderWithMockBackend()
  303. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  304. cmd := &instructions.UserCommand{
  305. User: "test",
  306. }
  307. err := dispatch(sb, cmd)
  308. assert.NilError(t, err)
  309. assert.Check(t, is.Equal("test", sb.state.runConfig.User))
  310. }
  311. func TestVolume(t *testing.T) {
  312. b := newBuilderWithMockBackend()
  313. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  314. exposedVolume := "/foo"
  315. cmd := &instructions.VolumeCommand{
  316. Volumes: []string{exposedVolume},
  317. }
  318. err := dispatch(sb, cmd)
  319. assert.NilError(t, err)
  320. assert.Assert(t, sb.state.runConfig.Volumes != nil)
  321. assert.Check(t, is.Len(sb.state.runConfig.Volumes, 1))
  322. assert.Check(t, is.Contains(sb.state.runConfig.Volumes, exposedVolume))
  323. }
  324. func TestStopSignal(t *testing.T) {
  325. if runtime.GOOS == "windows" {
  326. t.Skip("Windows does not support stopsignal")
  327. return
  328. }
  329. b := newBuilderWithMockBackend()
  330. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  331. sb.state.baseImage = &mockImage{}
  332. signal := "SIGKILL"
  333. cmd := &instructions.StopSignalCommand{
  334. Signal: signal,
  335. }
  336. err := dispatch(sb, cmd)
  337. assert.NilError(t, err)
  338. assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal))
  339. }
  340. func TestArg(t *testing.T) {
  341. b := newBuilderWithMockBackend()
  342. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  343. argName := "foo"
  344. argVal := "bar"
  345. cmd := &instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{Key: argName, Value: &argVal}}}
  346. err := dispatch(sb, cmd)
  347. assert.NilError(t, err)
  348. expected := map[string]string{argName: argVal}
  349. assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed()))
  350. }
  351. func TestShell(t *testing.T) {
  352. b := newBuilderWithMockBackend()
  353. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  354. shellCmd := "powershell"
  355. cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
  356. err := dispatch(sb, cmd)
  357. assert.NilError(t, err)
  358. expectedShell := strslice.StrSlice([]string{shellCmd})
  359. assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell))
  360. }
  361. func TestPrependEnvOnCmd(t *testing.T) {
  362. buildArgs := NewBuildArgs(nil)
  363. buildArgs.AddArg("NO_PROXY", nil)
  364. args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
  365. cmd := []string{"foo", "bar"}
  366. cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
  367. expected := strslice.StrSlice([]string{
  368. "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
  369. assert.Check(t, is.DeepEqual(expected, cmdWithEnv))
  370. }
  371. func TestRunWithBuildArgs(t *testing.T) {
  372. b := newBuilderWithMockBackend()
  373. args := NewBuildArgs(make(map[string]*string))
  374. args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
  375. b.disableCommit = false
  376. sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
  377. runConfig := &container.Config{}
  378. origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
  379. var cmdWithShell strslice.StrSlice
  380. if runtime.GOOS == "windows" {
  381. cmdWithShell = strslice.StrSlice([]string{strings.Join(append(getShell(runConfig, runtime.GOOS), []string{"echo foo"}...), " ")})
  382. } else {
  383. cmdWithShell = strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
  384. }
  385. envVars := []string{"|1", "one=two"}
  386. cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
  387. imageCache := &mockImageCache{
  388. getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
  389. // Check the runConfig.Cmd sent to probeCache()
  390. assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd))
  391. assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint))
  392. return "", nil
  393. },
  394. }
  395. mockBackend := b.docker.(*MockBackend)
  396. mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
  397. return imageCache
  398. }
  399. b.imageProber = newImageProber(mockBackend, nil, false)
  400. mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
  401. return &mockImage{
  402. id: "abcdef",
  403. config: &container.Config{Cmd: origCmd},
  404. }, nil, nil
  405. }
  406. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  407. // Check the runConfig.Cmd sent to create()
  408. assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd))
  409. assert.Check(t, is.Contains(config.Config.Env, "one=two"))
  410. assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint))
  411. return container.ContainerCreateCreatedBody{ID: "12345"}, nil
  412. }
  413. mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
  414. // Check the runConfig.Cmd sent to commit()
  415. assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd))
  416. assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd))
  417. assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint))
  418. return "", nil
  419. }
  420. from := &instructions.Stage{BaseName: "abcdef"}
  421. err := initializeStage(sb, from)
  422. assert.NilError(t, err)
  423. sb.state.buildArgs.AddArg("one", strPtr("two"))
  424. // This is hugely annoying. On the Windows side, it relies on the
  425. // RunCommand being able to emit String() and Name() (as implemented by
  426. // withNameAndCode). Unfortunately, that is internal, and no way to directly
  427. // set. However, we can fortunately use ParseInstruction in the instructions
  428. // package to parse a fake node which can be used as our instructions.RunCommand
  429. // instead.
  430. node := &parser.Node{
  431. Original: `RUN echo foo`,
  432. Value: "run",
  433. }
  434. runint, err := instructions.ParseInstruction(node)
  435. assert.NilError(t, err)
  436. runinst := runint.(*instructions.RunCommand)
  437. runinst.CmdLine = strslice.StrSlice{"echo foo"}
  438. runinst.PrependShell = true
  439. assert.NilError(t, dispatch(sb, runinst))
  440. // Check that runConfig.Cmd has not been modified by run
  441. assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd))
  442. }
  443. func TestRunIgnoresHealthcheck(t *testing.T) {
  444. b := newBuilderWithMockBackend()
  445. args := NewBuildArgs(make(map[string]*string))
  446. sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
  447. b.disableCommit = false
  448. origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
  449. imageCache := &mockImageCache{
  450. getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
  451. return "", nil
  452. },
  453. }
  454. mockBackend := b.docker.(*MockBackend)
  455. mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
  456. return imageCache
  457. }
  458. b.imageProber = newImageProber(mockBackend, nil, false)
  459. mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
  460. return &mockImage{
  461. id: "abcdef",
  462. config: &container.Config{Cmd: origCmd},
  463. }, nil, nil
  464. }
  465. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  466. return container.ContainerCreateCreatedBody{ID: "12345"}, nil
  467. }
  468. mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
  469. return "", nil
  470. }
  471. from := &instructions.Stage{BaseName: "abcdef"}
  472. err := initializeStage(sb, from)
  473. assert.NilError(t, err)
  474. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  475. healthint, err := instructions.ParseInstruction(&parser.Node{
  476. Original: `HEALTHCHECK CMD curl -f http://localhost/ || exit 1`,
  477. Value: "healthcheck",
  478. Next: &parser.Node{
  479. Value: "cmd",
  480. Next: &parser.Node{
  481. Value: `curl -f http://localhost/ || exit 1`,
  482. },
  483. },
  484. })
  485. assert.NilError(t, err)
  486. cmd := healthint.(*instructions.HealthCheckCommand)
  487. assert.NilError(t, dispatch(sb, cmd))
  488. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  489. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  490. // Check the Healthcheck is disabled.
  491. assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test))
  492. return container.ContainerCreateCreatedBody{ID: "123456"}, nil
  493. }
  494. sb.state.buildArgs.AddArg("one", strPtr("two"))
  495. runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"})
  496. assert.NilError(t, err)
  497. run := runint.(*instructions.RunCommand)
  498. run.PrependShell = true
  499. assert.NilError(t, dispatch(sb, run))
  500. assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
  501. }
  502. func TestDispatchUnsupportedOptions(t *testing.T) {
  503. b := newBuilderWithMockBackend()
  504. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  505. sb.state.baseImage = &mockImage{}
  506. sb.state.operatingSystem = runtime.GOOS
  507. t.Run("ADD with chmod", func(t *testing.T) {
  508. cmd := &instructions.AddCommand{
  509. SourcesAndDest: instructions.SourcesAndDest{
  510. SourcePaths: []string{"."},
  511. DestPath: ".",
  512. },
  513. Chmod: "0655",
  514. }
  515. err := dispatch(sb, cmd)
  516. assert.Error(t, err, "the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled")
  517. })
  518. t.Run("COPY with chmod", func(t *testing.T) {
  519. cmd := &instructions.CopyCommand{
  520. SourcesAndDest: instructions.SourcesAndDest{
  521. SourcePaths: []string{"."},
  522. DestPath: ".",
  523. },
  524. Chmod: "0655",
  525. }
  526. err := dispatch(sb, cmd)
  527. assert.Error(t, err, "the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled")
  528. })
  529. t.Run("RUN with unsupported options", func(t *testing.T) {
  530. runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"})
  531. assert.NilError(t, err)
  532. cmd := runint.(*instructions.RunCommand)
  533. // classic builder "RUN" currently doesn't support any flags, but testing
  534. // both "known" flags and "bogus" flags for completeness, and in case
  535. // one or more of these flags will be supported in future
  536. for _, f := range []string{"mount", "network", "security", "any-flag"} {
  537. cmd.FlagsUsed = []string{f}
  538. err := dispatch(sb, cmd)
  539. assert.Error(t, err, fmt.Sprintf("the --%s option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled", f))
  540. }
  541. })
  542. }