dispatchers_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. package dockerfile
  2. import (
  3. "fmt"
  4. "runtime"
  5. "testing"
  6. "bytes"
  7. "context"
  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/builder/dockerfile/parser"
  14. "github.com/docker/docker/pkg/testutil"
  15. "github.com/docker/go-connections/nat"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. )
  19. type commandWithFunction struct {
  20. name string
  21. function func(args []string) error
  22. }
  23. func withArgs(f dispatcher) func([]string) error {
  24. return func(args []string) error {
  25. return f(dispatchRequest{args: args})
  26. }
  27. }
  28. func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error {
  29. return func(args []string) error {
  30. return f(defaultDispatchReq(builder, args...))
  31. }
  32. }
  33. func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
  34. return dispatchRequest{
  35. builder: builder,
  36. args: args,
  37. flags: NewBFlags(),
  38. shlex: NewShellLex(parser.DefaultEscapeToken),
  39. state: &dispatchState{runConfig: &container.Config{}},
  40. }
  41. }
  42. func newBuilderWithMockBackend() *Builder {
  43. b := &Builder{
  44. options: &types.ImageBuildOptions{},
  45. docker: &MockBackend{},
  46. buildArgs: newBuildArgs(make(map[string]*string)),
  47. tmpContainers: make(map[string]struct{}),
  48. Stdout: new(bytes.Buffer),
  49. clientCtx: context.Background(),
  50. disableCommit: true,
  51. }
  52. b.imageContexts = &imageContexts{b: b}
  53. return b
  54. }
  55. func TestCommandsExactlyOneArgument(t *testing.T) {
  56. commands := []commandWithFunction{
  57. {"MAINTAINER", withArgs(maintainer)},
  58. {"WORKDIR", withArgs(workdir)},
  59. {"USER", withArgs(user)},
  60. {"STOPSIGNAL", withArgs(stopSignal)},
  61. }
  62. for _, command := range commands {
  63. err := command.function([]string{})
  64. assert.EqualError(t, err, errExactlyOneArgument(command.name).Error())
  65. }
  66. }
  67. func TestCommandsAtLeastOneArgument(t *testing.T) {
  68. commands := []commandWithFunction{
  69. {"ENV", withArgs(env)},
  70. {"LABEL", withArgs(label)},
  71. {"ONBUILD", withArgs(onbuild)},
  72. {"HEALTHCHECK", withArgs(healthcheck)},
  73. {"EXPOSE", withArgs(expose)},
  74. {"VOLUME", withArgs(volume)},
  75. }
  76. for _, command := range commands {
  77. err := command.function([]string{})
  78. assert.EqualError(t, err, errAtLeastOneArgument(command.name).Error())
  79. }
  80. }
  81. func TestCommandsAtLeastTwoArguments(t *testing.T) {
  82. commands := []commandWithFunction{
  83. {"ADD", withArgs(add)},
  84. {"COPY", withArgs(dispatchCopy)}}
  85. for _, command := range commands {
  86. err := command.function([]string{"arg1"})
  87. assert.EqualError(t, err, errAtLeastTwoArguments(command.name).Error())
  88. }
  89. }
  90. func TestCommandsTooManyArguments(t *testing.T) {
  91. commands := []commandWithFunction{
  92. {"ENV", withArgs(env)},
  93. {"LABEL", withArgs(label)}}
  94. for _, command := range commands {
  95. err := command.function([]string{"arg1", "arg2", "arg3"})
  96. assert.EqualError(t, err, errTooManyArguments(command.name).Error())
  97. }
  98. }
  99. func TestCommandsBlankNames(t *testing.T) {
  100. builder := newBuilderWithMockBackend()
  101. commands := []commandWithFunction{
  102. {"ENV", withBuilderAndArgs(builder, env)},
  103. {"LABEL", withBuilderAndArgs(builder, label)},
  104. }
  105. for _, command := range commands {
  106. err := command.function([]string{"", ""})
  107. assert.EqualError(t, err, errBlankCommandNames(command.name).Error())
  108. }
  109. }
  110. func TestEnv2Variables(t *testing.T) {
  111. b := newBuilderWithMockBackend()
  112. args := []string{"var1", "val1", "var2", "val2"}
  113. req := defaultDispatchReq(b, args...)
  114. err := env(req)
  115. require.NoError(t, err)
  116. expected := []string{
  117. fmt.Sprintf("%s=%s", args[0], args[1]),
  118. fmt.Sprintf("%s=%s", args[2], args[3]),
  119. }
  120. assert.Equal(t, expected, req.state.runConfig.Env)
  121. }
  122. func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
  123. b := newBuilderWithMockBackend()
  124. args := []string{"var1", "val1"}
  125. req := defaultDispatchReq(b, args...)
  126. req.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
  127. err := env(req)
  128. require.NoError(t, err)
  129. expected := []string{
  130. fmt.Sprintf("%s=%s", args[0], args[1]),
  131. "var2=fromenv",
  132. }
  133. assert.Equal(t, expected, req.state.runConfig.Env)
  134. }
  135. func TestMaintainer(t *testing.T) {
  136. maintainerEntry := "Some Maintainer <maintainer@example.com>"
  137. b := newBuilderWithMockBackend()
  138. req := defaultDispatchReq(b, maintainerEntry)
  139. err := maintainer(req)
  140. require.NoError(t, err)
  141. assert.Equal(t, maintainerEntry, req.state.maintainer)
  142. }
  143. func TestLabel(t *testing.T) {
  144. labelName := "label"
  145. labelValue := "value"
  146. labelEntry := []string{labelName, labelValue}
  147. b := newBuilderWithMockBackend()
  148. req := defaultDispatchReq(b, labelEntry...)
  149. err := label(req)
  150. require.NoError(t, err)
  151. require.Contains(t, req.state.runConfig.Labels, labelName)
  152. assert.Equal(t, req.state.runConfig.Labels[labelName], labelValue)
  153. }
  154. func TestFromScratch(t *testing.T) {
  155. b := newBuilderWithMockBackend()
  156. req := defaultDispatchReq(b, "scratch")
  157. err := from(req)
  158. if runtime.GOOS == "windows" {
  159. assert.EqualError(t, err, "Windows does not support FROM scratch")
  160. return
  161. }
  162. require.NoError(t, err)
  163. assert.Equal(t, "", req.state.imageID)
  164. assert.Equal(t, true, req.state.noBaseImage)
  165. }
  166. func TestFromWithArg(t *testing.T) {
  167. tag, expected := ":sometag", "expectedthisid"
  168. getImage := func(name string) (builder.Image, error) {
  169. assert.Equal(t, "alpine"+tag, name)
  170. return &mockImage{id: "expectedthisid"}, nil
  171. }
  172. b := newBuilderWithMockBackend()
  173. b.docker.(*MockBackend).getImageOnBuildFunc = getImage
  174. require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
  175. req := defaultDispatchReq(b, "alpine${THETAG}")
  176. err := from(req)
  177. require.NoError(t, err)
  178. assert.Equal(t, expected, req.state.imageID)
  179. assert.Equal(t, expected, req.state.baseImage.ImageID())
  180. assert.Len(t, b.buildArgs.GetAllAllowed(), 0)
  181. assert.Len(t, b.buildArgs.GetAllMeta(), 1)
  182. }
  183. func TestFromWithUndefinedArg(t *testing.T) {
  184. tag, expected := "sometag", "expectedthisid"
  185. getImage := func(name string) (builder.Image, error) {
  186. assert.Equal(t, "alpine", name)
  187. return &mockImage{id: "expectedthisid"}, nil
  188. }
  189. b := newBuilderWithMockBackend()
  190. b.docker.(*MockBackend).getImageOnBuildFunc = getImage
  191. b.options.BuildArgs = map[string]*string{"THETAG": &tag}
  192. req := defaultDispatchReq(b, "alpine${THETAG}")
  193. err := from(req)
  194. require.NoError(t, err)
  195. assert.Equal(t, expected, req.state.imageID)
  196. }
  197. func TestFromMultiStageWithScratchNamedStage(t *testing.T) {
  198. if runtime.GOOS == "windows" {
  199. t.Skip("Windows does not support scratch")
  200. }
  201. b := newBuilderWithMockBackend()
  202. req := defaultDispatchReq(b, "scratch", "AS", "base")
  203. require.NoError(t, from(req))
  204. assert.True(t, req.state.hasFromImage())
  205. req.args = []string{"base"}
  206. require.NoError(t, from(req))
  207. assert.True(t, req.state.hasFromImage())
  208. }
  209. func TestOnbuildIllegalTriggers(t *testing.T) {
  210. triggers := []struct{ command, expectedError string }{
  211. {"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"},
  212. {"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"},
  213. {"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
  214. for _, trigger := range triggers {
  215. b := newBuilderWithMockBackend()
  216. err := onbuild(defaultDispatchReq(b, trigger.command))
  217. testutil.ErrorContains(t, err, trigger.expectedError)
  218. }
  219. }
  220. func TestOnbuild(t *testing.T) {
  221. b := newBuilderWithMockBackend()
  222. req := defaultDispatchReq(b, "ADD", ".", "/app/src")
  223. req.original = "ONBUILD ADD . /app/src"
  224. req.state.runConfig = &container.Config{}
  225. err := onbuild(req)
  226. require.NoError(t, err)
  227. assert.Equal(t, "ADD . /app/src", req.state.runConfig.OnBuild[0])
  228. }
  229. func TestWorkdir(t *testing.T) {
  230. b := newBuilderWithMockBackend()
  231. workingDir := "/app"
  232. if runtime.GOOS == "windows" {
  233. workingDir = "C:\app"
  234. }
  235. req := defaultDispatchReq(b, workingDir)
  236. err := workdir(req)
  237. require.NoError(t, err)
  238. assert.Equal(t, workingDir, req.state.runConfig.WorkingDir)
  239. }
  240. func TestCmd(t *testing.T) {
  241. b := newBuilderWithMockBackend()
  242. command := "./executable"
  243. req := defaultDispatchReq(b, command)
  244. err := cmd(req)
  245. require.NoError(t, err)
  246. var expectedCommand strslice.StrSlice
  247. if runtime.GOOS == "windows" {
  248. expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
  249. } else {
  250. expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
  251. }
  252. assert.Equal(t, expectedCommand, req.state.runConfig.Cmd)
  253. assert.True(t, req.state.cmdSet)
  254. }
  255. func TestHealthcheckNone(t *testing.T) {
  256. b := newBuilderWithMockBackend()
  257. req := defaultDispatchReq(b, "NONE")
  258. err := healthcheck(req)
  259. require.NoError(t, err)
  260. require.NotNil(t, req.state.runConfig.Healthcheck)
  261. assert.Equal(t, []string{"NONE"}, req.state.runConfig.Healthcheck.Test)
  262. }
  263. func TestHealthcheckCmd(t *testing.T) {
  264. b := newBuilderWithMockBackend()
  265. args := []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}
  266. req := defaultDispatchReq(b, args...)
  267. err := healthcheck(req)
  268. require.NoError(t, err)
  269. require.NotNil(t, req.state.runConfig.Healthcheck)
  270. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  271. assert.Equal(t, expectedTest, req.state.runConfig.Healthcheck.Test)
  272. }
  273. func TestEntrypoint(t *testing.T) {
  274. b := newBuilderWithMockBackend()
  275. entrypointCmd := "/usr/sbin/nginx"
  276. req := defaultDispatchReq(b, entrypointCmd)
  277. err := entrypoint(req)
  278. require.NoError(t, err)
  279. require.NotNil(t, req.state.runConfig.Entrypoint)
  280. var expectedEntrypoint strslice.StrSlice
  281. if runtime.GOOS == "windows" {
  282. expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
  283. } else {
  284. expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
  285. }
  286. assert.Equal(t, expectedEntrypoint, req.state.runConfig.Entrypoint)
  287. }
  288. func TestExpose(t *testing.T) {
  289. b := newBuilderWithMockBackend()
  290. exposedPort := "80"
  291. req := defaultDispatchReq(b, exposedPort)
  292. err := expose(req)
  293. require.NoError(t, err)
  294. require.NotNil(t, req.state.runConfig.ExposedPorts)
  295. require.Len(t, req.state.runConfig.ExposedPorts, 1)
  296. portsMapping, err := nat.ParsePortSpec(exposedPort)
  297. require.NoError(t, err)
  298. assert.Contains(t, req.state.runConfig.ExposedPorts, portsMapping[0].Port)
  299. }
  300. func TestUser(t *testing.T) {
  301. b := newBuilderWithMockBackend()
  302. userCommand := "foo"
  303. req := defaultDispatchReq(b, userCommand)
  304. err := user(req)
  305. require.NoError(t, err)
  306. assert.Equal(t, userCommand, req.state.runConfig.User)
  307. }
  308. func TestVolume(t *testing.T) {
  309. b := newBuilderWithMockBackend()
  310. exposedVolume := "/foo"
  311. req := defaultDispatchReq(b, exposedVolume)
  312. err := volume(req)
  313. require.NoError(t, err)
  314. require.NotNil(t, req.state.runConfig.Volumes)
  315. assert.Len(t, req.state.runConfig.Volumes, 1)
  316. assert.Contains(t, req.state.runConfig.Volumes, exposedVolume)
  317. }
  318. func TestStopSignal(t *testing.T) {
  319. b := newBuilderWithMockBackend()
  320. signal := "SIGKILL"
  321. req := defaultDispatchReq(b, signal)
  322. err := stopSignal(req)
  323. require.NoError(t, err)
  324. assert.Equal(t, signal, req.state.runConfig.StopSignal)
  325. }
  326. func TestArg(t *testing.T) {
  327. b := newBuilderWithMockBackend()
  328. argName := "foo"
  329. argVal := "bar"
  330. argDef := fmt.Sprintf("%s=%s", argName, argVal)
  331. err := arg(defaultDispatchReq(b, argDef))
  332. require.NoError(t, err)
  333. expected := map[string]string{argName: argVal}
  334. assert.Equal(t, expected, b.buildArgs.GetAllAllowed())
  335. }
  336. func TestShell(t *testing.T) {
  337. b := newBuilderWithMockBackend()
  338. shellCmd := "powershell"
  339. req := defaultDispatchReq(b, shellCmd)
  340. req.attributes = map[string]bool{"json": true}
  341. err := shell(req)
  342. require.NoError(t, err)
  343. expectedShell := strslice.StrSlice([]string{shellCmd})
  344. assert.Equal(t, expectedShell, req.state.runConfig.Shell)
  345. }
  346. func TestParseOptInterval(t *testing.T) {
  347. flInterval := &Flag{
  348. name: "interval",
  349. flagType: stringType,
  350. Value: "50ns",
  351. }
  352. _, err := parseOptInterval(flInterval)
  353. testutil.ErrorContains(t, err, "cannot be less than 1ms")
  354. flInterval.Value = "1ms"
  355. _, err = parseOptInterval(flInterval)
  356. require.NoError(t, err)
  357. }
  358. func TestPrependEnvOnCmd(t *testing.T) {
  359. buildArgs := newBuildArgs(nil)
  360. buildArgs.AddArg("NO_PROXY", nil)
  361. args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
  362. cmd := []string{"foo", "bar"}
  363. cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
  364. expected := strslice.StrSlice([]string{
  365. "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
  366. assert.Equal(t, expected, cmdWithEnv)
  367. }
  368. func TestRunWithBuildArgs(t *testing.T) {
  369. b := newBuilderWithMockBackend()
  370. b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
  371. b.disableCommit = false
  372. runConfig := &container.Config{}
  373. origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
  374. cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo"))
  375. envVars := []string{"|1", "one=two"}
  376. cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
  377. imageCache := &mockImageCache{
  378. getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
  379. // Check the runConfig.Cmd sent to probeCache()
  380. assert.Equal(t, cachedCmd, cfg.Cmd)
  381. assert.Equal(t, strslice.StrSlice(nil), cfg.Entrypoint)
  382. return "", nil
  383. },
  384. }
  385. b.imageCache = imageCache
  386. mockBackend := b.docker.(*MockBackend)
  387. mockBackend.getImageOnBuildImage = &mockImage{
  388. id: "abcdef",
  389. config: &container.Config{Cmd: origCmd},
  390. }
  391. mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
  392. // Check the runConfig.Cmd sent to create()
  393. assert.Equal(t, cmdWithShell, config.Config.Cmd)
  394. assert.Contains(t, config.Config.Env, "one=two")
  395. assert.Equal(t, strslice.StrSlice{""}, config.Config.Entrypoint)
  396. return container.ContainerCreateCreatedBody{ID: "12345"}, nil
  397. }
  398. mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
  399. // Check the runConfig.Cmd sent to commit()
  400. assert.Equal(t, origCmd, cfg.Config.Cmd)
  401. assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd)
  402. assert.Equal(t, strslice.StrSlice(nil), cfg.Config.Entrypoint)
  403. return "", nil
  404. }
  405. req := defaultDispatchReq(b, "abcdef")
  406. require.NoError(t, from(req))
  407. b.buildArgs.AddArg("one", strPtr("two"))
  408. req.args = []string{"echo foo"}
  409. require.NoError(t, run(req))
  410. // Check that runConfig.Cmd has not been modified by run
  411. assert.Equal(t, origCmd, req.state.runConfig.Cmd)
  412. }