dispatchers_test.go 13 KB

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