buildfile.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. package docker
  2. import (
  3. "bufio"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/dotcloud/docker/utils"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "path"
  11. "reflect"
  12. "strings"
  13. )
  14. type BuildFile interface {
  15. Build(io.Reader, io.Reader) (string, error)
  16. CmdFrom(string) error
  17. CmdRun(string) error
  18. }
  19. type buildFile struct {
  20. runtime *Runtime
  21. builder *Builder
  22. srv *Server
  23. image string
  24. maintainer string
  25. config *Config
  26. context string
  27. tmpContainers map[string]struct{}
  28. tmpImages map[string]struct{}
  29. needCommit bool
  30. out io.Writer
  31. }
  32. func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
  33. for c := range containers {
  34. tmp := b.runtime.Get(c)
  35. b.runtime.Destroy(tmp)
  36. utils.Debugf("Removing container %s", c)
  37. }
  38. for i := range images {
  39. b.runtime.graph.Delete(i)
  40. utils.Debugf("Removing image %s", i)
  41. }
  42. }
  43. func (b *buildFile) CmdFrom(name string) error {
  44. image, err := b.runtime.repositories.LookupImage(name)
  45. if err != nil {
  46. if b.runtime.graph.IsNotExist(err) {
  47. var tag, remote string
  48. if strings.Contains(name, ":") {
  49. remoteParts := strings.Split(name, ":")
  50. tag = remoteParts[1]
  51. remote = remoteParts[0]
  52. } else {
  53. remote = name
  54. }
  55. if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
  56. return err
  57. }
  58. image, err = b.runtime.repositories.LookupImage(name)
  59. if err != nil {
  60. return err
  61. }
  62. } else {
  63. return err
  64. }
  65. }
  66. b.image = image.Id
  67. b.config = &Config{}
  68. return nil
  69. }
  70. func (b *buildFile) CmdMaintainer(name string) error {
  71. b.needCommit = true
  72. b.maintainer = name
  73. return nil
  74. }
  75. func (b *buildFile) CmdRun(args string) error {
  76. if b.image == "" {
  77. return fmt.Errorf("Please provide a source image with `from` prior to run")
  78. }
  79. config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
  80. if err != nil {
  81. return err
  82. }
  83. cmd, env := b.config.Cmd, b.config.Env
  84. b.config.Cmd = nil
  85. MergeConfig(b.config, config)
  86. if cache, err := b.srv.ImageGetCached(b.image, config); err != nil {
  87. return err
  88. } else if cache != nil {
  89. utils.Debugf("Use cached version")
  90. b.image = cache.Id
  91. return nil
  92. }
  93. cid, err := b.run()
  94. if err != nil {
  95. return err
  96. }
  97. b.config.Cmd, b.config.Env = cmd, env
  98. return b.commit(cid)
  99. }
  100. func (b *buildFile) CmdEnv(args string) error {
  101. b.needCommit = true
  102. tmp := strings.SplitN(args, " ", 2)
  103. if len(tmp) != 2 {
  104. return fmt.Errorf("Invalid ENV format")
  105. }
  106. key := strings.Trim(tmp[0], " ")
  107. value := strings.Trim(tmp[1], " ")
  108. for i, elem := range b.config.Env {
  109. if strings.HasPrefix(elem, key+"=") {
  110. b.config.Env[i] = key + "=" + value
  111. return nil
  112. }
  113. }
  114. b.config.Env = append(b.config.Env, key+"="+value)
  115. return nil
  116. }
  117. func (b *buildFile) CmdCmd(args string) error {
  118. b.needCommit = true
  119. var cmd []string
  120. if err := json.Unmarshal([]byte(args), &cmd); err != nil {
  121. utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
  122. b.config.Cmd = []string{"/bin/sh", "-c", args}
  123. } else {
  124. b.config.Cmd = cmd
  125. }
  126. return nil
  127. }
  128. func (b *buildFile) CmdExpose(args string) error {
  129. ports := strings.Split(args, " ")
  130. b.config.PortSpecs = append(ports, b.config.PortSpecs...)
  131. return nil
  132. }
  133. func (b *buildFile) CmdInsert(args string) error {
  134. if b.image == "" {
  135. return fmt.Errorf("Please provide a source image with `from` prior to insert")
  136. }
  137. tmp := strings.SplitN(args, " ", 2)
  138. if len(tmp) != 2 {
  139. return fmt.Errorf("Invalid INSERT format")
  140. }
  141. sourceUrl := strings.Trim(tmp[0], " ")
  142. destPath := strings.Trim(tmp[1], " ")
  143. file, err := utils.Download(sourceUrl, b.out)
  144. if err != nil {
  145. return err
  146. }
  147. defer file.Body.Close()
  148. b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath}
  149. cid, err := b.run()
  150. if err != nil {
  151. return err
  152. }
  153. container := b.runtime.Get(cid)
  154. if container == nil {
  155. return fmt.Errorf("An error occured while creating the container")
  156. }
  157. if err := container.Inject(file.Body, destPath); err != nil {
  158. return err
  159. }
  160. return b.commit(cid)
  161. }
  162. func (b *buildFile) CmdAdd(args string) error {
  163. if b.context == "" {
  164. return fmt.Errorf("No context given. Impossible to use ADD")
  165. }
  166. tmp := strings.SplitN(args, " ", 2)
  167. if len(tmp) != 2 {
  168. return fmt.Errorf("Invalid INSERT format")
  169. }
  170. orig := strings.Trim(tmp[0], " ")
  171. dest := strings.Trim(tmp[1], " ")
  172. b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest}
  173. cid, err := b.run()
  174. if err != nil {
  175. return err
  176. }
  177. container := b.runtime.Get(cid)
  178. if container == nil {
  179. return fmt.Errorf("Error while creating the container (CmdAdd)")
  180. }
  181. if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
  182. return err
  183. }
  184. origPath := path.Join(b.context, orig)
  185. destPath := path.Join(container.rwPath(), dest)
  186. fi, err := os.Stat(origPath)
  187. if err != nil {
  188. return err
  189. }
  190. if fi.IsDir() {
  191. files, err := ioutil.ReadDir(path.Join(b.context, orig))
  192. if err != nil {
  193. return err
  194. }
  195. for _, fi := range files {
  196. if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
  197. return err
  198. }
  199. }
  200. } else {
  201. if err := utils.CopyDirectory(origPath, destPath); err != nil {
  202. return err
  203. }
  204. }
  205. return b.commit(cid)
  206. }
  207. func (b *buildFile) run() (string, error) {
  208. if b.image == "" {
  209. return "", fmt.Errorf("Please provide a source image with `from` prior to run")
  210. }
  211. b.config.Image = b.image
  212. // Create the container and start it
  213. c, err := b.builder.Create(b.config)
  214. if err != nil {
  215. return "", err
  216. }
  217. b.tmpContainers[c.Id] = struct{}{}
  218. //start the container
  219. if err := c.Start(); err != nil {
  220. return "", err
  221. }
  222. // Wait for it to finish
  223. if ret := c.Wait(); ret != 0 {
  224. return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
  225. }
  226. return c.Id, nil
  227. }
  228. func (b *buildFile) commit(id string) error {
  229. if b.image == "" {
  230. return fmt.Errorf("Please provide a source image with `from` prior to commit")
  231. }
  232. b.config.Image = b.image
  233. if id == "" {
  234. cmd := b.config.Cmd
  235. b.config.Cmd = []string{"true"}
  236. if cid, err := b.run(); err != nil {
  237. return err
  238. } else {
  239. id = cid
  240. }
  241. b.config.Cmd = cmd
  242. }
  243. container := b.runtime.Get(id)
  244. if container == nil {
  245. return fmt.Errorf("An error occured while creating the container")
  246. }
  247. // Commit the container
  248. image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil)
  249. if err != nil {
  250. return err
  251. }
  252. b.tmpImages[image.Id] = struct{}{}
  253. b.image = image.Id
  254. b.needCommit = false
  255. return nil
  256. }
  257. func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
  258. defer b.clearTmp(b.tmpContainers, b.tmpImages)
  259. if context != nil {
  260. name, err := ioutil.TempDir("/tmp", "docker-build")
  261. if err != nil {
  262. return "", err
  263. }
  264. if err := Untar(context, name); err != nil {
  265. return "", err
  266. }
  267. defer os.RemoveAll(name)
  268. b.context = name
  269. }
  270. file := bufio.NewReader(dockerfile)
  271. for {
  272. line, err := file.ReadString('\n')
  273. if err != nil {
  274. if err == io.EOF {
  275. break
  276. }
  277. return "", err
  278. }
  279. line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
  280. // Skip comments and empty line
  281. if len(line) == 0 || line[0] == '#' {
  282. continue
  283. }
  284. tmp := strings.SplitN(line, " ", 2)
  285. if len(tmp) != 2 {
  286. return "", fmt.Errorf("Invalid Dockerfile format")
  287. }
  288. instruction := strings.ToLower(strings.Trim(tmp[0], " "))
  289. arguments := strings.Trim(tmp[1], " ")
  290. fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
  291. method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
  292. if !exists {
  293. fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
  294. }
  295. ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
  296. if ret != nil {
  297. return "", ret.(error)
  298. }
  299. fmt.Fprintf(b.out, "===> %v\n", b.image)
  300. }
  301. if b.needCommit {
  302. if err := b.commit(""); err != nil {
  303. return "", err
  304. }
  305. }
  306. if b.image != "" {
  307. // The build is successful, keep the temporary containers and images
  308. for i := range b.tmpImages {
  309. delete(b.tmpImages, i)
  310. }
  311. fmt.Fprintf(b.out, "Build finished. image id: %s\n", b.image)
  312. return b.image, nil
  313. }
  314. for i := range b.tmpContainers {
  315. delete(b.tmpContainers, i)
  316. }
  317. return "", fmt.Errorf("An error occured during the build\n")
  318. }
  319. func NewBuildFile(srv *Server, out io.Writer) BuildFile {
  320. return &buildFile{
  321. builder: NewBuilder(srv.runtime),
  322. runtime: srv.runtime,
  323. srv: srv,
  324. config: &Config{},
  325. out: out,
  326. tmpContainers: make(map[string]struct{}),
  327. tmpImages: make(map[string]struct{}),
  328. }
  329. }