builder.go 7.3 KB

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