builder.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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) mergeConfig(userConf, imageConf *Config) {
  24. if userConf.Hostname != "" {
  25. userConf.Hostname = imageConf.Hostname
  26. }
  27. if userConf.User != "" {
  28. userConf.User = imageConf.User
  29. }
  30. if userConf.Memory == 0 {
  31. userConf.Memory = imageConf.Memory
  32. }
  33. if userConf.MemorySwap == 0 {
  34. userConf.MemorySwap = imageConf.MemorySwap
  35. }
  36. if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
  37. userConf.PortSpecs = imageConf.PortSpecs
  38. }
  39. if !userConf.Tty {
  40. userConf.Tty = userConf.Tty
  41. }
  42. if !userConf.OpenStdin {
  43. userConf.OpenStdin = imageConf.OpenStdin
  44. }
  45. if !userConf.StdinOnce {
  46. userConf.StdinOnce = imageConf.StdinOnce
  47. }
  48. if userConf.Env == nil || len(userConf.Env) == 0 {
  49. userConf.Env = imageConf.Env
  50. }
  51. if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
  52. userConf.Cmd = imageConf.Cmd
  53. }
  54. if userConf.Dns == nil || len(userConf.Dns) == 0 {
  55. userConf.Dns = imageConf.Dns
  56. }
  57. }
  58. func (builder *Builder) Create(config *Config) (*Container, error) {
  59. // Lookup image
  60. img, err := builder.repositories.LookupImage(config.Image)
  61. if err != nil {
  62. return nil, err
  63. }
  64. if img.Config != nil {
  65. builder.mergeConfig(config, img.Config)
  66. }
  67. if config.Cmd == nil {
  68. return nil, fmt.Errorf("No command specified")
  69. }
  70. // Generate id
  71. id := GenerateId()
  72. // Generate default hostname
  73. // FIXME: the lxc template no longer needs to set a default hostname
  74. if config.Hostname == "" {
  75. config.Hostname = id[:12]
  76. }
  77. container := &Container{
  78. // FIXME: we should generate the ID here instead of receiving it as an argument
  79. Id: id,
  80. Created: time.Now(),
  81. Path: config.Cmd[0],
  82. Args: config.Cmd[1:], //FIXME: de-duplicate from config
  83. Config: config,
  84. Image: img.Id, // Always use the resolved image id
  85. NetworkSettings: &NetworkSettings{},
  86. // FIXME: do we need to store this in the container?
  87. SysInitPath: sysInitPath,
  88. }
  89. container.root = builder.runtime.containerRoot(container.Id)
  90. // Step 1: create the container directory.
  91. // This doubles as a barrier to avoid race conditions.
  92. if err := os.Mkdir(container.root, 0700); err != nil {
  93. return nil, err
  94. }
  95. // If custom dns exists, then create a resolv.conf for the container
  96. if len(config.Dns) > 0 {
  97. container.ResolvConfPath = path.Join(container.root, "resolv.conf")
  98. f, err := os.Create(container.ResolvConfPath)
  99. if err != nil {
  100. return nil, err
  101. }
  102. defer f.Close()
  103. for _, dns := range config.Dns {
  104. if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
  105. return nil, err
  106. }
  107. }
  108. } else {
  109. container.ResolvConfPath = "/etc/resolv.conf"
  110. }
  111. // Step 2: save the container json
  112. if err := container.ToDisk(); err != nil {
  113. return nil, err
  114. }
  115. // Step 3: register the container
  116. if err := builder.runtime.Register(container); err != nil {
  117. return nil, err
  118. }
  119. return container, nil
  120. }
  121. // Commit creates a new filesystem image from the current state of a container.
  122. // The image can optionally be tagged into a repository
  123. func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
  124. // FIXME: freeze the container before copying it to avoid data corruption?
  125. // FIXME: this shouldn't be in commands.
  126. rwTar, err := container.ExportRw()
  127. if err != nil {
  128. return nil, err
  129. }
  130. // Create a new image from the container's base layers + a new layer from container changes
  131. img, err := builder.graph.Create(rwTar, container, comment, author, config)
  132. if err != nil {
  133. return nil, err
  134. }
  135. // Register the image if needed
  136. if repository != "" {
  137. if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil {
  138. return img, err
  139. }
  140. }
  141. return img, nil
  142. }
  143. func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
  144. for c := range containers {
  145. tmp := builder.runtime.Get(c)
  146. builder.runtime.Destroy(tmp)
  147. Debugf("Removing container %s", c)
  148. }
  149. for i := range images {
  150. builder.runtime.graph.Delete(i)
  151. Debugf("Removing image %s", i)
  152. }
  153. }
  154. func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) {
  155. // Retrieve all images
  156. images, err := builder.graph.All()
  157. if err != nil {
  158. return nil, err
  159. }
  160. // Store the tree in a map of map (map[parentId][childId])
  161. imageMap := make(map[string]map[string]struct{})
  162. for _, img := range images {
  163. if _, exists := imageMap[img.Parent]; !exists {
  164. imageMap[img.Parent] = make(map[string]struct{})
  165. }
  166. imageMap[img.Parent][img.Id] = struct{}{}
  167. }
  168. // Loop on the children of the given image and check the config
  169. for elem := range imageMap[image.Id] {
  170. img, err := builder.graph.Get(elem)
  171. if err != nil {
  172. return nil, err
  173. }
  174. if CompareConfig(&img.ContainerConfig, config) {
  175. return img, nil
  176. }
  177. }
  178. return nil, nil
  179. }
  180. func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
  181. var (
  182. image, base *Image
  183. maintainer string
  184. tmpContainers map[string]struct{} = make(map[string]struct{})
  185. tmpImages map[string]struct{} = make(map[string]struct{})
  186. )
  187. defer builder.clearTmp(tmpContainers, tmpImages)
  188. file := bufio.NewReader(dockerfile)
  189. for {
  190. line, err := file.ReadString('\n')
  191. if err != nil {
  192. if err == io.EOF {
  193. break
  194. }
  195. return nil, err
  196. }
  197. line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
  198. // Skip comments and empty line
  199. if len(line) == 0 || line[0] == '#' {
  200. continue
  201. }
  202. tmp := strings.SplitN(line, " ", 2)
  203. if len(tmp) != 2 {
  204. return nil, fmt.Errorf("Invalid Dockerfile format")
  205. }
  206. instruction := strings.Trim(tmp[0], " ")
  207. arguments := strings.Trim(tmp[1], " ")
  208. switch strings.ToLower(instruction) {
  209. case "from":
  210. fmt.Fprintf(stdout, "FROM %s\n", arguments)
  211. image, err = builder.runtime.repositories.LookupImage(arguments)
  212. if err != nil {
  213. if builder.runtime.graph.IsNotExist(err) {
  214. var tag, remote string
  215. if strings.Contains(arguments, ":") {
  216. remoteParts := strings.Split(arguments, ":")
  217. tag = remoteParts[1]
  218. remote = remoteParts[0]
  219. } else {
  220. remote = arguments
  221. }
  222. if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
  223. return nil, err
  224. }
  225. image, err = builder.runtime.repositories.LookupImage(arguments)
  226. if err != nil {
  227. return nil, err
  228. }
  229. } else {
  230. return nil, err
  231. }
  232. }
  233. break
  234. case "mainainer":
  235. fmt.Fprintf(stdout, "MAINTAINER %s\n", arguments)
  236. maintainer = arguments
  237. break
  238. case "run":
  239. fmt.Fprintf(stdout, "RUN %s\n", arguments)
  240. if image == nil {
  241. return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
  242. }
  243. config, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, nil, builder.runtime.capabilities)
  244. if err != nil {
  245. return nil, err
  246. }
  247. if cache, err := builder.getCachedImage(image, config); err != nil {
  248. return nil, err
  249. } else if cache != nil {
  250. image = cache
  251. fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
  252. break
  253. }
  254. // Create the container and start it
  255. c, err := builder.Create(config)
  256. if err != nil {
  257. return nil, err
  258. }
  259. if err := c.Start(); err != nil {
  260. return nil, err
  261. }
  262. tmpContainers[c.Id] = struct{}{}
  263. // Wait for it to finish
  264. if result := c.Wait(); result != 0 {
  265. return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
  266. }
  267. // Commit the container
  268. base, err = builder.Commit(c, "", "", "", maintainer, nil)
  269. if err != nil {
  270. return nil, err
  271. }
  272. tmpImages[base.Id] = struct{}{}
  273. fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
  274. // use the base as the new image
  275. image = base
  276. break
  277. case "expose":
  278. ports := strings.Split(arguments, " ")
  279. fmt.Fprintf(stdout, "EXPOSE %v\n", ports)
  280. if image == nil {
  281. return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
  282. }
  283. // Create the container and start it
  284. c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
  285. if err != nil {
  286. return nil, err
  287. }
  288. if err := c.Start(); err != nil {
  289. return nil, err
  290. }
  291. tmpContainers[c.Id] = struct{}{}
  292. // Commit the container
  293. base, err = builder.Commit(c, "", "", "", maintainer, &Config{PortSpecs: ports})
  294. if err != nil {
  295. return nil, err
  296. }
  297. tmpImages[base.Id] = struct{}{}
  298. fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
  299. image = base
  300. break
  301. case "insert":
  302. if image == nil {
  303. return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
  304. }
  305. tmp = strings.SplitN(arguments, " ", 2)
  306. if len(tmp) != 2 {
  307. return nil, fmt.Errorf("Invalid INSERT format")
  308. }
  309. sourceUrl := strings.Trim(tmp[0], " ")
  310. destPath := strings.Trim(tmp[1], " ")
  311. fmt.Fprintf(stdout, "COPY %s to %s in %s\n", sourceUrl, destPath, base.ShortId())
  312. file, err := Download(sourceUrl, stdout)
  313. if err != nil {
  314. return nil, err
  315. }
  316. defer file.Body.Close()
  317. config, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, nil, builder.runtime.capabilities)
  318. if err != nil {
  319. return nil, err
  320. }
  321. c, err := builder.Create(config)
  322. if err != nil {
  323. return nil, err
  324. }
  325. if err := c.Start(); err != nil {
  326. return nil, err
  327. }
  328. // Wait for echo to finish
  329. if result := c.Wait(); result != 0 {
  330. return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
  331. }
  332. if err := c.Inject(file.Body, destPath); err != nil {
  333. return nil, err
  334. }
  335. base, err = builder.Commit(c, "", "", "", maintainer, nil)
  336. if err != nil {
  337. return nil, err
  338. }
  339. fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
  340. image = base
  341. break
  342. default:
  343. fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
  344. }
  345. }
  346. if image != nil {
  347. // The build is successful, keep the temporary containers and images
  348. for i := range tmpImages {
  349. delete(tmpImages, i)
  350. }
  351. for i := range tmpContainers {
  352. delete(tmpContainers, i)
  353. }
  354. fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
  355. return image, nil
  356. }
  357. return nil, fmt.Errorf("An error occured during the build\n")
  358. }