internals.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. package evaluator
  2. func (b *buildFile) addContext(context io.Reader) (string, error) {
  3. tmpdirPath, err := ioutil.TempDir("", "docker-build")
  4. if err != nil {
  5. return err
  6. }
  7. decompressedStream, err := archive.DecompressStream(context)
  8. if err != nil {
  9. return err
  10. }
  11. b.context = &tarsum.TarSum{Reader: decompressedStream, DisableCompression: true}
  12. if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
  13. return err
  14. }
  15. b.contextPath = tmpdirPath
  16. return tmpdirPath
  17. }
  18. func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
  19. if b.image == "" {
  20. return fmt.Errorf("Please provide a source image with `from` prior to commit")
  21. }
  22. b.config.Image = b.image
  23. if id == "" {
  24. cmd := b.config.Cmd
  25. b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
  26. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  27. hit, err := b.probeCache()
  28. if err != nil {
  29. return err
  30. }
  31. if hit {
  32. return nil
  33. }
  34. container, warnings, err := b.daemon.Create(b.config, "")
  35. if err != nil {
  36. return err
  37. }
  38. for _, warning := range warnings {
  39. fmt.Fprintf(b.outStream, " ---> [Warning] %s\n", warning)
  40. }
  41. b.tmpContainers[container.ID] = struct{}{}
  42. fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
  43. id = container.ID
  44. if err := container.Mount(); err != nil {
  45. return err
  46. }
  47. defer container.Unmount()
  48. }
  49. container := b.daemon.Get(id)
  50. if container == nil {
  51. return fmt.Errorf("An error occured while creating the container")
  52. }
  53. // Note: Actually copy the struct
  54. autoConfig := *b.config
  55. autoConfig.Cmd = autoCmd
  56. // Commit the container
  57. image, err := b.daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
  58. if err != nil {
  59. return err
  60. }
  61. b.tmpImages[image.ID] = struct{}{}
  62. b.image = image.ID
  63. return nil
  64. }
  65. func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecompression bool, cmdName string) error {
  66. if b.context == nil {
  67. return fmt.Errorf("No context given. Impossible to use %s", cmdName)
  68. }
  69. tmp := strings.SplitN(args, " ", 2)
  70. if len(tmp) != 2 {
  71. return fmt.Errorf("Invalid %s format", cmdName)
  72. }
  73. orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
  74. if err != nil {
  75. return err
  76. }
  77. dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
  78. if err != nil {
  79. return err
  80. }
  81. cmd := b.config.Cmd
  82. b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, orig, dest)}
  83. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  84. b.config.Image = b.image
  85. var (
  86. origPath = orig
  87. destPath = dest
  88. remoteHash string
  89. isRemote bool
  90. decompress = true
  91. )
  92. isRemote = utils.IsURL(orig)
  93. if isRemote && !allowRemote {
  94. return fmt.Errorf("Source can't be an URL for %s", cmdName)
  95. } else if utils.IsURL(orig) {
  96. // Initiate the download
  97. resp, err := utils.Download(orig)
  98. if err != nil {
  99. return err
  100. }
  101. // Create a tmp dir
  102. tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
  103. if err != nil {
  104. return err
  105. }
  106. // Create a tmp file within our tmp dir
  107. tmpFileName := path.Join(tmpDirName, "tmp")
  108. tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
  109. if err != nil {
  110. return err
  111. }
  112. defer os.RemoveAll(tmpDirName)
  113. // Download and dump result to tmp file
  114. if _, err := io.Copy(tmpFile, resp.Body); err != nil {
  115. tmpFile.Close()
  116. return err
  117. }
  118. tmpFile.Close()
  119. // Remove the mtime of the newly created tmp file
  120. if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
  121. return err
  122. }
  123. origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
  124. // Process the checksum
  125. r, err := archive.Tar(tmpFileName, archive.Uncompressed)
  126. if err != nil {
  127. return err
  128. }
  129. tarSum := &tarsum.TarSum{Reader: r, DisableCompression: true}
  130. if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
  131. return err
  132. }
  133. remoteHash = tarSum.Sum(nil)
  134. r.Close()
  135. // If the destination is a directory, figure out the filename.
  136. if strings.HasSuffix(dest, "/") {
  137. u, err := url.Parse(orig)
  138. if err != nil {
  139. return err
  140. }
  141. path := u.Path
  142. if strings.HasSuffix(path, "/") {
  143. path = path[:len(path)-1]
  144. }
  145. parts := strings.Split(path, "/")
  146. filename := parts[len(parts)-1]
  147. if filename == "" {
  148. return fmt.Errorf("cannot determine filename from url: %s", u)
  149. }
  150. destPath = dest + filename
  151. }
  152. }
  153. if err := b.checkPathForAddition(origPath); err != nil {
  154. return err
  155. }
  156. // Hash path and check the cache
  157. if b.utilizeCache {
  158. var (
  159. hash string
  160. sums = b.context.GetSums()
  161. )
  162. if remoteHash != "" {
  163. hash = remoteHash
  164. } else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil {
  165. return err
  166. } else if fi.IsDir() {
  167. var subfiles []string
  168. for file, sum := range sums {
  169. absFile := path.Join(b.contextPath, file)
  170. absOrigPath := path.Join(b.contextPath, origPath)
  171. if strings.HasPrefix(absFile, absOrigPath) {
  172. subfiles = append(subfiles, sum)
  173. }
  174. }
  175. sort.Strings(subfiles)
  176. hasher := sha256.New()
  177. hasher.Write([]byte(strings.Join(subfiles, ",")))
  178. hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
  179. } else {
  180. if origPath[0] == '/' && len(origPath) > 1 {
  181. origPath = origPath[1:]
  182. }
  183. origPath = strings.TrimPrefix(origPath, "./")
  184. if h, ok := sums[origPath]; ok {
  185. hash = "file:" + h
  186. }
  187. }
  188. b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, hash, dest)}
  189. hit, err := b.probeCache()
  190. if err != nil {
  191. return err
  192. }
  193. // If we do not have a hash, never use the cache
  194. if hit && hash != "" {
  195. return nil
  196. }
  197. }
  198. // Create the container
  199. container, _, err := b.daemon.Create(b.config, "")
  200. if err != nil {
  201. return err
  202. }
  203. b.tmpContainers[container.ID] = struct{}{}
  204. if err := container.Mount(); err != nil {
  205. return err
  206. }
  207. defer container.Unmount()
  208. if !allowDecompression || isRemote {
  209. decompress = false
  210. }
  211. if err := b.addContext(container, origPath, destPath, decompress); err != nil {
  212. return err
  213. }
  214. if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, orig, dest)); err != nil {
  215. return err
  216. }
  217. return nil
  218. }