builder.go 12 KB

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