builder.go 7.6 KB

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