builder.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package docker
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "path"
  8. "strings"
  9. "time"
  10. )
  11. type Builder struct {
  12. runtime *Runtime
  13. repositories *TagStore
  14. graph *Graph
  15. }
  16. func NewBuilder(runtime *Runtime) *Builder {
  17. return &Builder{
  18. runtime: runtime,
  19. graph: runtime.graph,
  20. repositories: runtime.repositories,
  21. }
  22. }
  23. func (builder *Builder) Create(config *Config) (*Container, error) {
  24. // Lookup image
  25. img, err := builder.repositories.LookupImage(config.Image)
  26. if err != nil {
  27. return nil, err
  28. }
  29. // Generate id
  30. id := GenerateId()
  31. // Generate default hostname
  32. // FIXME: the lxc template no longer needs to set a default hostname
  33. if config.Hostname == "" {
  34. config.Hostname = id[:12]
  35. }
  36. container := &Container{
  37. // FIXME: we should generate the ID here instead of receiving it as an argument
  38. Id: id,
  39. Created: time.Now(),
  40. Path: config.Cmd[0],
  41. Args: config.Cmd[1:], //FIXME: de-duplicate from config
  42. Config: config,
  43. Image: img.Id, // Always use the resolved image id
  44. NetworkSettings: &NetworkSettings{},
  45. // FIXME: do we need to store this in the container?
  46. SysInitPath: sysInitPath,
  47. }
  48. container.root = builder.runtime.containerRoot(container.Id)
  49. // Step 1: create the container directory.
  50. // This doubles as a barrier to avoid race conditions.
  51. if err := os.Mkdir(container.root, 0700); err != nil {
  52. return nil, err
  53. }
  54. // If custom dns exists, then create a resolv.conf for the container
  55. if len(config.Dns) > 0 {
  56. container.ResolvConfPath = path.Join(container.root, "resolv.conf")
  57. f, err := os.Create(container.ResolvConfPath)
  58. if err != nil {
  59. return nil, err
  60. }
  61. defer f.Close()
  62. for _, dns := range config.Dns {
  63. if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
  64. return nil, err
  65. }
  66. }
  67. } else {
  68. container.ResolvConfPath = "/etc/resolv.conf"
  69. }
  70. // Step 2: save the container json
  71. if err := container.ToDisk(); err != nil {
  72. return nil, err
  73. }
  74. // Step 3: register the container
  75. if err := builder.runtime.Register(container); err != nil {
  76. return nil, err
  77. }
  78. return container, nil
  79. }
  80. // Commit creates a new filesystem image from the current state of a container.
  81. // The image can optionally be tagged into a repository
  82. func (builder *Builder) Commit(container *Container, repository, tag, comment, author string) (*Image, error) {
  83. // FIXME: freeze the container before copying it to avoid data corruption?
  84. // FIXME: this shouldn't be in commands.
  85. rwTar, err := container.ExportRw()
  86. if err != nil {
  87. return nil, err
  88. }
  89. // Create a new image from the container's base layers + a new layer from container changes
  90. img, err := builder.graph.Create(rwTar, container, comment, author)
  91. if err != nil {
  92. return nil, err
  93. }
  94. // Register the image if needed
  95. if repository != "" {
  96. if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil {
  97. return img, err
  98. }
  99. }
  100. return img, nil
  101. }
  102. func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
  103. for c := range containers {
  104. tmp := builder.runtime.Get(c)
  105. builder.runtime.Destroy(tmp)
  106. Debugf("Removing container %s", c)
  107. }
  108. for i := range images {
  109. builder.runtime.graph.Delete(i)
  110. Debugf("Removing image %s", i)
  111. }
  112. }
  113. func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error {
  114. var (
  115. image, base *Image
  116. tmpContainers map[string]struct{} = make(map[string]struct{})
  117. tmpImages map[string]struct{} = make(map[string]struct{})
  118. )
  119. defer builder.clearTmp(tmpContainers, tmpImages)
  120. file := bufio.NewReader(dockerfile)
  121. for {
  122. line, err := file.ReadString('\n')
  123. if err != nil {
  124. if err == io.EOF {
  125. break
  126. }
  127. return err
  128. }
  129. line = strings.TrimSpace(line)
  130. // Skip comments and empty line
  131. if len(line) == 0 || line[0] == '#' {
  132. continue
  133. }
  134. tmp := strings.SplitN(line, " ", 2)
  135. if len(tmp) != 2 {
  136. return fmt.Errorf("Invalid Dockerfile format")
  137. }
  138. switch tmp[0] {
  139. case "from":
  140. fmt.Fprintf(stdout, "FROM %s\n", tmp[1])
  141. image, err = builder.runtime.repositories.LookupImage(tmp[1])
  142. if err != nil {
  143. return err
  144. }
  145. break
  146. case "run":
  147. fmt.Fprintf(stdout, "RUN %s\n", tmp[1])
  148. if image == nil {
  149. return fmt.Errorf("Please provide a source image with `from` prior to run")
  150. }
  151. config, err := ParseRun([]string{image.Id, "/bin/sh", "-c", tmp[1]}, nil, builder.runtime.capabilities)
  152. if err != nil {
  153. return err
  154. }
  155. // Create the container and start it
  156. c, err := builder.Create(config)
  157. if err != nil {
  158. return err
  159. }
  160. if err := c.Start(); err != nil {
  161. return err
  162. }
  163. tmpContainers[c.Id] = struct{}{}
  164. // Wait for it to finish
  165. if result := c.Wait(); result != 0 {
  166. return fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", tmp[1], result)
  167. }
  168. // Commit the container
  169. base, err = builder.Commit(c, "", "", "", "")
  170. if err != nil {
  171. return err
  172. }
  173. tmpImages[base.Id] = struct{}{}
  174. fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
  175. break
  176. case "copy":
  177. if image == nil {
  178. return fmt.Errorf("Please provide a source image with `from` prior to copy")
  179. }
  180. tmp2 := strings.SplitN(tmp[1], " ", 2)
  181. if len(tmp) != 2 {
  182. return fmt.Errorf("Invalid COPY format")
  183. }
  184. fmt.Fprintf(stdout, "COPY %s to %s in %s\n", tmp2[0], tmp2[1], base.ShortId())
  185. file, err := Download(tmp2[0], stdout)
  186. if err != nil {
  187. return err
  188. }
  189. defer file.Body.Close()
  190. config, err := ParseRun([]string{base.Id, "echo", "insert", tmp2[0], tmp2[1]}, nil, builder.runtime.capabilities)
  191. if err != nil {
  192. return err
  193. }
  194. c, err := builder.Create(config)
  195. if err != nil {
  196. return err
  197. }
  198. if err := c.Start(); err != nil {
  199. return err
  200. }
  201. // Wait for echo to finish
  202. if result := c.Wait(); result != 0 {
  203. return fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", tmp[1], result)
  204. }
  205. if err := c.Inject(file.Body, tmp2[1]); err != nil {
  206. return err
  207. }
  208. base, err = builder.Commit(c, "", "", "", "")
  209. if err != nil {
  210. return err
  211. }
  212. fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
  213. break
  214. default:
  215. fmt.Fprintf(stdout, "Skipping unknown op %s\n", tmp[0])
  216. }
  217. }
  218. if base != nil {
  219. // The build is successful, keep the temporary containers and images
  220. for i := range tmpImages {
  221. delete(tmpImages, i)
  222. }
  223. for i := range tmpContainers {
  224. delete(tmpContainers, i)
  225. }
  226. fmt.Fprintf(stdout, "Build finished. image id: %s\n", base.ShortId())
  227. } else {
  228. fmt.Fprintf(stdout, "An error occured during the build\n")
  229. }
  230. return nil
  231. }