buildfile.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. package docker
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "github.com/dotcloud/docker/archive"
  9. "github.com/dotcloud/docker/auth"
  10. "github.com/dotcloud/docker/utils"
  11. "io"
  12. "io/ioutil"
  13. "net/url"
  14. "os"
  15. "path"
  16. "path/filepath"
  17. "reflect"
  18. "regexp"
  19. "sort"
  20. "strings"
  21. )
  22. var (
  23. ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
  24. )
  25. type BuildFile interface {
  26. Build(io.Reader) (string, error)
  27. CmdFrom(string) error
  28. CmdRun(string) error
  29. }
  30. type buildFile struct {
  31. runtime *Runtime
  32. srv *Server
  33. image string
  34. maintainer string
  35. config *Config
  36. contextPath string
  37. context *utils.TarSum
  38. verbose bool
  39. utilizeCache bool
  40. rm bool
  41. authConfig *auth.AuthConfig
  42. tmpContainers map[string]struct{}
  43. tmpImages map[string]struct{}
  44. outStream io.Writer
  45. errStream io.Writer
  46. // Deprecated, original writer used for ImagePull. To be removed.
  47. outOld io.Writer
  48. sf *utils.StreamFormatter
  49. }
  50. func (b *buildFile) clearTmp(containers map[string]struct{}) {
  51. for c := range containers {
  52. tmp := b.runtime.Get(c)
  53. b.runtime.Destroy(tmp)
  54. fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c))
  55. }
  56. }
  57. func (b *buildFile) CmdFrom(name string) error {
  58. image, err := b.runtime.repositories.LookupImage(name)
  59. if err != nil {
  60. if b.runtime.graph.IsNotExist(err) {
  61. remote, tag := utils.ParseRepositoryTag(name)
  62. if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, b.authConfig, nil, true); err != nil {
  63. return err
  64. }
  65. image, err = b.runtime.repositories.LookupImage(name)
  66. if err != nil {
  67. return err
  68. }
  69. } else {
  70. return err
  71. }
  72. }
  73. b.image = image.ID
  74. b.config = &Config{}
  75. if image.Config != nil {
  76. b.config = image.Config
  77. }
  78. if b.config.Env == nil || len(b.config.Env) == 0 {
  79. b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
  80. }
  81. return nil
  82. }
  83. func (b *buildFile) CmdMaintainer(name string) error {
  84. b.maintainer = name
  85. return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
  86. }
  87. // probeCache checks to see if image-caching is enabled (`b.utilizeCache`)
  88. // and if so attempts to look up the current `b.image` and `b.config` pair
  89. // in the current server `b.srv`. If an image is found, probeCache returns
  90. // `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
  91. // is any error, it returns `(false, err)`.
  92. func (b *buildFile) probeCache() (bool, error) {
  93. if b.utilizeCache {
  94. if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
  95. return false, err
  96. } else if cache != nil {
  97. fmt.Fprintf(b.outStream, " ---> Using cache\n")
  98. utils.Debugf("[BUILDER] Use cached version")
  99. b.image = cache.ID
  100. return true, nil
  101. } else {
  102. utils.Debugf("[BUILDER] Cache miss")
  103. }
  104. }
  105. return false, nil
  106. }
  107. func (b *buildFile) CmdRun(args string) error {
  108. if b.image == "" {
  109. return fmt.Errorf("Please provide a source image with `from` prior to run")
  110. }
  111. config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
  112. if err != nil {
  113. return err
  114. }
  115. cmd := b.config.Cmd
  116. b.config.Cmd = nil
  117. MergeConfig(b.config, config)
  118. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  119. utils.Debugf("Command to be executed: %v", b.config.Cmd)
  120. hit, err := b.probeCache()
  121. if err != nil {
  122. return err
  123. }
  124. if hit {
  125. return nil
  126. }
  127. cid, err := b.run()
  128. if err != nil {
  129. return err
  130. }
  131. if err := b.commit(cid, cmd, "run"); err != nil {
  132. return err
  133. }
  134. return nil
  135. }
  136. func (b *buildFile) FindEnvKey(key string) int {
  137. for k, envVar := range b.config.Env {
  138. envParts := strings.SplitN(envVar, "=", 2)
  139. if key == envParts[0] {
  140. return k
  141. }
  142. }
  143. return -1
  144. }
  145. func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
  146. exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
  147. if err != nil {
  148. return value, err
  149. }
  150. matches := exp.FindAllString(value, -1)
  151. for _, match := range matches {
  152. match = match[strings.Index(match, "$"):]
  153. matchKey := strings.Trim(match, "${}")
  154. for _, envVar := range b.config.Env {
  155. envParts := strings.SplitN(envVar, "=", 2)
  156. envKey := envParts[0]
  157. envValue := envParts[1]
  158. if envKey == matchKey {
  159. value = strings.Replace(value, match, envValue, -1)
  160. break
  161. }
  162. }
  163. }
  164. return value, nil
  165. }
  166. func (b *buildFile) CmdEnv(args string) error {
  167. tmp := strings.SplitN(args, " ", 2)
  168. if len(tmp) != 2 {
  169. return fmt.Errorf("Invalid ENV format")
  170. }
  171. key := strings.Trim(tmp[0], " \t")
  172. value := strings.Trim(tmp[1], " \t")
  173. envKey := b.FindEnvKey(key)
  174. replacedValue, err := b.ReplaceEnvMatches(value)
  175. if err != nil {
  176. return err
  177. }
  178. replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
  179. if envKey >= 0 {
  180. b.config.Env[envKey] = replacedVar
  181. } else {
  182. b.config.Env = append(b.config.Env, replacedVar)
  183. }
  184. return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
  185. }
  186. func (b *buildFile) buildCmdFromJson(args string) []string {
  187. var cmd []string
  188. if err := json.Unmarshal([]byte(args), &cmd); err != nil {
  189. utils.Debugf("Error unmarshalling: %s, setting to /bin/sh -c", err)
  190. cmd = []string{"/bin/sh", "-c", args}
  191. }
  192. return cmd
  193. }
  194. func (b *buildFile) CmdCmd(args string) error {
  195. cmd := b.buildCmdFromJson(args)
  196. b.config.Cmd = cmd
  197. if err := b.commit("", b.config.Cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
  198. return err
  199. }
  200. return nil
  201. }
  202. func (b *buildFile) CmdEntrypoint(args string) error {
  203. entrypoint := b.buildCmdFromJson(args)
  204. b.config.Entrypoint = entrypoint
  205. if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %v", entrypoint)); err != nil {
  206. return err
  207. }
  208. return nil
  209. }
  210. func (b *buildFile) CmdExpose(args string) error {
  211. ports := strings.Split(args, " ")
  212. b.config.PortSpecs = append(ports, b.config.PortSpecs...)
  213. return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
  214. }
  215. func (b *buildFile) CmdUser(args string) error {
  216. b.config.User = args
  217. return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
  218. }
  219. func (b *buildFile) CmdInsert(args string) error {
  220. return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
  221. }
  222. func (b *buildFile) CmdCopy(args string) error {
  223. return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
  224. }
  225. func (b *buildFile) CmdWorkdir(workdir string) error {
  226. b.config.WorkingDir = workdir
  227. return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
  228. }
  229. func (b *buildFile) CmdVolume(args string) error {
  230. if args == "" {
  231. return fmt.Errorf("Volume cannot be empty")
  232. }
  233. var volume []string
  234. if err := json.Unmarshal([]byte(args), &volume); err != nil {
  235. volume = []string{args}
  236. }
  237. if b.config.Volumes == nil {
  238. b.config.Volumes = map[string]struct{}{}
  239. }
  240. for _, v := range volume {
  241. b.config.Volumes[v] = struct{}{}
  242. }
  243. if err := b.commit("", b.config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
  244. return err
  245. }
  246. return nil
  247. }
  248. func (b *buildFile) checkPathForAddition(orig string) error {
  249. origPath := path.Join(b.contextPath, orig)
  250. if !strings.HasPrefix(origPath, b.contextPath) {
  251. return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
  252. }
  253. _, err := os.Stat(origPath)
  254. if err != nil {
  255. return fmt.Errorf("%s: no such file or directory", orig)
  256. }
  257. return nil
  258. }
  259. func (b *buildFile) addContext(container *Container, orig, dest string) error {
  260. var (
  261. origPath = path.Join(b.contextPath, orig)
  262. destPath = path.Join(container.RootfsPath(), dest)
  263. )
  264. // Preserve the trailing '/'
  265. if strings.HasSuffix(dest, "/") {
  266. destPath = destPath + "/"
  267. }
  268. fi, err := os.Stat(origPath)
  269. if err != nil {
  270. return fmt.Errorf("%s: no such file or directory", orig)
  271. }
  272. if fi.IsDir() {
  273. if err := archive.CopyWithTar(origPath, destPath); err != nil {
  274. return err
  275. }
  276. // First try to unpack the source as an archive
  277. } else if err := archive.UntarPath(origPath, destPath); err != nil {
  278. utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
  279. // If that fails, just copy it as a regular file
  280. if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
  281. return err
  282. }
  283. if err := archive.CopyWithTar(origPath, destPath); err != nil {
  284. return err
  285. }
  286. }
  287. return nil
  288. }
  289. func (b *buildFile) CmdAdd(args string) error {
  290. if b.context == nil {
  291. return fmt.Errorf("No context given. Impossible to use ADD")
  292. }
  293. tmp := strings.SplitN(args, " ", 2)
  294. if len(tmp) != 2 {
  295. return fmt.Errorf("Invalid ADD format")
  296. }
  297. orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
  298. if err != nil {
  299. return err
  300. }
  301. dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
  302. if err != nil {
  303. return err
  304. }
  305. cmd := b.config.Cmd
  306. b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
  307. b.config.Image = b.image
  308. // FIXME: do we really need this?
  309. var (
  310. origPath = orig
  311. destPath = dest
  312. remoteHash string
  313. )
  314. if utils.IsURL(orig) {
  315. resp, err := utils.Download(orig)
  316. if err != nil {
  317. return err
  318. }
  319. tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
  320. if err != nil {
  321. return err
  322. }
  323. tmpFileName := path.Join(tmpDirName, "tmp")
  324. tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
  325. if err != nil {
  326. return err
  327. }
  328. defer os.RemoveAll(tmpDirName)
  329. if _, err = io.Copy(tmpFile, resp.Body); err != nil {
  330. tmpFile.Close()
  331. return err
  332. }
  333. origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
  334. tmpFile.Close()
  335. // Process the checksum
  336. r, err := archive.Tar(tmpFileName, archive.Uncompressed)
  337. if err != nil {
  338. return err
  339. }
  340. tarSum := utils.TarSum{Reader: r, DisableCompression: true}
  341. remoteHash = tarSum.Sum(nil)
  342. // If the destination is a directory, figure out the filename.
  343. if strings.HasSuffix(dest, "/") {
  344. u, err := url.Parse(orig)
  345. if err != nil {
  346. return err
  347. }
  348. path := u.Path
  349. if strings.HasSuffix(path, "/") {
  350. path = path[:len(path)-1]
  351. }
  352. parts := strings.Split(path, "/")
  353. filename := parts[len(parts)-1]
  354. if filename == "" {
  355. return fmt.Errorf("cannot determine filename from url: %s", u)
  356. }
  357. destPath = dest + filename
  358. }
  359. }
  360. if err := b.checkPathForAddition(origPath); err != nil {
  361. return err
  362. }
  363. // Hash path and check the cache
  364. if b.utilizeCache {
  365. var (
  366. hash string
  367. sums = b.context.GetSums()
  368. )
  369. if remoteHash != "" {
  370. hash = remoteHash
  371. } else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil {
  372. return err
  373. } else if fi.IsDir() {
  374. var subfiles []string
  375. for file, sum := range sums {
  376. absFile := path.Join(b.contextPath, file)
  377. absOrigPath := path.Join(b.contextPath, origPath)
  378. if strings.HasPrefix(absFile, absOrigPath) {
  379. subfiles = append(subfiles, sum)
  380. }
  381. }
  382. sort.Strings(subfiles)
  383. hasher := sha256.New()
  384. hasher.Write([]byte(strings.Join(subfiles, ",")))
  385. hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
  386. } else {
  387. if origPath[0] == '/' && len(origPath) > 1 {
  388. origPath = origPath[1:]
  389. }
  390. origPath = strings.TrimPrefix(origPath, "./")
  391. if h, ok := sums[origPath]; ok {
  392. hash = "file:" + h
  393. }
  394. }
  395. b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", hash, dest)}
  396. hit, err := b.probeCache()
  397. if err != nil {
  398. return err
  399. }
  400. // If we do not have a hash, never use the cache
  401. if hit && hash != "" {
  402. return nil
  403. }
  404. }
  405. // Create the container and start it
  406. container, _, err := b.runtime.Create(b.config, "")
  407. if err != nil {
  408. return err
  409. }
  410. b.tmpContainers[container.ID] = struct{}{}
  411. if err := container.EnsureMounted(); err != nil {
  412. return err
  413. }
  414. defer container.Unmount()
  415. if err := b.addContext(container, origPath, destPath); err != nil {
  416. return err
  417. }
  418. if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
  419. return err
  420. }
  421. b.config.Cmd = cmd
  422. return nil
  423. }
  424. type StdoutFormater struct {
  425. io.Writer
  426. *utils.StreamFormatter
  427. }
  428. func (sf *StdoutFormater) Write(buf []byte) (int, error) {
  429. formattedBuf := sf.StreamFormatter.FormatStream(string(buf))
  430. n, err := sf.Writer.Write(formattedBuf)
  431. if n != len(formattedBuf) {
  432. return n, io.ErrShortWrite
  433. }
  434. return len(buf), err
  435. }
  436. type StderrFormater struct {
  437. io.Writer
  438. *utils.StreamFormatter
  439. }
  440. func (sf *StderrFormater) Write(buf []byte) (int, error) {
  441. formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m")
  442. n, err := sf.Writer.Write(formattedBuf)
  443. if n != len(formattedBuf) {
  444. return n, io.ErrShortWrite
  445. }
  446. return len(buf), err
  447. }
  448. func (b *buildFile) run() (string, error) {
  449. if b.image == "" {
  450. return "", fmt.Errorf("Please provide a source image with `from` prior to run")
  451. }
  452. b.config.Image = b.image
  453. // Create the container and start it
  454. c, _, err := b.runtime.Create(b.config, "")
  455. if err != nil {
  456. return "", err
  457. }
  458. b.tmpContainers[c.ID] = struct{}{}
  459. fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
  460. // override the entry point that may have been picked up from the base image
  461. c.Path = b.config.Cmd[0]
  462. c.Args = b.config.Cmd[1:]
  463. var errCh chan error
  464. if b.verbose {
  465. errCh = utils.Go(func() error {
  466. return <-c.Attach(nil, nil, b.outStream, b.errStream)
  467. })
  468. }
  469. //start the container
  470. if err := c.Start(); err != nil {
  471. return "", err
  472. }
  473. if errCh != nil {
  474. if err := <-errCh; err != nil {
  475. return "", err
  476. }
  477. }
  478. // Wait for it to finish
  479. if ret := c.Wait(); ret != 0 {
  480. err := &utils.JSONError{
  481. Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret),
  482. Code: ret,
  483. }
  484. return "", err
  485. }
  486. return c.ID, nil
  487. }
  488. // Commit the container <id> with the autorun command <autoCmd>
  489. func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
  490. if b.image == "" {
  491. return fmt.Errorf("Please provide a source image with `from` prior to commit")
  492. }
  493. b.config.Image = b.image
  494. if id == "" {
  495. cmd := b.config.Cmd
  496. b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
  497. defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
  498. hit, err := b.probeCache()
  499. if err != nil {
  500. return err
  501. }
  502. if hit {
  503. return nil
  504. }
  505. container, warnings, err := b.runtime.Create(b.config, "")
  506. if err != nil {
  507. return err
  508. }
  509. for _, warning := range warnings {
  510. fmt.Fprintf(b.outStream, " ---> [Warning] %s\n", warning)
  511. }
  512. b.tmpContainers[container.ID] = struct{}{}
  513. fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
  514. id = container.ID
  515. if err := container.EnsureMounted(); err != nil {
  516. return err
  517. }
  518. defer container.Unmount()
  519. }
  520. container := b.runtime.Get(id)
  521. if container == nil {
  522. return fmt.Errorf("An error occured while creating the container")
  523. }
  524. // Note: Actually copy the struct
  525. autoConfig := *b.config
  526. autoConfig.Cmd = autoCmd
  527. // Commit the container
  528. image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig)
  529. if err != nil {
  530. return err
  531. }
  532. b.tmpImages[image.ID] = struct{}{}
  533. b.image = image.ID
  534. return nil
  535. }
  536. // Long lines can be split with a backslash
  537. var lineContinuation = regexp.MustCompile(`\s*\\\s*\n`)
  538. func (b *buildFile) Build(context io.Reader) (string, error) {
  539. tmpdirPath, err := ioutil.TempDir("", "docker-build")
  540. if err != nil {
  541. return "", err
  542. }
  543. b.context = &utils.TarSum{Reader: context, DisableCompression: true}
  544. if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
  545. return "", err
  546. }
  547. defer os.RemoveAll(tmpdirPath)
  548. b.contextPath = tmpdirPath
  549. filename := path.Join(tmpdirPath, "Dockerfile")
  550. if _, err := os.Stat(filename); os.IsNotExist(err) {
  551. return "", fmt.Errorf("Can't build a directory with no Dockerfile")
  552. }
  553. fileBytes, err := ioutil.ReadFile(filename)
  554. if err != nil {
  555. return "", err
  556. }
  557. if len(fileBytes) == 0 {
  558. return "", ErrDockerfileEmpty
  559. }
  560. dockerfile := string(fileBytes)
  561. dockerfile = lineContinuation.ReplaceAllString(dockerfile, "")
  562. stepN := 0
  563. for _, line := range strings.Split(dockerfile, "\n") {
  564. line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
  565. // Skip comments and empty line
  566. if len(line) == 0 || line[0] == '#' {
  567. continue
  568. }
  569. tmp := strings.SplitN(line, " ", 2)
  570. if len(tmp) != 2 {
  571. return "", fmt.Errorf("Invalid Dockerfile format")
  572. }
  573. instruction := strings.ToLower(strings.Trim(tmp[0], " "))
  574. arguments := strings.Trim(tmp[1], " ")
  575. method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
  576. if !exists {
  577. fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
  578. continue
  579. }
  580. stepN += 1
  581. fmt.Fprintf(b.outStream, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
  582. ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
  583. if ret != nil {
  584. return "", ret.(error)
  585. }
  586. fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image))
  587. }
  588. if b.image != "" {
  589. fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image))
  590. if b.rm {
  591. b.clearTmp(b.tmpContainers)
  592. }
  593. return b.image, nil
  594. }
  595. return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n")
  596. }
  597. func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig) BuildFile {
  598. return &buildFile{
  599. runtime: srv.runtime,
  600. srv: srv,
  601. config: &Config{},
  602. outStream: outStream,
  603. errStream: errStream,
  604. tmpContainers: make(map[string]struct{}),
  605. tmpImages: make(map[string]struct{}),
  606. verbose: verbose,
  607. utilizeCache: utilizeCache,
  608. rm: rm,
  609. sf: sf,
  610. authConfig: auth,
  611. outOld: outOld,
  612. }
  613. }