dispatchers_test.go 11 KB

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