buildfile.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. package server
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/url"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "reflect"
  15. "regexp"
  16. "sort"
  17. "strings"
  18. "github.com/dotcloud/docker/archive"
  19. "github.com/dotcloud/docker/daemon"
  20. "github.com/dotcloud/docker/nat"
  21. "github.com/dotcloud/docker/pkg/symlink"
  22. "github.com/dotcloud/docker/registry"
  23. "github.com/dotcloud/docker/runconfig"
  24. "github.com/dotcloud/docker/utils"
  25. )
  26. var (
  27. ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
  28. )
  29. type BuildFile interface {
  30. Build(io.Reader) (string, error)
  31. CmdFrom(string) error
  32. CmdRun(string) error
  33. }
  34. type buildFile struct {
  35. daemon *daemon.Daemon
  36. srv *Server
  37. image string
  38. maintainer string
  39. config *runconfig.Config
  40. contextPath string
  41. context *utils.TarSum
  42. verbose bool
  43. utilizeCache bool
  44. rm bool
  45. authConfig *registry.AuthConfig
  46. configFile *registry.ConfigFile
  47. tmpContainers map[string]struct{}
  48. tmpImages map[string]struct{}
  49. outStream io.Writer
  50. errStream io.Writer
  51. // Deprecated, original writer used for ImagePull. To be removed.
  52. outOld io.Writer
  53. sf *utils.StreamFormatter
  54. }
  55. func (b *buildFile) clearTmp(containers map[string]struct{}) {
  56. for c := range containers {
  57. tmp := b.daemon.Get(c)
  58. if err := b.daemon.Destroy(tmp); err != nil {
  59. fmt.Fprintf(b.outStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
  60. } else {
  61. delete(containers, c)
  62. fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c))
  63. }
  64. }
  65. }
  66. func (b *buildFile) CmdFrom(name string) error {
  67. image, err := b.daemon.Repositories().LookupImage(name)
  68. if err != nil {
  69. if b.daemon.Graph().IsNotExist(err) {
  70. remote, tag := utils.ParseRepositoryTag(name)
  71. pullRegistryAuth := b.authConfig
  72. if len(b.configFile.Configs) > 0 {
  73. // The request came with a full auth config file, we prefer to use that
  74. endpoint, _, err := registry.ResolveRepositoryName(remote)
  75. if err != nil {
  76. return err
  77. }
  78. resolvedAuth := b.configFile.ResolveAuthConfig(endpoint)
  79. pullRegistryAuth = &resolvedAuth
  80. }
  81. job := b.srv.Eng.Job("pull", remote, tag)
  82. job.SetenvBool("json", b.sf.Json())
  83. job.SetenvBool("parallel", true)
  84. job.SetenvJson("authConfig", pullRegistryAuth)
  85. job.Stdout.Add(b.outOld)
  86. if err := job.Run(); err != nil {
  87. return err
  88. }
  89. image, err = b.daemon.Repositories().LookupImage(name)
  90. if err != nil {
  91. return err
  92. }
  93. } else {
  94. return err
  95. }
  96. }
  97. b.image = image.ID
  98. b.config = &runconfig.Config{}
  99. if image.Config != nil {
  100. b.config = image.Config
  101. }
  102. if b.config.Env == nil || len(b.config.Env) == 0 {
  103. b.config.Env = append(b.config.Env, "HOME=/", "PATH="+daemon.DefaultPathEnv)
  104. }
  105. // Process ONBUILD triggers if they exist
  106. if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
  107. fmt.Fprintf(b.errStream, "# Executing %d build triggers\n", nTriggers)
  108. }
  109. for n, step := range b.config.OnBuild {
  110. splitStep := strings.Split(step, " ")
  111. stepInstruction := strings.ToUpper(strings.Trim(splitStep[0], " "))
  112. switch stepInstruction {
  113. case "ONBUILD":
  114. return fmt.Errorf("Source image contains forbidden chained `ONBUILD ONBUILD` trigger: %s", step)
  115. case "MAINTAINER", "FROM":
  116. return fmt.Errorf("Source image contains forbidden %s trigger: %s", stepInstruction, step)
  117. }
  118. if err := b.BuildStep(fmt.Sprintf("onbuild-%d", n), step); err != nil {
  119. return err
  120. }
  121. }
  122. b.config.OnBuild = []string{}
  123. return nil
  124. }
  125. // The ONBUILD command declares a build instruction to be executed in any future build
  126. // using the current image as a base.
  127. func (b *buildFile) CmdOnbuild(trigger string) error {
  128. splitTrigger := strings.Split(trigger, " ")
  129. triggerInstruction := strings.ToUpper(strings.Trim(splitTrigger[0], " "))
  130. switch triggerInstruction {
  131. case "ONBUILD":
  132. return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
  133. case "MAINTAINER", "FROM":
  134. return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
  135. }
  136. b.config.OnBuild = append(b.config.OnBuild, trigger)
  137. return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
  138. }
  139. func (b *buildFile) CmdMaintainer(name string) error {
  140. b.maintainer = name
  141. return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
  142. }
  143. // probeCache checks to see if image-caching is enabled (`b.utilizeCache`)
  144. // and if so attempts to look up the current `b.image` and `b.config` pair
  145. // in the current server `b.srv`. If an image is found, probeCache returns
  146. // `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
  147. // is any error, it returns `(false, err)`.
  148. func (b *buildFile) probeCache() (bool, error) {
  149. if b.utilizeCache {
  150. if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
  151. return false, err
  152. } else if cache != nil {
  153. fmt.Fprintf(b.outStream, " ---> Using cache\n")
  154. utils.Debugf("[BUILDER] Use cached version")
  155. b.image = cache.ID
  156. return true, nil
  157. } else {
  158. utils.Debugf("[BUILDER] Cache miss")
  159. }
  160. }
  161. return false, nil
  162. }
  163. func (b *buildFile) CmdRun(args string) error {
  164. if b.image == "" {
  165. return fmt.Errorf("Please provide a source image with `from` prior to run")
  166. }
  167. config, _, _, err := runconfig.Parse(append([]string{b.image}, b.buildCmdFromJson(args)...), nil)
  168. if err != nil {
  169. return err
  170. }
  171. cmd := b.config.Cmd
  172. b.config.Cmd = nil
  173. runconfig.Merge(b.config, config)
  174. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  175. utils.Debugf("Command to be executed: %v", b.config.Cmd)
  176. hit, err := b.probeCache()
  177. if err != nil {
  178. return err
  179. }
  180. if hit {
  181. return nil
  182. }
  183. c, err := b.create()
  184. if err != nil {
  185. return err
  186. }
  187. // Ensure that we keep the container mounted until the commit
  188. // to avoid unmounting and then mounting directly again
  189. c.Mount()
  190. defer c.Unmount()
  191. err = b.run(c)
  192. if err != nil {
  193. return err
  194. }
  195. if err := b.commit(c.ID, cmd, "run"); err != nil {
  196. return err
  197. }
  198. return nil
  199. }
  200. func (b *buildFile) FindEnvKey(key string) int {
  201. for k, envVar := range b.config.Env {
  202. envParts := strings.SplitN(envVar, "=", 2)
  203. if key == envParts[0] {
  204. return k
  205. }
  206. }
  207. return -1
  208. }
  209. func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
  210. exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
  211. if err != nil {
  212. return value, err
  213. }
  214. matches := exp.FindAllString(value, -1)
  215. for _, match := range matches {
  216. match = match[strings.Index(match, "$"):]
  217. matchKey := strings.Trim(match, "${}")
  218. for _, envVar := range b.config.Env {
  219. envParts := strings.SplitN(envVar, "=", 2)
  220. envKey := envParts[0]
  221. envValue := envParts[1]
  222. if envKey == matchKey {
  223. value = strings.Replace(value, match, envValue, -1)
  224. break
  225. }
  226. }
  227. }
  228. return value, nil
  229. }
  230. func (b *buildFile) CmdEnv(args string) error {
  231. tmp := strings.SplitN(args, " ", 2)
  232. if len(tmp) != 2 {
  233. return fmt.Errorf("Invalid ENV format")
  234. }
  235. key := strings.Trim(tmp[0], " \t")
  236. value := strings.Trim(tmp[1], " \t")
  237. envKey := b.FindEnvKey(key)
  238. replacedValue, err := b.ReplaceEnvMatches(value)
  239. if err != nil {
  240. return err
  241. }
  242. replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
  243. if envKey >= 0 {
  244. b.config.Env[envKey] = replacedVar
  245. } else {
  246. b.config.Env = append(b.config.Env, replacedVar)
  247. }
  248. return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
  249. }
  250. func (b *buildFile) buildCmdFromJson(args string) []string {
  251. var cmd []string
  252. if err := json.Unmarshal([]byte(args), &cmd); err != nil {
  253. utils.Debugf("Error unmarshalling: %s, setting to /bin/sh -c", err)
  254. cmd = []string{"/bin/sh", "-c", args}
  255. }
  256. return cmd
  257. }
  258. func (b *buildFile) CmdCmd(args string) error {
  259. cmd := b.buildCmdFromJson(args)
  260. b.config.Cmd = cmd
  261. if err := b.commit("", b.config.Cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
  262. return err
  263. }
  264. return nil
  265. }
  266. func (b *buildFile) CmdEntrypoint(args string) error {
  267. entrypoint := b.buildCmdFromJson(args)
  268. b.config.Entrypoint = entrypoint
  269. if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %v", entrypoint)); err != nil {
  270. return err
  271. }
  272. return nil
  273. }
  274. func (b *buildFile) CmdExpose(args string) error {
  275. portsTab := strings.Split(args, " ")
  276. if b.config.ExposedPorts == nil {
  277. b.config.ExposedPorts = make(nat.PortSet)
  278. }
  279. ports, _, err := nat.ParsePortSpecs(append(portsTab, b.config.PortSpecs...))
  280. if err != nil {
  281. return err
  282. }
  283. for port := range ports {
  284. if _, exists := b.config.ExposedPorts[port]; !exists {
  285. b.config.ExposedPorts[port] = struct{}{}
  286. }
  287. }
  288. b.config.PortSpecs = nil
  289. return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
  290. }
  291. func (b *buildFile) CmdUser(args string) error {
  292. b.config.User = args
  293. return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
  294. }
  295. func (b *buildFile) CmdInsert(args string) error {
  296. return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
  297. }
  298. func (b *buildFile) CmdCopy(args string) error {
  299. return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
  300. }
  301. func (b *buildFile) CmdWorkdir(workdir string) error {
  302. if workdir[0] == '/' {
  303. b.config.WorkingDir = workdir
  304. } else {
  305. if b.config.WorkingDir == "" {
  306. b.config.WorkingDir = "/"
  307. }
  308. b.config.WorkingDir = filepath.Join(b.config.WorkingDir, workdir)
  309. }
  310. return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
  311. }
  312. func (b *buildFile) CmdVolume(args string) error {
  313. if args == "" {
  314. return fmt.Errorf("Volume cannot be empty")
  315. }
  316. var volume []string
  317. if err := json.Unmarshal([]byte(args), &volume); err != nil {
  318. volume = []string{args}
  319. }
  320. if b.config.Volumes == nil {
  321. b.config.Volumes = map[string]struct{}{}
  322. }
  323. for _, v := range volume {
  324. b.config.Volumes[v] = struct{}{}
  325. }
  326. if err := b.commit("", b.config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
  327. return err
  328. }
  329. return nil
  330. }
  331. func (b *buildFile) checkPathForAddition(orig string) error {
  332. origPath := path.Join(b.contextPath, orig)
  333. if p, err := filepath.EvalSymlinks(origPath); err != nil {
  334. if os.IsNotExist(err) {
  335. return fmt.Errorf("%s: no such file or directory", orig)
  336. }
  337. return err
  338. } else {
  339. origPath = p
  340. }
  341. if !strings.HasPrefix(origPath, b.contextPath) {
  342. return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
  343. }
  344. _, err := os.Stat(origPath)
  345. if err != nil {
  346. if os.IsNotExist(err) {
  347. return fmt.Errorf("%s: no such file or directory", orig)
  348. }
  349. return err
  350. }
  351. return nil
  352. }
  353. func (b *buildFile) addContext(container *daemon.Container, orig, dest string, remote bool) error {
  354. var (
  355. err error
  356. destExists = true
  357. origPath = path.Join(b.contextPath, orig)
  358. destPath = path.Join(container.RootfsPath(), dest)
  359. )
  360. if destPath != container.RootfsPath() {
  361. destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath())
  362. if err != nil {
  363. return err
  364. }
  365. }
  366. // Preserve the trailing '/'
  367. if strings.HasSuffix(dest, "/") {
  368. destPath = destPath + "/"
  369. }
  370. destStat, err := os.Stat(destPath)
  371. if err != nil {
  372. if os.IsNotExist(err) {
  373. destExists = false
  374. } else {
  375. return err
  376. }
  377. }
  378. fi, err := os.Stat(origPath)
  379. if err != nil {
  380. if os.IsNotExist(err) {
  381. return fmt.Errorf("%s: no such file or directory", orig)
  382. }
  383. return err
  384. }
  385. chownR := func(destPath string, uid, gid int) error {
  386. return filepath.Walk(destPath, func(path string, info os.FileInfo, err error) error {
  387. if err := os.Lchown(path, uid, gid); err != nil {
  388. return err
  389. }
  390. return nil
  391. })
  392. }
  393. if fi.IsDir() {
  394. if err := archive.CopyWithTar(origPath, destPath); err != nil {
  395. return err
  396. }
  397. if destExists {
  398. files, err := ioutil.ReadDir(origPath)
  399. if err != nil {
  400. return err
  401. }
  402. for _, file := range files {
  403. if err := chownR(filepath.Join(destPath, file.Name()), 0, 0); err != nil {
  404. return err
  405. }
  406. }
  407. } else {
  408. if err := chownR(destPath, 0, 0); err != nil {
  409. return err
  410. }
  411. }
  412. return nil
  413. }
  414. // First try to unpack the source as an archive
  415. // to support the untar feature we need to clean up the path a little bit
  416. // because tar is very forgiving. First we need to strip off the archive's
  417. // filename from the path but this is only added if it does not end in / .
  418. tarDest := destPath
  419. if strings.HasSuffix(tarDest, "/") {
  420. tarDest = filepath.Dir(destPath)
  421. }
  422. // If we are adding a remote file, do not try to untar it
  423. if !remote {
  424. // try to successfully untar the orig
  425. if err := archive.UntarPath(origPath, tarDest); err == nil {
  426. return nil
  427. }
  428. utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
  429. }
  430. // If that fails, just copy it as a regular file
  431. // but do not use all the magic path handling for the tar path
  432. if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
  433. return err
  434. }
  435. if err := archive.CopyWithTar(origPath, destPath); err != nil {
  436. return err
  437. }
  438. resPath := destPath
  439. if destExists && destStat.IsDir() {
  440. resPath = path.Join(destPath, path.Base(origPath))
  441. }
  442. if err := chownR(resPath, 0, 0); err != nil {
  443. return err
  444. }
  445. return nil
  446. }
  447. func (b *buildFile) CmdAdd(args string) error {
  448. if b.context == nil {
  449. return fmt.Errorf("No context given. Impossible to use ADD")
  450. }
  451. tmp := strings.SplitN(args, " ", 2)
  452. if len(tmp) != 2 {
  453. return fmt.Errorf("Invalid ADD format")
  454. }
  455. orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
  456. if err != nil {
  457. return err
  458. }
  459. dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
  460. if err != nil {
  461. return err
  462. }
  463. cmd := b.config.Cmd
  464. b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
  465. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  466. b.config.Image = b.image
  467. var (
  468. origPath = orig
  469. destPath = dest
  470. remoteHash string
  471. isRemote bool
  472. )
  473. if utils.IsURL(orig) {
  474. // Initiate the download
  475. isRemote = true
  476. resp, err := utils.Download(orig)
  477. if err != nil {
  478. return err
  479. }
  480. // Create a tmp dir
  481. tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
  482. if err != nil {
  483. return err
  484. }
  485. // Create a tmp file within our tmp dir
  486. tmpFileName := path.Join(tmpDirName, "tmp")
  487. tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
  488. if err != nil {
  489. return err
  490. }
  491. defer os.RemoveAll(tmpDirName)
  492. // Download and dump result to tmp file
  493. if _, err := io.Copy(tmpFile, resp.Body); err != nil {
  494. tmpFile.Close()
  495. return err
  496. }
  497. tmpFile.Close()
  498. origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
  499. // Process the checksum
  500. r, err := archive.Tar(tmpFileName, archive.Uncompressed)
  501. if err != nil {
  502. return err
  503. }
  504. tarSum := utils.TarSum{Reader: r, DisableCompression: true}
  505. remoteHash = tarSum.Sum(nil)
  506. r.Close()
  507. // If the destination is a directory, figure out the filename.
  508. if strings.HasSuffix(dest, "/") {
  509. u, err := url.Parse(orig)
  510. if err != nil {
  511. return err
  512. }
  513. path := u.Path
  514. if strings.HasSuffix(path, "/") {
  515. path = path[:len(path)-1]
  516. }
  517. parts := strings.Split(path, "/")
  518. filename := parts[len(parts)-1]
  519. if filename == "" {
  520. return fmt.Errorf("cannot determine filename from url: %s", u)
  521. }
  522. destPath = dest + filename
  523. }
  524. }
  525. if err := b.checkPathForAddition(origPath); err != nil {
  526. return err
  527. }
  528. // Hash path and check the cache
  529. if b.utilizeCache {
  530. var (
  531. hash string
  532. sums = b.context.GetSums()
  533. )
  534. if remoteHash != "" {
  535. hash = remoteHash
  536. } else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil {
  537. return err
  538. } else if fi.IsDir() {
  539. var subfiles []string
  540. for file, sum := range sums {
  541. absFile := path.Join(b.contextPath, file)
  542. absOrigPath := path.Join(b.contextPath, origPath)
  543. if strings.HasPrefix(absFile, absOrigPath) {
  544. subfiles = append(subfiles, sum)
  545. }
  546. }
  547. sort.Strings(subfiles)
  548. hasher := sha256.New()
  549. hasher.Write([]byte(strings.Join(subfiles, ",")))
  550. hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
  551. } else {
  552. if origPath[0] == '/' && len(origPath) > 1 {
  553. origPath = origPath[1:]
  554. }
  555. origPath = strings.TrimPrefix(origPath, "./")
  556. if h, ok := sums[origPath]; ok {
  557. hash = "file:" + h
  558. }
  559. }
  560. b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", hash, dest)}
  561. hit, err := b.probeCache()
  562. if err != nil {
  563. return err
  564. }
  565. // If we do not have a hash, never use the cache
  566. if hit && hash != "" {
  567. return nil
  568. }
  569. }
  570. // Create the container
  571. container, _, err := b.daemon.Create(b.config, "")
  572. if err != nil {
  573. return err
  574. }
  575. b.tmpContainers[container.ID] = struct{}{}
  576. if err := container.Mount(); err != nil {
  577. return err
  578. }
  579. defer container.Unmount()
  580. if err := b.addContext(container, origPath, destPath, isRemote); err != nil {
  581. return err
  582. }
  583. if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
  584. return err
  585. }
  586. return nil
  587. }
  588. func (b *buildFile) create() (*daemon.Container, error) {
  589. if b.image == "" {
  590. return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
  591. }
  592. b.config.Image = b.image
  593. // Create the container
  594. c, _, err := b.daemon.Create(b.config, "")
  595. if err != nil {
  596. return nil, err
  597. }
  598. b.tmpContainers[c.ID] = struct{}{}
  599. fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
  600. // override the entry point that may have been picked up from the base image
  601. c.Path = b.config.Cmd[0]
  602. c.Args = b.config.Cmd[1:]
  603. return c, nil
  604. }
  605. func (b *buildFile) run(c *daemon.Container) error {
  606. var errCh chan error
  607. if b.verbose {
  608. errCh = utils.Go(func() error {
  609. return <-b.daemon.Attach(c, nil, nil, b.outStream, b.errStream)
  610. })
  611. }
  612. //start the container
  613. if err := c.Start(); err != nil {
  614. return err
  615. }
  616. if errCh != nil {
  617. if err := <-errCh; err != nil {
  618. return err
  619. }
  620. }
  621. // Wait for it to finish
  622. if ret := c.Wait(); ret != 0 {
  623. err := &utils.JSONError{
  624. Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret),
  625. Code: ret,
  626. }
  627. return err
  628. }
  629. return nil
  630. }
  631. // Commit the container <id> with the autorun command <autoCmd>
  632. func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
  633. if b.image == "" {
  634. return fmt.Errorf("Please provide a source image with `from` prior to commit")
  635. }
  636. b.config.Image = b.image
  637. if id == "" {
  638. cmd := b.config.Cmd
  639. b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
  640. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  641. hit, err := b.probeCache()
  642. if err != nil {
  643. return err
  644. }
  645. if hit {
  646. return nil
  647. }
  648. container, warnings, err := b.daemon.Create(b.config, "")
  649. if err != nil {
  650. return err
  651. }
  652. for _, warning := range warnings {
  653. fmt.Fprintf(b.outStream, " ---> [Warning] %s\n", warning)
  654. }
  655. b.tmpContainers[container.ID] = struct{}{}
  656. fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
  657. id = container.ID
  658. if err := container.Mount(); err != nil {
  659. return err
  660. }
  661. defer container.Unmount()
  662. }
  663. container := b.daemon.Get(id)
  664. if container == nil {
  665. return fmt.Errorf("An error occured while creating the container")
  666. }
  667. // Note: Actually copy the struct
  668. autoConfig := *b.config
  669. autoConfig.Cmd = autoCmd
  670. // Commit the container
  671. image, err := b.daemon.Commit(container, "", "", "", b.maintainer, &autoConfig)
  672. if err != nil {
  673. return err
  674. }
  675. b.tmpImages[image.ID] = struct{}{}
  676. b.image = image.ID
  677. return nil
  678. }
  679. // Long lines can be split with a backslash
  680. var lineContinuation = regexp.MustCompile(`\s*\\\s*\n`)
  681. func (b *buildFile) Build(context io.Reader) (string, error) {
  682. tmpdirPath, err := ioutil.TempDir("", "docker-build")
  683. if err != nil {
  684. return "", err
  685. }
  686. decompressedStream, err := archive.DecompressStream(context)
  687. if err != nil {
  688. return "", err
  689. }
  690. b.context = &utils.TarSum{Reader: decompressedStream, DisableCompression: true}
  691. if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
  692. return "", err
  693. }
  694. defer os.RemoveAll(tmpdirPath)
  695. b.contextPath = tmpdirPath
  696. filename := path.Join(tmpdirPath, "Dockerfile")
  697. if _, err := os.Stat(filename); os.IsNotExist(err) {
  698. return "", fmt.Errorf("Can't build a directory with no Dockerfile")
  699. }
  700. fileBytes, err := ioutil.ReadFile(filename)
  701. if err != nil {
  702. return "", err
  703. }
  704. if len(fileBytes) == 0 {
  705. return "", ErrDockerfileEmpty
  706. }
  707. var (
  708. dockerfile = lineContinuation.ReplaceAllString(stripComments(fileBytes), "")
  709. stepN = 0
  710. )
  711. for _, line := range strings.Split(dockerfile, "\n") {
  712. line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
  713. if len(line) == 0 {
  714. continue
  715. }
  716. if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
  717. return "", err
  718. } else if b.rm {
  719. b.clearTmp(b.tmpContainers)
  720. }
  721. stepN += 1
  722. }
  723. if b.image != "" {
  724. fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image))
  725. return b.image, nil
  726. }
  727. return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n")
  728. }
  729. // BuildStep parses a single build step from `instruction` and executes it in the current context.
  730. func (b *buildFile) BuildStep(name, expression string) error {
  731. fmt.Fprintf(b.outStream, "Step %s : %s\n", name, expression)
  732. tmp := strings.SplitN(expression, " ", 2)
  733. if len(tmp) != 2 {
  734. return fmt.Errorf("Invalid Dockerfile format")
  735. }
  736. instruction := strings.ToLower(strings.Trim(tmp[0], " "))
  737. arguments := strings.Trim(tmp[1], " ")
  738. method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
  739. if !exists {
  740. fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
  741. return nil
  742. }
  743. ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
  744. if ret != nil {
  745. return ret.(error)
  746. }
  747. fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image))
  748. return nil
  749. }
  750. func stripComments(raw []byte) string {
  751. var (
  752. out []string
  753. lines = strings.Split(string(raw), "\n")
  754. )
  755. for _, l := range lines {
  756. if len(l) == 0 || l[0] == '#' {
  757. continue
  758. }
  759. out = append(out, l)
  760. }
  761. return strings.Join(out, "\n")
  762. }
  763. func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile {
  764. return &buildFile{
  765. daemon: srv.daemon,
  766. srv: srv,
  767. config: &runconfig.Config{},
  768. outStream: outStream,
  769. errStream: errStream,
  770. tmpContainers: make(map[string]struct{}),
  771. tmpImages: make(map[string]struct{}),
  772. verbose: verbose,
  773. utilizeCache: utilizeCache,
  774. rm: rm,
  775. sf: sf,
  776. authConfig: auth,
  777. configFile: authConfigFile,
  778. outOld: outOld,
  779. }
  780. }