dispatchers_test.go 15 KB

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