builder.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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/pkg/stringid"
  14. "github.com/docker/engine-api/types"
  15. "github.com/docker/engine-api/types/container"
  16. )
  17. var validCommitCommands = map[string]bool{
  18. "cmd": true,
  19. "entrypoint": true,
  20. "env": true,
  21. "expose": true,
  22. "label": true,
  23. "onbuild": true,
  24. "user": true,
  25. "volume": true,
  26. "workdir": true,
  27. }
  28. // BuiltinAllowedBuildArgs is list of built-in allowed build args
  29. var BuiltinAllowedBuildArgs = map[string]bool{
  30. "HTTP_PROXY": true,
  31. "http_proxy": true,
  32. "HTTPS_PROXY": true,
  33. "https_proxy": true,
  34. "FTP_PROXY": true,
  35. "ftp_proxy": true,
  36. "NO_PROXY": true,
  37. "no_proxy": true,
  38. }
  39. // Builder is a Dockerfile builder
  40. // It implements the builder.Backend interface.
  41. type Builder struct {
  42. options *types.ImageBuildOptions
  43. Stdout io.Writer
  44. Stderr io.Writer
  45. docker builder.Backend
  46. context builder.Context
  47. dockerfile *parser.Node
  48. runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
  49. flags *BFlags
  50. tmpContainers map[string]struct{}
  51. image string // imageID
  52. noBaseImage bool
  53. maintainer string
  54. cmdSet bool
  55. disableCommit bool
  56. cacheBusted bool
  57. cancelled chan struct{}
  58. cancelOnce sync.Once
  59. allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
  60. // TODO: remove once docker.Commit can receive a tag
  61. id string
  62. }
  63. // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
  64. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
  65. // will be read from the Context passed to Build().
  66. func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
  67. if config == nil {
  68. config = new(types.ImageBuildOptions)
  69. }
  70. if config.BuildArgs == nil {
  71. config.BuildArgs = make(map[string]string)
  72. }
  73. b = &Builder{
  74. options: config,
  75. Stdout: os.Stdout,
  76. Stderr: os.Stderr,
  77. docker: backend,
  78. context: context,
  79. runConfig: new(container.Config),
  80. tmpContainers: map[string]struct{}{},
  81. cancelled: make(chan struct{}),
  82. id: stringid.GenerateNonCryptoID(),
  83. allowedBuildArgs: make(map[string]bool),
  84. }
  85. if dockerfile != nil {
  86. b.dockerfile, err = parser.Parse(dockerfile)
  87. if err != nil {
  88. return nil, err
  89. }
  90. }
  91. return b, nil
  92. }
  93. // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
  94. // to Docker.
  95. //
  96. // This will (barring errors):
  97. //
  98. // * read the dockerfile from context
  99. // * parse the dockerfile if not already parsed
  100. // * walk the AST and execute it by dispatching to handlers. If Remove
  101. // or ForceRemove is set, additional cleanup around containers happens after
  102. // processing.
  103. // * Print a happy message and return the image ID.
  104. // * NOT tag the image, that is responsibility of the caller.
  105. //
  106. func (b *Builder) Build() (string, error) {
  107. // If Dockerfile was not parsed yet, extract it from the Context
  108. if b.dockerfile == nil {
  109. if err := b.readDockerfile(); err != nil {
  110. return "", err
  111. }
  112. }
  113. var shortImgID string
  114. for i, n := range b.dockerfile.Children {
  115. select {
  116. case <-b.cancelled:
  117. logrus.Debug("Builder: build cancelled!")
  118. fmt.Fprintf(b.Stdout, "Build cancelled")
  119. return "", fmt.Errorf("Build cancelled")
  120. default:
  121. // Not cancelled yet, keep going...
  122. }
  123. if err := b.dispatch(i, n); err != nil {
  124. if b.options.ForceRemove {
  125. b.clearTmp()
  126. }
  127. return "", err
  128. }
  129. shortImgID = stringid.TruncateID(b.image)
  130. fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
  131. if b.options.Remove {
  132. b.clearTmp()
  133. }
  134. }
  135. // check if there are any leftover build-args that were passed but not
  136. // consumed during build. Return an error, if there are any.
  137. leftoverArgs := []string{}
  138. for arg := range b.options.BuildArgs {
  139. if !b.isBuildArgAllowed(arg) {
  140. leftoverArgs = append(leftoverArgs, arg)
  141. }
  142. }
  143. if len(leftoverArgs) > 0 {
  144. return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
  145. }
  146. if b.image == "" {
  147. return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
  148. }
  149. fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
  150. return b.image, nil
  151. }
  152. // Cancel cancels an ongoing Dockerfile build.
  153. func (b *Builder) Cancel() {
  154. b.cancelOnce.Do(func() {
  155. close(b.cancelled)
  156. })
  157. }
  158. // BuildFromConfig will do build directly from parameter 'changes',
  159. // which is treated like it is coming from a Dockerfile. It will:
  160. // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
  161. // - Do build by calling builder.dispatch() to call all entries' handling routines
  162. //
  163. // BuildFromConfig is used by the /commit endpoint, with the changes
  164. // coming from the query parameter of the same name.
  165. //
  166. // TODO: Remove?
  167. func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
  168. ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
  169. if err != nil {
  170. return nil, err
  171. }
  172. // ensure that the commands are valid
  173. for _, n := range ast.Children {
  174. if !validCommitCommands[n.Value] {
  175. return nil, fmt.Errorf("%s is not a valid change command", n.Value)
  176. }
  177. }
  178. b, err := NewBuilder(nil, nil, nil, nil)
  179. if err != nil {
  180. return nil, err
  181. }
  182. b.runConfig = config
  183. b.Stdout = ioutil.Discard
  184. b.Stderr = ioutil.Discard
  185. b.disableCommit = true
  186. for i, n := range ast.Children {
  187. if err := b.dispatch(i, n); err != nil {
  188. return nil, err
  189. }
  190. }
  191. return b.runConfig, nil
  192. }