dispatchers_test.go 15 KB

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