dispatchers_test.go 22 KB

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