evaluator_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package dockerfile
  2. import (
  3. "io/ioutil"
  4. "strings"
  5. "testing"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/api/types/container"
  8. "github.com/docker/docker/builder/dockerfile/parser"
  9. "github.com/docker/docker/builder/remotecontext"
  10. "github.com/docker/docker/internal/testutil"
  11. "github.com/docker/docker/pkg/archive"
  12. "github.com/docker/docker/pkg/reexec"
  13. )
  14. type dispatchTestCase struct {
  15. name, dockerfile, expectedError string
  16. files map[string]string
  17. }
  18. func init() {
  19. reexec.Init()
  20. }
  21. func initDispatchTestCases() []dispatchTestCase {
  22. dispatchTestCases := []dispatchTestCase{{
  23. name: "copyEmptyWhitespace",
  24. dockerfile: `COPY
  25. quux \
  26. bar`,
  27. expectedError: "COPY requires at least two arguments",
  28. },
  29. {
  30. name: "ONBUILD forbidden FROM",
  31. dockerfile: "ONBUILD FROM scratch",
  32. expectedError: "FROM isn't allowed as an ONBUILD trigger",
  33. files: nil,
  34. },
  35. {
  36. name: "ONBUILD forbidden MAINTAINER",
  37. dockerfile: "ONBUILD MAINTAINER docker.io",
  38. expectedError: "MAINTAINER isn't allowed as an ONBUILD trigger",
  39. files: nil,
  40. },
  41. {
  42. name: "ARG two arguments",
  43. dockerfile: "ARG foo bar",
  44. expectedError: "ARG requires exactly one argument",
  45. files: nil,
  46. },
  47. {
  48. name: "MAINTAINER unknown flag",
  49. dockerfile: "MAINTAINER --boo joe@example.com",
  50. expectedError: "Unknown flag: boo",
  51. files: nil,
  52. },
  53. {
  54. name: "ADD multiple files to file",
  55. dockerfile: "ADD file1.txt file2.txt test",
  56. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  57. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  58. },
  59. {
  60. name: "JSON ADD multiple files to file",
  61. dockerfile: `ADD ["file1.txt", "file2.txt", "test"]`,
  62. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  63. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  64. },
  65. {
  66. name: "Wildcard ADD multiple files to file",
  67. dockerfile: "ADD file*.txt test",
  68. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  69. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  70. },
  71. {
  72. name: "Wildcard JSON ADD multiple files to file",
  73. dockerfile: `ADD ["file*.txt", "test"]`,
  74. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  75. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  76. },
  77. {
  78. name: "COPY multiple files to file",
  79. dockerfile: "COPY file1.txt file2.txt test",
  80. expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
  81. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  82. },
  83. {
  84. name: "JSON COPY multiple files to file",
  85. dockerfile: `COPY ["file1.txt", "file2.txt", "test"]`,
  86. expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
  87. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  88. },
  89. {
  90. name: "ADD multiple files to file with whitespace",
  91. dockerfile: `ADD [ "test file1.txt", "test file2.txt", "test" ]`,
  92. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  93. files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
  94. },
  95. {
  96. name: "COPY multiple files to file with whitespace",
  97. dockerfile: `COPY [ "test file1.txt", "test file2.txt", "test" ]`,
  98. expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
  99. files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
  100. },
  101. {
  102. name: "COPY wildcard no files",
  103. dockerfile: `COPY file*.txt /tmp/`,
  104. expectedError: "COPY failed: no source files were specified",
  105. files: nil,
  106. },
  107. {
  108. name: "COPY url",
  109. dockerfile: `COPY https://index.docker.io/robots.txt /`,
  110. expectedError: "source can't be a URL for COPY",
  111. files: nil,
  112. },
  113. {
  114. name: "Chaining ONBUILD",
  115. dockerfile: `ONBUILD ONBUILD RUN touch foobar`,
  116. expectedError: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
  117. files: nil,
  118. },
  119. {
  120. name: "Invalid instruction",
  121. dockerfile: `foo bar`,
  122. expectedError: "unknown instruction: FOO",
  123. files: nil,
  124. }}
  125. return dispatchTestCases
  126. }
  127. func TestDispatch(t *testing.T) {
  128. testCases := initDispatchTestCases()
  129. for _, testCase := range testCases {
  130. executeTestCase(t, testCase)
  131. }
  132. }
  133. func executeTestCase(t *testing.T, testCase dispatchTestCase) {
  134. contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
  135. defer cleanup()
  136. for filename, content := range testCase.files {
  137. createTestTempFile(t, contextDir, filename, content, 0777)
  138. }
  139. tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
  140. if err != nil {
  141. t.Fatalf("Error when creating tar stream: %s", err)
  142. }
  143. defer func() {
  144. if err = tarStream.Close(); err != nil {
  145. t.Fatalf("Error when closing tar stream: %s", err)
  146. }
  147. }()
  148. context, err := remotecontext.FromArchive(tarStream)
  149. if err != nil {
  150. t.Fatalf("Error when creating tar context: %s", err)
  151. }
  152. defer func() {
  153. if err = context.Close(); err != nil {
  154. t.Fatalf("Error when closing tar context: %s", err)
  155. }
  156. }()
  157. r := strings.NewReader(testCase.dockerfile)
  158. result, err := parser.Parse(r)
  159. if err != nil {
  160. t.Fatalf("Error when parsing Dockerfile: %s", err)
  161. }
  162. options := &types.ImageBuildOptions{
  163. BuildArgs: make(map[string]*string),
  164. }
  165. b := &Builder{
  166. options: options,
  167. Stdout: ioutil.Discard,
  168. buildArgs: newBuildArgs(options.BuildArgs),
  169. }
  170. shlex := NewShellLex(parser.DefaultEscapeToken)
  171. n := result.AST
  172. state := &dispatchState{runConfig: &container.Config{}}
  173. opts := dispatchOptions{
  174. state: state,
  175. stepMsg: formatStep(0, len(n.Children)),
  176. node: n.Children[0],
  177. shlex: shlex,
  178. source: context,
  179. }
  180. _, err = b.dispatch(opts)
  181. testutil.ErrorContains(t, err, testCase.expectedError)
  182. }