builder.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package dockerfile
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "strings"
  9. "sync"
  10. "github.com/Sirupsen/logrus"
  11. "github.com/docker/docker/builder"
  12. "github.com/docker/docker/builder/dockerfile/parser"
  13. "github.com/docker/docker/daemon"
  14. "github.com/docker/docker/pkg/stringid"
  15. "github.com/docker/docker/pkg/ulimit"
  16. "github.com/docker/docker/runconfig"
  17. )
  18. var validCommitCommands = map[string]bool{
  19. "cmd": true,
  20. "entrypoint": true,
  21. "env": true,
  22. "expose": true,
  23. "label": true,
  24. "onbuild": true,
  25. "user": true,
  26. "volume": true,
  27. "workdir": true,
  28. }
  29. // BuiltinAllowedBuildArgs is list of built-in allowed build args
  30. var BuiltinAllowedBuildArgs = map[string]bool{
  31. "HTTP_PROXY": true,
  32. "http_proxy": true,
  33. "HTTPS_PROXY": true,
  34. "https_proxy": true,
  35. "FTP_PROXY": true,
  36. "ftp_proxy": true,
  37. "NO_PROXY": true,
  38. "no_proxy": true,
  39. }
  40. // Config constitutes the configuration for a Dockerfile builder.
  41. type Config struct {
  42. // only used if Dockerfile has to be extracted from Context
  43. DockerfileName string
  44. Verbose bool
  45. UseCache bool
  46. Remove bool
  47. ForceRemove bool
  48. Pull bool
  49. BuildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
  50. Isolation runconfig.IsolationLevel
  51. // resource constraints
  52. // TODO: factor out to be reused with Run ?
  53. Memory int64
  54. MemorySwap int64
  55. CPUShares int64
  56. CPUPeriod int64
  57. CPUQuota int64
  58. CPUSetCpus string
  59. CPUSetMems string
  60. CgroupParent string
  61. Ulimits []*ulimit.Ulimit
  62. }
  63. // Builder is a Dockerfile builder
  64. // It implements the builder.Builder interface.
  65. type Builder struct {
  66. *Config
  67. Stdout io.Writer
  68. Stderr io.Writer
  69. docker builder.Docker
  70. context builder.Context
  71. dockerfile *parser.Node
  72. runConfig *runconfig.Config // runconfig for cmd, run, entrypoint etc.
  73. flags *BFlags
  74. tmpContainers map[string]struct{}
  75. image string // imageID
  76. noBaseImage bool
  77. maintainer string
  78. cmdSet bool
  79. disableCommit bool
  80. cacheBusted bool
  81. cancelled chan struct{}
  82. cancelOnce sync.Once
  83. allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
  84. // TODO: remove once docker.Commit can receive a tag
  85. id string
  86. activeImages []string
  87. }
  88. // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
  89. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
  90. // will be read from the Context passed to Build().
  91. func NewBuilder(config *Config, docker builder.Docker, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
  92. if config == nil {
  93. config = new(Config)
  94. }
  95. if config.BuildArgs == nil {
  96. config.BuildArgs = make(map[string]string)
  97. }
  98. b = &Builder{
  99. Config: config,
  100. Stdout: os.Stdout,
  101. Stderr: os.Stderr,
  102. docker: docker,
  103. context: context,
  104. runConfig: new(runconfig.Config),
  105. tmpContainers: map[string]struct{}{},
  106. cancelled: make(chan struct{}),
  107. id: stringid.GenerateNonCryptoID(),
  108. allowedBuildArgs: make(map[string]bool),
  109. }
  110. if dockerfile != nil {
  111. b.dockerfile, err = parser.Parse(dockerfile)
  112. if err != nil {
  113. return nil, err
  114. }
  115. }
  116. return b, nil
  117. }
  118. // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
  119. // to Docker.
  120. //
  121. // This will (barring errors):
  122. //
  123. // * read the dockerfile from context
  124. // * parse the dockerfile if not already parsed
  125. // * walk the AST and execute it by dispatching to handlers. If Remove
  126. // or ForceRemove is set, additional cleanup around containers happens after
  127. // processing.
  128. // * Print a happy message and return the image ID.
  129. // * NOT tag the image, that is responsibility of the caller.
  130. //
  131. func (b *Builder) Build() (string, error) {
  132. // TODO: remove once b.docker.Commit can take a tag parameter.
  133. defer func() {
  134. b.docker.Release(b.id, b.activeImages)
  135. }()
  136. // If Dockerfile was not parsed yet, extract it from the Context
  137. if b.dockerfile == nil {
  138. if err := b.readDockerfile(); err != nil {
  139. return "", err
  140. }
  141. }
  142. var shortImgID string
  143. for i, n := range b.dockerfile.Children {
  144. select {
  145. case <-b.cancelled:
  146. logrus.Debug("Builder: build cancelled!")
  147. fmt.Fprintf(b.Stdout, "Build cancelled")
  148. return "", fmt.Errorf("Build cancelled")
  149. default:
  150. // Not cancelled yet, keep going...
  151. }
  152. if err := b.dispatch(i, n); err != nil {
  153. if b.ForceRemove {
  154. b.clearTmp()
  155. }
  156. return "", err
  157. }
  158. shortImgID = stringid.TruncateID(b.image)
  159. fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
  160. if b.Remove {
  161. b.clearTmp()
  162. }
  163. }
  164. // check if there are any leftover build-args that were passed but not
  165. // consumed during build. Return an error, if there are any.
  166. leftoverArgs := []string{}
  167. for arg := range b.BuildArgs {
  168. if !b.isBuildArgAllowed(arg) {
  169. leftoverArgs = append(leftoverArgs, arg)
  170. }
  171. }
  172. if len(leftoverArgs) > 0 {
  173. return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
  174. }
  175. if b.image == "" {
  176. return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
  177. }
  178. fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
  179. return b.image, nil
  180. }
  181. // Cancel cancels an ongoing Dockerfile build.
  182. func (b *Builder) Cancel() {
  183. b.cancelOnce.Do(func() {
  184. close(b.cancelled)
  185. })
  186. }
  187. // CommitConfig contains build configs for commit operation
  188. type CommitConfig struct {
  189. Pause bool
  190. Repo string
  191. Tag string
  192. Author string
  193. Comment string
  194. Changes []string
  195. Config *runconfig.Config
  196. }
  197. // BuildFromConfig will do build directly from parameter 'changes', which comes
  198. // from Dockerfile entries, it will:
  199. // - call parse.Parse() to get AST root from Dockerfile entries
  200. // - do build by calling builder.dispatch() to call all entries' handling routines
  201. // TODO: remove?
  202. func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) {
  203. ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
  204. if err != nil {
  205. return nil, err
  206. }
  207. // ensure that the commands are valid
  208. for _, n := range ast.Children {
  209. if !validCommitCommands[n.Value] {
  210. return nil, fmt.Errorf("%s is not a valid change command", n.Value)
  211. }
  212. }
  213. b, err := NewBuilder(nil, nil, nil, nil)
  214. if err != nil {
  215. return nil, err
  216. }
  217. b.runConfig = config
  218. b.Stdout = ioutil.Discard
  219. b.Stderr = ioutil.Discard
  220. b.disableCommit = true
  221. for i, n := range ast.Children {
  222. if err := b.dispatch(i, n); err != nil {
  223. return nil, err
  224. }
  225. }
  226. return b.runConfig, nil
  227. }
  228. // Commit will create a new image from a container's changes
  229. // TODO: remove daemon, make Commit a method on *Builder ?
  230. func Commit(containerName string, d *daemon.Daemon, c *CommitConfig) (string, error) {
  231. if c.Config == nil {
  232. c.Config = &runconfig.Config{}
  233. }
  234. newConfig, err := BuildFromConfig(c.Config, c.Changes)
  235. if err != nil {
  236. return "", err
  237. }
  238. commitCfg := &daemon.ContainerCommitConfig{
  239. Pause: c.Pause,
  240. Repo: c.Repo,
  241. Tag: c.Tag,
  242. Author: c.Author,
  243. Comment: c.Comment,
  244. Config: newConfig,
  245. MergeConfigs: true,
  246. }
  247. img, err := d.Commit(containerName, commitCfg)
  248. if err != nil {
  249. return "", err
  250. }
  251. return img.ID, nil
  252. }