dispatchers_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. package dockerfile
  2. import (
  3. "fmt"
  4. "runtime"
  5. "strings"
  6. "testing"
  7. "github.com/docker/engine-api/types"
  8. "github.com/docker/engine-api/types/container"
  9. "github.com/docker/engine-api/types/strslice"
  10. "github.com/docker/go-connections/nat"
  11. )
  12. type commandWithFunction struct {
  13. name string
  14. function func(args []string) error
  15. }
  16. func TestCommandsExactlyOneArgument(t *testing.T) {
  17. commands := []commandWithFunction{
  18. {"MAINTAINER", func(args []string) error { return maintainer(nil, args, nil, "") }},
  19. {"FROM", func(args []string) error { return from(nil, args, nil, "") }},
  20. {"WORKDIR", func(args []string) error { return workdir(nil, args, nil, "") }},
  21. {"USER", func(args []string) error { return user(nil, args, nil, "") }}}
  22. for _, command := range commands {
  23. err := command.function([]string{})
  24. if err == nil {
  25. t.Fatalf("Error should be present for %s command", command.name)
  26. }
  27. expectedError := fmt.Sprintf("%s requires exactly one argument", command.name)
  28. if err.Error() != expectedError {
  29. t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
  30. }
  31. }
  32. }
  33. func TestCommandsAtLeastOneArgument(t *testing.T) {
  34. commands := []commandWithFunction{
  35. {"ENV", func(args []string) error { return env(nil, args, nil, "") }},
  36. {"LABEL", func(args []string) error { return label(nil, args, nil, "") }},
  37. {"ADD", func(args []string) error { return add(nil, args, nil, "") }},
  38. {"COPY", func(args []string) error { return dispatchCopy(nil, args, nil, "") }},
  39. {"ONBUILD", func(args []string) error { return onbuild(nil, args, nil, "") }},
  40. {"EXPOSE", func(args []string) error { return expose(nil, args, nil, "") }},
  41. {"VOLUME", func(args []string) error { return volume(nil, args, nil, "") }}}
  42. for _, command := range commands {
  43. err := command.function([]string{})
  44. if err == nil {
  45. t.Fatalf("Error should be present for %s command", command.name)
  46. }
  47. expectedError := fmt.Sprintf("%s requires at least one argument", command.name)
  48. if err.Error() != expectedError {
  49. t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
  50. }
  51. }
  52. }
  53. func TestCommandsTooManyArguments(t *testing.T) {
  54. commands := []commandWithFunction{
  55. {"ENV", func(args []string) error { return env(nil, args, nil, "") }},
  56. {"LABEL", func(args []string) error { return label(nil, args, nil, "") }}}
  57. for _, command := range commands {
  58. err := command.function([]string{"arg1", "arg2", "arg3"})
  59. if err == nil {
  60. t.Fatalf("Error should be present for %s command", command.name)
  61. }
  62. expectedError := fmt.Sprintf("Bad input to %s, too many arguments", command.name)
  63. if err.Error() != expectedError {
  64. t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
  65. }
  66. }
  67. }
  68. func TestEnv2Variables(t *testing.T) {
  69. variables := []string{"var1", "val1", "var2", "val2"}
  70. bflags := &BFlags{}
  71. config := &container.Config{}
  72. b := &Builder{flags: bflags, runConfig: config, disableCommit: true}
  73. if err := env(b, variables, nil, ""); err != nil {
  74. t.Fatalf("Error when executing env: %s", err.Error())
  75. }
  76. expectedVar1 := fmt.Sprintf("%s=%s", variables[0], variables[1])
  77. expectedVar2 := fmt.Sprintf("%s=%s", variables[2], variables[3])
  78. if b.runConfig.Env[0] != expectedVar1 {
  79. t.Fatalf("Wrong env output for first variable. Got: %s. Should be: %s", b.runConfig.Env[0], expectedVar1)
  80. }
  81. if b.runConfig.Env[1] != expectedVar2 {
  82. t.Fatalf("Wrong env output for second variable. Got: %s, Should be: %s", b.runConfig.Env[1], expectedVar2)
  83. }
  84. }
  85. func TestMaintainer(t *testing.T) {
  86. maintainerEntry := "Some Maintainer <maintainer@example.com>"
  87. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  88. if err := maintainer(b, []string{maintainerEntry}, nil, ""); err != nil {
  89. t.Fatalf("Error when executing maintainer: %s", err.Error())
  90. }
  91. if b.maintainer != maintainerEntry {
  92. t.Fatalf("Maintainer in builder should be set to %s. Got: %s", maintainerEntry, b.maintainer)
  93. }
  94. }
  95. func TestLabel(t *testing.T) {
  96. labelName := "label"
  97. labelValue := "value"
  98. labelEntry := []string{labelName, labelValue}
  99. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  100. if err := label(b, labelEntry, nil, ""); err != nil {
  101. t.Fatalf("Error when executing label: %s", err.Error())
  102. }
  103. if val, ok := b.runConfig.Labels[labelName]; ok {
  104. if val != labelValue {
  105. t.Fatalf("Label %s should have value %s, had %s instead", labelName, labelValue, val)
  106. }
  107. } else {
  108. t.Fatalf("Label %s should be present but it is not", labelName)
  109. }
  110. }
  111. func TestFrom(t *testing.T) {
  112. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  113. err := from(b, []string{"scratch"}, nil, "")
  114. if runtime.GOOS == "windows" {
  115. if err == nil {
  116. t.Fatalf("Error not set on Windows")
  117. }
  118. expectedError := "Windows does not support FROM scratch"
  119. if !strings.Contains(err.Error(), expectedError) {
  120. t.Fatalf("Error message not correct on Windows. Should be: %s, got: %s", expectedError, err.Error())
  121. }
  122. } else {
  123. if err != nil {
  124. t.Fatalf("Error when executing from: %s", err.Error())
  125. }
  126. if b.image != "" {
  127. t.Fatalf("Image shoule be empty, got: %s", b.image)
  128. }
  129. if b.noBaseImage != true {
  130. t.Fatalf("Image should not have any base image, got: %s", b.noBaseImage)
  131. }
  132. }
  133. }
  134. func TestOnbuildIllegalTriggers(t *testing.T) {
  135. triggers := []struct{ command, expectedError string }{
  136. {"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"},
  137. {"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"},
  138. {"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
  139. for _, trigger := range triggers {
  140. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  141. err := onbuild(b, []string{trigger.command}, nil, "")
  142. if err == nil {
  143. t.Fatalf("Error should not be nil")
  144. }
  145. if !strings.Contains(err.Error(), trigger.expectedError) {
  146. t.Fatalf("Error message not correct. Should be: %s, got: %s", trigger.expectedError, err.Error())
  147. }
  148. }
  149. }
  150. func TestOnbuild(t *testing.T) {
  151. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  152. err := onbuild(b, []string{"ADD", ".", "/app/src"}, nil, "ONBUILD ADD . /app/src")
  153. if err != nil {
  154. t.Fatalf("Error should be empty, got: %s", err.Error())
  155. }
  156. expectedOnbuild := "ADD . /app/src"
  157. if b.runConfig.OnBuild[0] != expectedOnbuild {
  158. t.Fatalf("Wrong ONBUILD command. Expected: %s, got: %s", expectedOnbuild, b.runConfig.OnBuild[0])
  159. }
  160. }
  161. func TestWorkdir(t *testing.T) {
  162. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  163. workingDir := "/app"
  164. if runtime.GOOS == "windows" {
  165. workingDir = "C:\app"
  166. }
  167. err := workdir(b, []string{workingDir}, nil, "")
  168. if err != nil {
  169. t.Fatalf("Error should be empty, got: %s", err.Error())
  170. }
  171. if b.runConfig.WorkingDir != workingDir {
  172. t.Fatalf("WorkingDir should be set to %s, got %s", workingDir, b.runConfig.WorkingDir)
  173. }
  174. }
  175. func TestCmd(t *testing.T) {
  176. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  177. command := "./executable"
  178. err := cmd(b, []string{command}, nil, "")
  179. if err != nil {
  180. t.Fatalf("Error should be empty, got: %s", err.Error())
  181. }
  182. var expectedCommand strslice.StrSlice
  183. if runtime.GOOS == "windows" {
  184. expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
  185. } else {
  186. expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
  187. }
  188. if !compareStrSlice(b.runConfig.Cmd, expectedCommand) {
  189. t.Fatalf("Command should be set to %s, got %s", command, b.runConfig.Cmd)
  190. }
  191. if !b.cmdSet {
  192. t.Fatalf("Command should be marked as set")
  193. }
  194. }
  195. func compareStrSlice(slice1, slice2 strslice.StrSlice) bool {
  196. if len(slice1) != len(slice2) {
  197. return false
  198. }
  199. for i := range slice1 {
  200. if slice1[i] != slice2[i] {
  201. return false
  202. }
  203. }
  204. return true
  205. }
  206. func TestHealthcheckNone(t *testing.T) {
  207. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  208. if err := healthcheck(b, []string{"NONE"}, nil, ""); err != nil {
  209. t.Fatalf("Error should be empty, got: %s", err.Error())
  210. }
  211. if b.runConfig.Healthcheck == nil {
  212. t.Fatal("Healthcheck should be set, got nil")
  213. }
  214. expectedTest := strslice.StrSlice(append([]string{"NONE"}))
  215. if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) {
  216. t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test)
  217. }
  218. }
  219. func TestHealthcheckCmd(t *testing.T) {
  220. b := &Builder{flags: &BFlags{flags: make(map[string]*Flag)}, runConfig: &container.Config{}, disableCommit: true}
  221. if err := healthcheck(b, []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}, nil, ""); err != nil {
  222. t.Fatalf("Error should be empty, got: %s", err.Error())
  223. }
  224. if b.runConfig.Healthcheck == nil {
  225. t.Fatal("Healthcheck should be set, got nil")
  226. }
  227. expectedTest := strslice.StrSlice(append([]string{"CMD-SHELL"}, "curl -f http://localhost/ || exit 1"))
  228. if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) {
  229. t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test)
  230. }
  231. }
  232. func TestEntrypoint(t *testing.T) {
  233. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  234. entrypointCmd := "/usr/sbin/nginx"
  235. if err := entrypoint(b, []string{entrypointCmd}, nil, ""); err != nil {
  236. t.Fatalf("Error should be empty, got: %s", err.Error())
  237. }
  238. if b.runConfig.Entrypoint == nil {
  239. t.Fatalf("Entrypoint should be set")
  240. }
  241. var expectedEntrypoint strslice.StrSlice
  242. if runtime.GOOS == "windows" {
  243. expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
  244. } else {
  245. expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
  246. }
  247. if !compareStrSlice(expectedEntrypoint, b.runConfig.Entrypoint) {
  248. t.Fatalf("Entrypoint command should be set to %s, got %s", expectedEntrypoint, b.runConfig.Entrypoint)
  249. }
  250. }
  251. func TestExpose(t *testing.T) {
  252. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  253. exposedPort := "80"
  254. if err := expose(b, []string{exposedPort}, nil, ""); err != nil {
  255. t.Fatalf("Error should be empty, got: %s", err.Error())
  256. }
  257. if b.runConfig.ExposedPorts == nil {
  258. t.Fatalf("ExposedPorts should be set")
  259. }
  260. if len(b.runConfig.ExposedPorts) != 1 {
  261. t.Fatalf("ExposedPorts should contain only 1 element. Got %s", b.runConfig.ExposedPorts)
  262. }
  263. portsMapping, err := nat.ParsePortSpec(exposedPort)
  264. if err != nil {
  265. t.Fatalf("Error when parsing port spec: %s", err.Error())
  266. }
  267. if _, ok := b.runConfig.ExposedPorts[portsMapping[0].Port]; !ok {
  268. t.Fatalf("Port %s should be present. Got %s", exposedPort, b.runConfig.ExposedPorts)
  269. }
  270. }
  271. func TestUser(t *testing.T) {
  272. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  273. userCommand := "foo"
  274. if err := user(b, []string{userCommand}, nil, ""); err != nil {
  275. t.Fatalf("Error should be empty, got: %s", err.Error())
  276. }
  277. if b.runConfig.User != userCommand {
  278. t.Fatalf("User should be set to %s, got %s", userCommand, b.runConfig.User)
  279. }
  280. }
  281. func TestVolume(t *testing.T) {
  282. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  283. exposedVolume := "/foo"
  284. if err := volume(b, []string{exposedVolume}, nil, ""); err != nil {
  285. t.Fatalf("Error should be empty, got: %s", err.Error())
  286. }
  287. if b.runConfig.Volumes == nil {
  288. t.Fatalf("Volumes should be set")
  289. }
  290. if len(b.runConfig.Volumes) != 1 {
  291. t.Fatalf("Volumes should contain only 1 element. Got %s", b.runConfig.Volumes)
  292. }
  293. if _, ok := b.runConfig.Volumes[exposedVolume]; !ok {
  294. t.Fatalf("Volume %s should be present. Got %s", exposedVolume, b.runConfig.Volumes)
  295. }
  296. }
  297. func TestStopSignal(t *testing.T) {
  298. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  299. signal := "SIGKILL"
  300. if err := stopSignal(b, []string{signal}, nil, ""); err != nil {
  301. t.Fatalf("Error should be empty, got: %s", err.Error())
  302. }
  303. if b.runConfig.StopSignal != signal {
  304. t.Fatalf("StopSignal should be set to %s, got %s", signal, b.runConfig.StopSignal)
  305. }
  306. }
  307. func TestArg(t *testing.T) {
  308. buildOptions := &types.ImageBuildOptions{BuildArgs: make(map[string]string)}
  309. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true, allowedBuildArgs: make(map[string]bool), options: buildOptions}
  310. argName := "foo"
  311. argVal := "bar"
  312. argDef := fmt.Sprintf("%s=%s", argName, argVal)
  313. if err := arg(b, []string{argDef}, nil, ""); err != nil {
  314. t.Fatalf("Error should be empty, got: %s", err.Error())
  315. }
  316. allowed, ok := b.allowedBuildArgs[argName]
  317. if !ok {
  318. t.Fatalf("%s argument should be allowed as a build arg", argName)
  319. }
  320. if !allowed {
  321. t.Fatalf("%s argument was present in map but disallowed as a build arg", argName)
  322. }
  323. val, ok := b.options.BuildArgs[argName]
  324. if !ok {
  325. t.Fatalf("%s argument should be a build arg", argName)
  326. }
  327. if val != "bar" {
  328. t.Fatalf("%s argument should have default value 'bar', got %s", argName, val)
  329. }
  330. }
  331. func TestShell(t *testing.T) {
  332. b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
  333. shellCmd := "powershell"
  334. attrs := make(map[string]bool)
  335. attrs["json"] = true
  336. if err := shell(b, []string{shellCmd}, attrs, ""); err != nil {
  337. t.Fatalf("Error should be empty, got: %s", err.Error())
  338. }
  339. if b.runConfig.Shell == nil {
  340. t.Fatalf("Shell should be set")
  341. }
  342. expectedShell := strslice.StrSlice([]string{shellCmd})
  343. if !compareStrSlice(expectedShell, b.runConfig.Shell) {
  344. t.Fatalf("Shell should be set to %s, got %s", expectedShell, b.runConfig.Shell)
  345. }
  346. }