internals_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package dockerfile
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "runtime"
  7. "testing"
  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/builder"
  12. "github.com/docker/docker/builder/remotecontext"
  13. "github.com/docker/docker/pkg/archive"
  14. "github.com/docker/docker/pkg/idtools"
  15. "github.com/docker/go-connections/nat"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. )
  19. func TestEmptyDockerfile(t *testing.T) {
  20. contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
  21. defer cleanup()
  22. createTestTempFile(t, contextDir, builder.DefaultDockerfileName, "", 0777)
  23. readAndCheckDockerfile(t, "emptyDockerfile", contextDir, "", "the Dockerfile (Dockerfile) cannot be empty")
  24. }
  25. func TestSymlinkDockerfile(t *testing.T) {
  26. contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
  27. defer cleanup()
  28. createTestSymlink(t, contextDir, builder.DefaultDockerfileName, "/etc/passwd")
  29. // The reason the error is "Cannot locate specified Dockerfile" is because
  30. // in the builder, the symlink is resolved within the context, therefore
  31. // Dockerfile -> /etc/passwd becomes etc/passwd from the context which is
  32. // a nonexistent file.
  33. expectedError := fmt.Sprintf("Cannot locate specified Dockerfile: %s", builder.DefaultDockerfileName)
  34. readAndCheckDockerfile(t, "symlinkDockerfile", contextDir, builder.DefaultDockerfileName, expectedError)
  35. }
  36. func TestDockerfileOutsideTheBuildContext(t *testing.T) {
  37. contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
  38. defer cleanup()
  39. expectedError := "Forbidden path outside the build context: ../../Dockerfile ()"
  40. readAndCheckDockerfile(t, "DockerfileOutsideTheBuildContext", contextDir, "../../Dockerfile", expectedError)
  41. }
  42. func TestNonExistingDockerfile(t *testing.T) {
  43. contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
  44. defer cleanup()
  45. expectedError := "Cannot locate specified Dockerfile: Dockerfile"
  46. readAndCheckDockerfile(t, "NonExistingDockerfile", contextDir, "Dockerfile", expectedError)
  47. }
  48. func readAndCheckDockerfile(t *testing.T, testName, contextDir, dockerfilePath, expectedError string) {
  49. tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
  50. require.NoError(t, err)
  51. defer func() {
  52. if err = tarStream.Close(); err != nil {
  53. t.Fatalf("Error when closing tar stream: %s", err)
  54. }
  55. }()
  56. if dockerfilePath == "" { // handled in BuildWithContext
  57. dockerfilePath = builder.DefaultDockerfileName
  58. }
  59. config := backend.BuildConfig{
  60. Options: &types.ImageBuildOptions{Dockerfile: dockerfilePath},
  61. Source: tarStream,
  62. }
  63. _, _, err = remotecontext.Detect(config)
  64. assert.EqualError(t, err, expectedError)
  65. }
  66. func TestCopyRunConfig(t *testing.T) {
  67. defaultEnv := []string{"foo=1"}
  68. defaultCmd := []string{"old"}
  69. var testcases = []struct {
  70. doc string
  71. modifiers []runConfigModifier
  72. expected *container.Config
  73. }{
  74. {
  75. doc: "Set the command",
  76. modifiers: []runConfigModifier{withCmd([]string{"new"})},
  77. expected: &container.Config{
  78. Cmd: []string{"new"},
  79. Env: defaultEnv,
  80. },
  81. },
  82. {
  83. doc: "Set the command to a comment",
  84. modifiers: []runConfigModifier{withCmdComment("comment", runtime.GOOS)},
  85. expected: &container.Config{
  86. Cmd: append(defaultShellForOS(runtime.GOOS), "#(nop) ", "comment"),
  87. Env: defaultEnv,
  88. },
  89. },
  90. {
  91. doc: "Set the command and env",
  92. modifiers: []runConfigModifier{
  93. withCmd([]string{"new"}),
  94. withEnv([]string{"one", "two"}),
  95. },
  96. expected: &container.Config{
  97. Cmd: []string{"new"},
  98. Env: []string{"one", "two"},
  99. },
  100. },
  101. }
  102. for _, testcase := range testcases {
  103. runConfig := &container.Config{
  104. Cmd: defaultCmd,
  105. Env: defaultEnv,
  106. }
  107. runConfigCopy := copyRunConfig(runConfig, testcase.modifiers...)
  108. assert.Equal(t, testcase.expected, runConfigCopy, testcase.doc)
  109. // Assert the original was not modified
  110. assert.NotEqual(t, runConfig, runConfigCopy, testcase.doc)
  111. }
  112. }
  113. func fullMutableRunConfig() *container.Config {
  114. return &container.Config{
  115. Cmd: []string{"command", "arg1"},
  116. Env: []string{"env1=foo", "env2=bar"},
  117. ExposedPorts: nat.PortSet{
  118. "1000/tcp": {},
  119. "1001/tcp": {},
  120. },
  121. Volumes: map[string]struct{}{
  122. "one": {},
  123. "two": {},
  124. },
  125. Entrypoint: []string{"entry", "arg1"},
  126. OnBuild: []string{"first", "next"},
  127. Labels: map[string]string{
  128. "label1": "value1",
  129. "label2": "value2",
  130. },
  131. Shell: []string{"shell", "-c"},
  132. }
  133. }
  134. func TestDeepCopyRunConfig(t *testing.T) {
  135. runConfig := fullMutableRunConfig()
  136. copy := copyRunConfig(runConfig)
  137. assert.Equal(t, fullMutableRunConfig(), copy)
  138. copy.Cmd[1] = "arg2"
  139. copy.Env[1] = "env2=new"
  140. copy.ExposedPorts["10002"] = struct{}{}
  141. copy.Volumes["three"] = struct{}{}
  142. copy.Entrypoint[1] = "arg2"
  143. copy.OnBuild[0] = "start"
  144. copy.Labels["label3"] = "value3"
  145. copy.Shell[0] = "sh"
  146. assert.Equal(t, fullMutableRunConfig(), runConfig)
  147. }
  148. func TestChownFlagParsing(t *testing.T) {
  149. testFiles := map[string]string{
  150. "passwd": `root:x:0:0::/bin:/bin/false
  151. bin:x:1:1::/bin:/bin/false
  152. wwwwww:x:21:33::/bin:/bin/false
  153. unicorn:x:1001:1002::/bin:/bin/false
  154. `,
  155. "group": `root:x:0:
  156. bin:x:1:
  157. wwwwww:x:33:
  158. unicorn:x:1002:
  159. somegrp:x:5555:
  160. othergrp:x:6666:
  161. `,
  162. }
  163. // test mappings for validating use of maps
  164. idMaps := []idtools.IDMap{
  165. {
  166. ContainerID: 0,
  167. HostID: 100000,
  168. Size: 65536,
  169. },
  170. }
  171. remapped := idtools.NewIDMappingsFromMaps(idMaps, idMaps)
  172. unmapped := &idtools.IDMappings{}
  173. contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test")
  174. defer cleanup()
  175. if err := os.Mkdir(filepath.Join(contextDir, "etc"), 0755); err != nil {
  176. t.Fatalf("error creating test directory: %v", err)
  177. }
  178. for filename, content := range testFiles {
  179. createTestTempFile(t, filepath.Join(contextDir, "etc"), filename, content, 0644)
  180. }
  181. // positive tests
  182. for _, testcase := range []struct {
  183. name string
  184. chownStr string
  185. idMapping *idtools.IDMappings
  186. expected idtools.IDPair
  187. }{
  188. {
  189. name: "UIDNoMap",
  190. chownStr: "1",
  191. idMapping: unmapped,
  192. expected: idtools.IDPair{UID: 1, GID: 1},
  193. },
  194. {
  195. name: "UIDGIDNoMap",
  196. chownStr: "0:1",
  197. idMapping: unmapped,
  198. expected: idtools.IDPair{UID: 0, GID: 1},
  199. },
  200. {
  201. name: "UIDWithMap",
  202. chownStr: "0",
  203. idMapping: remapped,
  204. expected: idtools.IDPair{UID: 100000, GID: 100000},
  205. },
  206. {
  207. name: "UIDGIDWithMap",
  208. chownStr: "1:33",
  209. idMapping: remapped,
  210. expected: idtools.IDPair{UID: 100001, GID: 100033},
  211. },
  212. {
  213. name: "UserNoMap",
  214. chownStr: "bin:5555",
  215. idMapping: unmapped,
  216. expected: idtools.IDPair{UID: 1, GID: 5555},
  217. },
  218. {
  219. name: "GroupWithMap",
  220. chownStr: "0:unicorn",
  221. idMapping: remapped,
  222. expected: idtools.IDPair{UID: 100000, GID: 101002},
  223. },
  224. {
  225. name: "UserOnlyWithMap",
  226. chownStr: "unicorn",
  227. idMapping: remapped,
  228. expected: idtools.IDPair{UID: 101001, GID: 101002},
  229. },
  230. } {
  231. t.Run(testcase.name, func(t *testing.T) {
  232. idPair, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping)
  233. require.NoError(t, err, "Failed to parse chown flag: %q", testcase.chownStr)
  234. assert.Equal(t, testcase.expected, idPair, "chown flag mapping failure")
  235. })
  236. }
  237. // error tests
  238. for _, testcase := range []struct {
  239. name string
  240. chownStr string
  241. idMapping *idtools.IDMappings
  242. descr string
  243. }{
  244. {
  245. name: "BadChownFlagFormat",
  246. chownStr: "bob:1:555",
  247. idMapping: unmapped,
  248. descr: "invalid chown string format: bob:1:555",
  249. },
  250. {
  251. name: "UserNoExist",
  252. chownStr: "bob",
  253. idMapping: unmapped,
  254. descr: "can't find uid for user bob: no such user: bob",
  255. },
  256. {
  257. name: "GroupNoExist",
  258. chownStr: "root:bob",
  259. idMapping: unmapped,
  260. descr: "can't find gid for group bob: no such group: bob",
  261. },
  262. } {
  263. t.Run(testcase.name, func(t *testing.T) {
  264. _, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping)
  265. assert.EqualError(t, err, testcase.descr, "Expected error string doesn't match")
  266. })
  267. }
  268. }