builder.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. Output io.Writer
  63. }
  64. // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
  65. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
  66. // will be read from the Context passed to Build().
  67. func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
  68. if config == nil {
  69. config = new(types.ImageBuildOptions)
  70. }
  71. if config.BuildArgs == nil {
  72. config.BuildArgs = make(map[string]string)
  73. }
  74. b = &Builder{
  75. options: config,
  76. Stdout: os.Stdout,
  77. Stderr: os.Stderr,
  78. docker: backend,
  79. context: context,
  80. runConfig: new(container.Config),
  81. tmpContainers: map[string]struct{}{},
  82. cancelled: make(chan struct{}),
  83. id: stringid.GenerateNonCryptoID(),
  84. allowedBuildArgs: make(map[string]bool),
  85. }
  86. if dockerfile != nil {
  87. b.dockerfile, err = parser.Parse(dockerfile)
  88. if err != nil {
  89. return nil, err
  90. }
  91. }
  92. return b, nil
  93. }
  94. // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
  95. // to Docker.
  96. //
  97. // This will (barring errors):
  98. //
  99. // * read the dockerfile from context
  100. // * parse the dockerfile if not already parsed
  101. // * walk the AST and execute it by dispatching to handlers. If Remove
  102. // or ForceRemove is set, additional cleanup around containers happens after
  103. // processing.
  104. // * Print a happy message and return the image ID.
  105. // * NOT tag the image, that is responsibility of the caller.
  106. //
  107. func (b *Builder) Build() (string, error) {
  108. // If Dockerfile was not parsed yet, extract it from the Context
  109. if b.dockerfile == nil {
  110. if err := b.readDockerfile(); err != nil {
  111. return "", err
  112. }
  113. }
  114. var shortImgID string
  115. for i, n := range b.dockerfile.Children {
  116. select {
  117. case <-b.cancelled:
  118. logrus.Debug("Builder: build cancelled!")
  119. fmt.Fprintf(b.Stdout, "Build cancelled")
  120. return "", fmt.Errorf("Build cancelled")
  121. default:
  122. // Not cancelled yet, keep going...
  123. }
  124. if err := b.dispatch(i, n); err != nil {
  125. if b.options.ForceRemove {
  126. b.clearTmp()
  127. }
  128. return "", err
  129. }
  130. shortImgID = stringid.TruncateID(b.image)
  131. fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
  132. if b.options.Remove {
  133. b.clearTmp()
  134. }
  135. }
  136. // check if there are any leftover build-args that were passed but not
  137. // consumed during build. Return an error, if there are any.
  138. leftoverArgs := []string{}
  139. for arg := range b.options.BuildArgs {
  140. if !b.isBuildArgAllowed(arg) {
  141. leftoverArgs = append(leftoverArgs, arg)
  142. }
  143. }
  144. if len(leftoverArgs) > 0 {
  145. return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
  146. }
  147. if b.image == "" {
  148. return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
  149. }
  150. fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
  151. return b.image, nil
  152. }
  153. // Cancel cancels an ongoing Dockerfile build.
  154. func (b *Builder) Cancel() {
  155. b.cancelOnce.Do(func() {
  156. close(b.cancelled)
  157. })
  158. }
  159. // BuildFromConfig will do build directly from parameter 'changes', which comes
  160. // from Dockerfile entries, it will:
  161. // - call parse.Parse() to get AST root from Dockerfile entries
  162. // - do build by calling builder.dispatch() to call all entries' handling routines
  163. // TODO: remove?
  164. func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
  165. ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
  166. if err != nil {
  167. return nil, err
  168. }
  169. // ensure that the commands are valid
  170. for _, n := range ast.Children {
  171. if !validCommitCommands[n.Value] {
  172. return nil, fmt.Errorf("%s is not a valid change command", n.Value)
  173. }
  174. }
  175. b, err := NewBuilder(nil, nil, nil, nil)
  176. if err != nil {
  177. return nil, err
  178. }
  179. b.runConfig = config
  180. b.Stdout = ioutil.Discard
  181. b.Stderr = ioutil.Discard
  182. b.disableCommit = true
  183. for i, n := range ast.Children {
  184. if err := b.dispatch(i, n); err != nil {
  185. return nil, err
  186. }
  187. }
  188. return b.runConfig, nil
  189. }