dispatchers_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. package dockerfile // import "github.com/docker/docker/builder/dockerfile"
  2. import (
  3. "bytes"
  4. "context"
  5. "runtime"
  6. "strings"
  7. "testing"
  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/parser"
  18. "github.com/moby/buildkit/frontend/dockerfile/shell"
  19. "gotest.tools/v3/assert"
  20. is "gotest.tools/v3/assert/cmp"
  21. )
  22. func newBuilderWithMockBackend() *Builder {
  23. mockBackend := &MockBackend{}
  24. opts := &types.ImageBuildOptions{}
  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, "Linux containers are not supported on this system"))
  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{Args: []instructions.KeyValuePairOptional{{
  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 TestFromWithArgButBuildArgsNotGiven(t *testing.T) {
  144. b := newBuilderWithMockBackend()
  145. args := NewBuildArgs(make(map[string]*string))
  146. metaArg := instructions.ArgCommand{}
  147. cmd := &instructions.Stage{
  148. BaseName: "${THETAG}",
  149. }
  150. err := processMetaArg(metaArg, shell.NewLex('\\'), args)
  151. sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
  152. assert.NilError(t, err)
  153. err = initializeStage(sb, cmd)
  154. assert.Error(t, err, "base name (${THETAG}) should not be blank")
  155. }
  156. func TestFromWithUndefinedArg(t *testing.T) {
  157. tag, expected := "sometag", "expectedthisid"
  158. getImage := func(name string) (builder.Image, builder.ROLayer, error) {
  159. assert.Check(t, is.Equal("alpine", name))
  160. return &mockImage{id: "expectedthisid"}, nil, nil
  161. }
  162. b := newBuilderWithMockBackend()
  163. b.docker.(*MockBackend).getImageFunc = getImage
  164. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  165. b.options.BuildArgs = map[string]*string{"THETAG": &tag}
  166. cmd := &instructions.Stage{
  167. BaseName: "alpine${THETAG}",
  168. }
  169. err := initializeStage(sb, cmd)
  170. assert.NilError(t, err)
  171. assert.Check(t, is.Equal(expected, sb.state.imageID))
  172. }
  173. func TestFromMultiStageWithNamedStage(t *testing.T) {
  174. b := newBuilderWithMockBackend()
  175. firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
  176. secondFrom := &instructions.Stage{BaseName: "base"}
  177. previousResults := newStagesBuildResults()
  178. firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
  179. secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
  180. err := initializeStage(firstSB, firstFrom)
  181. assert.NilError(t, err)
  182. assert.Check(t, firstSB.state.hasFromImage())
  183. previousResults.indexed["base"] = firstSB.state.runConfig
  184. previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
  185. err = initializeStage(secondSB, secondFrom)
  186. assert.NilError(t, err)
  187. assert.Check(t, secondSB.state.hasFromImage())
  188. }
  189. func TestOnbuild(t *testing.T) {
  190. b := newBuilderWithMockBackend()
  191. sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  192. cmd := &instructions.OnbuildCommand{
  193. Expression: "ADD . /app/src",
  194. }
  195. err := dispatch(sb, cmd)
  196. assert.NilError(t, err)
  197. assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0]))
  198. }
  199. func TestWorkdir(t *testing.T) {
  200. b := newBuilderWithMockBackend()
  201. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  202. sb.state.baseImage = &mockImage{}
  203. workingDir := "/app"
  204. if runtime.GOOS == "windows" {
  205. workingDir = "C:\\app"
  206. }
  207. cmd := &instructions.WorkdirCommand{
  208. Path: workingDir,
  209. }
  210. err := dispatch(sb, cmd)
  211. assert.NilError(t, err)
  212. assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir))
  213. }
  214. func TestCmd(t *testing.T) {
  215. b := newBuilderWithMockBackend()
  216. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  217. sb.state.baseImage = &mockImage{}
  218. command := "./executable"
  219. cmd := &instructions.CmdCommand{
  220. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  221. CmdLine: strslice.StrSlice{command},
  222. PrependShell: true,
  223. },
  224. }
  225. err := dispatch(sb, cmd)
  226. assert.NilError(t, err)
  227. var expectedCommand strslice.StrSlice
  228. if runtime.GOOS == "windows" {
  229. expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
  230. } else {
  231. expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
  232. }
  233. assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd))
  234. assert.Check(t, sb.state.cmdSet)
  235. }
  236. func TestHealthcheckNone(t *testing.T) {
  237. b := newBuilderWithMockBackend()
  238. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  239. cmd := &instructions.HealthCheckCommand{
  240. Health: &container.HealthConfig{
  241. Test: []string{"NONE"},
  242. },
  243. }
  244. err := dispatch(sb, cmd)
  245. assert.NilError(t, err)
  246. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  247. assert.Check(t, is.DeepEqual([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test))
  248. }
  249. func TestHealthcheckCmd(t *testing.T) {
  250. b := newBuilderWithMockBackend()
  251. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  252. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  253. cmd := &instructions.HealthCheckCommand{
  254. Health: &container.HealthConfig{
  255. Test: expectedTest,
  256. },
  257. }
  258. err := dispatch(sb, cmd)
  259. assert.NilError(t, err)
  260. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  261. assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
  262. }
  263. func TestEntrypoint(t *testing.T) {
  264. b := newBuilderWithMockBackend()
  265. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  266. sb.state.baseImage = &mockImage{}
  267. entrypointCmd := "/usr/sbin/nginx"
  268. cmd := &instructions.EntrypointCommand{
  269. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  270. CmdLine: strslice.StrSlice{entrypointCmd},
  271. PrependShell: true,
  272. },
  273. }
  274. err := dispatch(sb, cmd)
  275. assert.NilError(t, err)
  276. assert.Assert(t, sb.state.runConfig.Entrypoint != nil)
  277. var expectedEntrypoint strslice.StrSlice
  278. if runtime.GOOS == "windows" {
  279. expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
  280. } else {
  281. expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
  282. }
  283. assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint))
  284. }
  285. func TestExpose(t *testing.T) {
  286. b := newBuilderWithMockBackend()
  287. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  288. exposedPort := "80"
  289. cmd := &instructions.ExposeCommand{
  290. Ports: []string{exposedPort},
  291. }
  292. err := dispatch(sb, cmd)
  293. assert.NilError(t, err)
  294. assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
  295. assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
  296. portsMapping, err := nat.ParsePortSpec(exposedPort)
  297. assert.NilError(t, err)
  298. assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port))
  299. }
  300. func TestUser(t *testing.T) {
  301. b := newBuilderWithMockBackend()
  302. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  303. cmd := &instructions.UserCommand{
  304. User: "test",
  305. }
  306. err := dispatch(sb, cmd)
  307. assert.NilError(t, err)
  308. assert.Check(t, is.Equal("test", sb.state.runConfig.User))
  309. }
  310. func TestVolume(t *testing.T) {
  311. b := newBuilderWithMockBackend()
  312. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  313. exposedVolume := "/foo"
  314. cmd := &instructions.VolumeCommand{
  315. Volumes: []string{exposedVolume},
  316. }
  317. err := dispatch(sb, cmd)
  318. assert.NilError(t, err)
  319. assert.Assert(t, sb.state.runConfig.Volumes != nil)
  320. assert.Check(t, is.Len(sb.state.runConfig.Volumes, 1))
  321. assert.Check(t, is.Contains(sb.state.runConfig.Volumes, exposedVolume))
  322. }
  323. func TestStopSignal(t *testing.T) {
  324. if runtime.GOOS == "windows" {
  325. t.Skip("Windows does not support stopsignal")
  326. return
  327. }
  328. b := newBuilderWithMockBackend()
  329. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  330. sb.state.baseImage = &mockImage{}
  331. signal := "SIGKILL"
  332. cmd := &instructions.StopSignalCommand{
  333. Signal: signal,
  334. }
  335. err := dispatch(sb, cmd)
  336. assert.NilError(t, err)
  337. assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal))
  338. }
  339. func TestArg(t *testing.T) {
  340. b := newBuilderWithMockBackend()
  341. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  342. argName := "foo"
  343. argVal := "bar"
  344. cmd := &instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{Key: argName, Value: &argVal}}}
  345. err := dispatch(sb, cmd)
  346. assert.NilError(t, err)
  347. expected := map[string]string{argName: argVal}
  348. assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed()))
  349. }
  350. func TestShell(t *testing.T) {
  351. b := newBuilderWithMockBackend()
  352. sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
  353. shellCmd := "powershell"
  354. cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
  355. err := dispatch(sb, cmd)
  356. assert.NilError(t, err)
  357. expectedShell := strslice.StrSlice([]string{shellCmd})
  358. assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell))
  359. }
  360. func TestPrependEnvOnCmd(t *testing.T) {
  361. buildArgs := NewBuildArgs(nil)
  362. buildArgs.AddArg("NO_PROXY", nil)
  363. args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
  364. cmd := []string{"foo", "bar"}
  365. cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
  366. expected := strslice.StrSlice([]string{
  367. "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
  368. assert.Check(t, is.DeepEqual(expected, cmdWithEnv))
  369. }
  370. func TestRunWithBuildArgs(t *testing.T) {
  371. b := newBuilderWithMockBackend()
  372. args := NewBuildArgs(make(map[string]*string))
  373. args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
  374. b.disableCommit = false
  375. sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
  376. runConfig := &container.Config{}
  377. origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
  378. var cmdWithShell strslice.StrSlice
  379. if runtime.GOOS == "windows" {
  380. cmdWithShell = strslice.StrSlice([]string{strings.Join(append(getShell(runConfig, runtime.GOOS), []string{"echo foo"}...), " ")})
  381. } else {
  382. cmdWithShell = strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
  383. }
  384. envVars := []string{"|1", "one=two"}
  385. cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
  386. imageCache := &mockImageCache{
  387. getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
  388. // Check the runConfig.Cmd sent to probeCache()
  389. assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd))
  390. assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint))
  391. return "", nil
  392. },
  393. }
  394. mockBackend := b.docker.(*MockBackend)
  395. mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
  396. return imageCache
  397. }
  398. b.imageProber = newImageProber(mockBackend, nil, false)
  399. mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
  400. return &mockImage{
  401. id: "abcdef",
  402. config: &container.Config{Cmd: origCmd},
  403. }, nil, nil
  404. }
  405. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  406. // Check the runConfig.Cmd sent to create()
  407. assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd))
  408. assert.Check(t, is.Contains(config.Config.Env, "one=two"))
  409. assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint))
  410. return container.ContainerCreateCreatedBody{ID: "12345"}, nil
  411. }
  412. mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
  413. // Check the runConfig.Cmd sent to commit()
  414. assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd))
  415. assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd))
  416. assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint))
  417. return "", nil
  418. }
  419. from := &instructions.Stage{BaseName: "abcdef"}
  420. err := initializeStage(sb, from)
  421. assert.NilError(t, err)
  422. sb.state.buildArgs.AddArg("one", strPtr("two"))
  423. // This is hugely annoying. On the Windows side, it relies on the
  424. // RunCommand being able to emit String() and Name() (as implemented by
  425. // withNameAndCode). Unfortunately, that is internal, and no way to directly
  426. // set. However, we can fortunately use ParseInstruction in the instructions
  427. // package to parse a fake node which can be used as our instructions.RunCommand
  428. // instead.
  429. node := &parser.Node{
  430. Original: `RUN echo foo`,
  431. Value: "run",
  432. }
  433. runint, err := instructions.ParseInstruction(node)
  434. assert.NilError(t, err)
  435. runinst := runint.(*instructions.RunCommand)
  436. runinst.CmdLine = strslice.StrSlice{"echo foo"}
  437. runinst.PrependShell = true
  438. assert.NilError(t, dispatch(sb, runinst))
  439. // Check that runConfig.Cmd has not been modified by run
  440. assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd))
  441. }
  442. func TestRunIgnoresHealthcheck(t *testing.T) {
  443. b := newBuilderWithMockBackend()
  444. args := NewBuildArgs(make(map[string]*string))
  445. sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
  446. b.disableCommit = false
  447. origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
  448. imageCache := &mockImageCache{
  449. getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
  450. return "", nil
  451. },
  452. }
  453. mockBackend := b.docker.(*MockBackend)
  454. mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
  455. return imageCache
  456. }
  457. b.imageProber = newImageProber(mockBackend, nil, false)
  458. mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
  459. return &mockImage{
  460. id: "abcdef",
  461. config: &container.Config{Cmd: origCmd},
  462. }, nil, nil
  463. }
  464. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  465. return container.ContainerCreateCreatedBody{ID: "12345"}, nil
  466. }
  467. mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
  468. return "", nil
  469. }
  470. from := &instructions.Stage{BaseName: "abcdef"}
  471. err := initializeStage(sb, from)
  472. assert.NilError(t, err)
  473. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  474. cmd := &instructions.HealthCheckCommand{
  475. Health: &container.HealthConfig{
  476. Test: expectedTest,
  477. },
  478. }
  479. assert.NilError(t, dispatch(sb, cmd))
  480. assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
  481. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  482. // Check the Healthcheck is disabled.
  483. assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test))
  484. return container.ContainerCreateCreatedBody{ID: "123456"}, nil
  485. }
  486. sb.state.buildArgs.AddArg("one", strPtr("two"))
  487. run := &instructions.RunCommand{
  488. ShellDependantCmdLine: instructions.ShellDependantCmdLine{
  489. CmdLine: strslice.StrSlice{"echo foo"},
  490. PrependShell: true,
  491. },
  492. }
  493. assert.NilError(t, dispatch(sb, run))
  494. assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
  495. }