dispatchers_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. package dockerfile
  2. import (
  3. "fmt"
  4. "runtime"
  5. "testing"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/api/types/container"
  8. "github.com/docker/docker/api/types/strslice"
  9. "github.com/docker/docker/builder"
  10. "github.com/docker/docker/builder/dockerfile/parser"
  11. "github.com/docker/docker/pkg/testutil"
  12. "github.com/docker/go-connections/nat"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. type commandWithFunction struct {
  17. name string
  18. function func(args []string) error
  19. }
  20. func withArgs(f dispatcher) func([]string) error {
  21. return func(args []string) error {
  22. return f(dispatchRequest{args: args, runConfig: &container.Config{}})
  23. }
  24. }
  25. func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error {
  26. return func(args []string) error {
  27. return f(defaultDispatchReq(builder, args...))
  28. }
  29. }
  30. func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
  31. return dispatchRequest{
  32. builder: builder,
  33. args: args,
  34. flags: NewBFlags(),
  35. runConfig: &container.Config{},
  36. shlex: NewShellLex(parser.DefaultEscapeToken),
  37. }
  38. }
  39. func newBuilderWithMockBackend() *Builder {
  40. b := &Builder{
  41. runConfig: &container.Config{},
  42. options: &types.ImageBuildOptions{},
  43. docker: &MockBackend{},
  44. buildArgs: newBuildArgs(make(map[string]*string)),
  45. disableCommit: true,
  46. }
  47. b.imageContexts = &imageContexts{b: b}
  48. return b
  49. }
  50. func TestCommandsExactlyOneArgument(t *testing.T) {
  51. commands := []commandWithFunction{
  52. {"MAINTAINER", withArgs(maintainer)},
  53. {"WORKDIR", withArgs(workdir)},
  54. {"USER", withArgs(user)},
  55. {"STOPSIGNAL", withArgs(stopSignal)},
  56. }
  57. for _, command := range commands {
  58. err := command.function([]string{})
  59. assert.EqualError(t, err, errExactlyOneArgument(command.name).Error())
  60. }
  61. }
  62. func TestCommandsAtLeastOneArgument(t *testing.T) {
  63. commands := []commandWithFunction{
  64. {"ENV", withArgs(env)},
  65. {"LABEL", withArgs(label)},
  66. {"ONBUILD", withArgs(onbuild)},
  67. {"HEALTHCHECK", withArgs(healthcheck)},
  68. {"EXPOSE", withArgs(expose)},
  69. {"VOLUME", withArgs(volume)},
  70. }
  71. for _, command := range commands {
  72. err := command.function([]string{})
  73. assert.EqualError(t, err, errAtLeastOneArgument(command.name).Error())
  74. }
  75. }
  76. func TestCommandsAtLeastTwoArguments(t *testing.T) {
  77. commands := []commandWithFunction{
  78. {"ADD", withArgs(add)},
  79. {"COPY", withArgs(dispatchCopy)}}
  80. for _, command := range commands {
  81. err := command.function([]string{"arg1"})
  82. assert.EqualError(t, err, errAtLeastTwoArguments(command.name).Error())
  83. }
  84. }
  85. func TestCommandsTooManyArguments(t *testing.T) {
  86. commands := []commandWithFunction{
  87. {"ENV", withArgs(env)},
  88. {"LABEL", withArgs(label)}}
  89. for _, command := range commands {
  90. err := command.function([]string{"arg1", "arg2", "arg3"})
  91. assert.EqualError(t, err, errTooManyArguments(command.name).Error())
  92. }
  93. }
  94. func TestCommandsBlankNames(t *testing.T) {
  95. builder := newBuilderWithMockBackend()
  96. commands := []commandWithFunction{
  97. {"ENV", withBuilderAndArgs(builder, env)},
  98. {"LABEL", withBuilderAndArgs(builder, label)},
  99. }
  100. for _, command := range commands {
  101. err := command.function([]string{"", ""})
  102. assert.EqualError(t, err, errBlankCommandNames(command.name).Error())
  103. }
  104. }
  105. func TestEnv2Variables(t *testing.T) {
  106. b := newBuilderWithMockBackend()
  107. args := []string{"var1", "val1", "var2", "val2"}
  108. req := defaultDispatchReq(b, args...)
  109. err := env(req)
  110. require.NoError(t, err)
  111. expected := []string{
  112. fmt.Sprintf("%s=%s", args[0], args[1]),
  113. fmt.Sprintf("%s=%s", args[2], args[3]),
  114. }
  115. assert.Equal(t, expected, req.runConfig.Env)
  116. }
  117. func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
  118. b := newBuilderWithMockBackend()
  119. args := []string{"var1", "val1"}
  120. req := defaultDispatchReq(b, args...)
  121. req.runConfig.Env = []string{"var1=old", "var2=fromenv"}
  122. err := env(req)
  123. require.NoError(t, err)
  124. expected := []string{
  125. fmt.Sprintf("%s=%s", args[0], args[1]),
  126. "var2=fromenv",
  127. }
  128. assert.Equal(t, expected, req.runConfig.Env)
  129. }
  130. func TestMaintainer(t *testing.T) {
  131. maintainerEntry := "Some Maintainer <maintainer@example.com>"
  132. b := newBuilderWithMockBackend()
  133. err := maintainer(defaultDispatchReq(b, maintainerEntry))
  134. require.NoError(t, err)
  135. assert.Equal(t, maintainerEntry, b.maintainer)
  136. }
  137. func TestLabel(t *testing.T) {
  138. labelName := "label"
  139. labelValue := "value"
  140. labelEntry := []string{labelName, labelValue}
  141. b := newBuilderWithMockBackend()
  142. req := defaultDispatchReq(b, labelEntry...)
  143. err := label(req)
  144. require.NoError(t, err)
  145. require.Contains(t, req.runConfig.Labels, labelName)
  146. assert.Equal(t, req.runConfig.Labels[labelName], labelValue)
  147. }
  148. func TestFromScratch(t *testing.T) {
  149. b := newBuilderWithMockBackend()
  150. err := from(defaultDispatchReq(b, "scratch"))
  151. if runtime.GOOS == "windows" {
  152. assert.EqualError(t, err, "Windows does not support FROM scratch")
  153. return
  154. }
  155. require.NoError(t, err)
  156. assert.Equal(t, "", b.image)
  157. assert.Equal(t, true, b.noBaseImage)
  158. }
  159. func TestFromWithArg(t *testing.T) {
  160. tag, expected := ":sometag", "expectedthisid"
  161. getImage := func(name string) (builder.Image, error) {
  162. assert.Equal(t, "alpine"+tag, name)
  163. return &mockImage{id: "expectedthisid"}, nil
  164. }
  165. b := newBuilderWithMockBackend()
  166. b.docker.(*MockBackend).getImageOnBuildFunc = getImage
  167. require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
  168. err := from(defaultDispatchReq(b, "alpine${THETAG}"))
  169. require.NoError(t, err)
  170. assert.Equal(t, expected, b.image)
  171. assert.Equal(t, expected, b.from.ImageID())
  172. assert.Len(t, b.buildArgs.GetAllAllowed(), 0)
  173. assert.Len(t, b.buildArgs.GetAllMeta(), 1)
  174. }
  175. func TestFromWithUndefinedArg(t *testing.T) {
  176. tag, expected := "sometag", "expectedthisid"
  177. getImage := func(name string) (builder.Image, error) {
  178. assert.Equal(t, "alpine", name)
  179. return &mockImage{id: "expectedthisid"}, nil
  180. }
  181. b := newBuilderWithMockBackend()
  182. b.docker.(*MockBackend).getImageOnBuildFunc = getImage
  183. b.options.BuildArgs = map[string]*string{"THETAG": &tag}
  184. err := from(defaultDispatchReq(b, "alpine${THETAG}"))
  185. require.NoError(t, err)
  186. assert.Equal(t, expected, b.image)
  187. }
  188. func TestOnbuildIllegalTriggers(t *testing.T) {
  189. triggers := []struct{ command, expectedError string }{
  190. {"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"},
  191. {"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"},
  192. {"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
  193. for _, trigger := range triggers {
  194. b := newBuilderWithMockBackend()
  195. err := onbuild(defaultDispatchReq(b, trigger.command))
  196. testutil.ErrorContains(t, err, trigger.expectedError)
  197. }
  198. }
  199. func TestOnbuild(t *testing.T) {
  200. b := newBuilderWithMockBackend()
  201. req := defaultDispatchReq(b, "ADD", ".", "/app/src")
  202. req.original = "ONBUILD ADD . /app/src"
  203. req.runConfig = &container.Config{}
  204. err := onbuild(req)
  205. require.NoError(t, err)
  206. assert.Equal(t, "ADD . /app/src", req.runConfig.OnBuild[0])
  207. }
  208. func TestWorkdir(t *testing.T) {
  209. b := newBuilderWithMockBackend()
  210. workingDir := "/app"
  211. if runtime.GOOS == "windows" {
  212. workingDir = "C:\app"
  213. }
  214. req := defaultDispatchReq(b, workingDir)
  215. err := workdir(req)
  216. require.NoError(t, err)
  217. assert.Equal(t, workingDir, req.runConfig.WorkingDir)
  218. }
  219. func TestCmd(t *testing.T) {
  220. b := newBuilderWithMockBackend()
  221. command := "./executable"
  222. req := defaultDispatchReq(b, command)
  223. err := cmd(req)
  224. require.NoError(t, err)
  225. var expectedCommand strslice.StrSlice
  226. if runtime.GOOS == "windows" {
  227. expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
  228. } else {
  229. expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
  230. }
  231. assert.Equal(t, expectedCommand, req.runConfig.Cmd)
  232. assert.True(t, b.cmdSet)
  233. }
  234. func TestHealthcheckNone(t *testing.T) {
  235. b := newBuilderWithMockBackend()
  236. req := defaultDispatchReq(b, "NONE")
  237. err := healthcheck(req)
  238. require.NoError(t, err)
  239. require.NotNil(t, req.runConfig.Healthcheck)
  240. assert.Equal(t, []string{"NONE"}, req.runConfig.Healthcheck.Test)
  241. }
  242. func TestHealthcheckCmd(t *testing.T) {
  243. b := newBuilderWithMockBackend()
  244. args := []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}
  245. req := defaultDispatchReq(b, args...)
  246. err := healthcheck(req)
  247. require.NoError(t, err)
  248. require.NotNil(t, req.runConfig.Healthcheck)
  249. expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
  250. assert.Equal(t, expectedTest, req.runConfig.Healthcheck.Test)
  251. }
  252. func TestEntrypoint(t *testing.T) {
  253. b := newBuilderWithMockBackend()
  254. entrypointCmd := "/usr/sbin/nginx"
  255. req := defaultDispatchReq(b, entrypointCmd)
  256. err := entrypoint(req)
  257. require.NoError(t, err)
  258. require.NotNil(t, req.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, req.runConfig.Entrypoint)
  266. }
  267. func TestExpose(t *testing.T) {
  268. b := newBuilderWithMockBackend()
  269. exposedPort := "80"
  270. req := defaultDispatchReq(b, exposedPort)
  271. err := expose(req)
  272. require.NoError(t, err)
  273. require.NotNil(t, req.runConfig.ExposedPorts)
  274. require.Len(t, req.runConfig.ExposedPorts, 1)
  275. portsMapping, err := nat.ParsePortSpec(exposedPort)
  276. require.NoError(t, err)
  277. assert.Contains(t, req.runConfig.ExposedPorts, portsMapping[0].Port)
  278. }
  279. func TestUser(t *testing.T) {
  280. b := newBuilderWithMockBackend()
  281. userCommand := "foo"
  282. req := defaultDispatchReq(b, userCommand)
  283. err := user(req)
  284. require.NoError(t, err)
  285. assert.Equal(t, userCommand, req.runConfig.User)
  286. }
  287. func TestVolume(t *testing.T) {
  288. b := newBuilderWithMockBackend()
  289. exposedVolume := "/foo"
  290. req := defaultDispatchReq(b, exposedVolume)
  291. err := volume(req)
  292. require.NoError(t, err)
  293. require.NotNil(t, req.runConfig.Volumes)
  294. assert.Len(t, req.runConfig.Volumes, 1)
  295. assert.Contains(t, req.runConfig.Volumes, exposedVolume)
  296. }
  297. func TestStopSignal(t *testing.T) {
  298. b := newBuilderWithMockBackend()
  299. signal := "SIGKILL"
  300. req := defaultDispatchReq(b, signal)
  301. err := stopSignal(req)
  302. require.NoError(t, err)
  303. assert.Equal(t, signal, req.runConfig.StopSignal)
  304. }
  305. func TestArg(t *testing.T) {
  306. b := newBuilderWithMockBackend()
  307. argName := "foo"
  308. argVal := "bar"
  309. argDef := fmt.Sprintf("%s=%s", argName, argVal)
  310. err := arg(defaultDispatchReq(b, argDef))
  311. require.NoError(t, err)
  312. expected := map[string]string{argName: argVal}
  313. assert.Equal(t, expected, b.buildArgs.GetAllAllowed())
  314. }
  315. func TestShell(t *testing.T) {
  316. b := newBuilderWithMockBackend()
  317. shellCmd := "powershell"
  318. req := defaultDispatchReq(b, shellCmd)
  319. req.attributes = map[string]bool{"json": true}
  320. err := shell(req)
  321. require.NoError(t, err)
  322. expectedShell := strslice.StrSlice([]string{shellCmd})
  323. assert.Equal(t, expectedShell, req.runConfig.Shell)
  324. }