builder.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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/docker/pkg/ulimit"
  15. "github.com/docker/docker/runconfig"
  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. // Config constitutes the configuration for a Dockerfile builder.
  40. type Config struct {
  41. // only used if Dockerfile has to be extracted from Context
  42. DockerfileName string
  43. Verbose bool
  44. UseCache bool
  45. Remove bool
  46. ForceRemove bool
  47. Pull bool
  48. BuildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
  49. Isolation runconfig.IsolationLevel
  50. // resource constraints
  51. // TODO: factor out to be reused with Run ?
  52. Memory int64
  53. MemorySwap int64
  54. ShmSize *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.Backend interface.
  65. type Builder struct {
  66. *Config
  67. Stdout io.Writer
  68. Stderr io.Writer
  69. docker builder.Backend
  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. }
  87. // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
  88. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
  89. // will be read from the Context passed to Build().
  90. func NewBuilder(config *Config, docker builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
  91. if config == nil {
  92. config = new(Config)
  93. }
  94. if config.BuildArgs == nil {
  95. config.BuildArgs = make(map[string]string)
  96. }
  97. b = &Builder{
  98. Config: config,
  99. Stdout: os.Stdout,
  100. Stderr: os.Stderr,
  101. docker: docker,
  102. context: context,
  103. runConfig: new(runconfig.Config),
  104. tmpContainers: map[string]struct{}{},
  105. cancelled: make(chan struct{}),
  106. id: stringid.GenerateNonCryptoID(),
  107. allowedBuildArgs: make(map[string]bool),
  108. }
  109. if dockerfile != nil {
  110. b.dockerfile, err = parser.Parse(dockerfile)
  111. if err != nil {
  112. return nil, err
  113. }
  114. }
  115. return b, nil
  116. }
  117. // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
  118. // to Docker.
  119. //
  120. // This will (barring errors):
  121. //
  122. // * read the dockerfile from context
  123. // * parse the dockerfile if not already parsed
  124. // * walk the AST and execute it by dispatching to handlers. If Remove
  125. // or ForceRemove is set, additional cleanup around containers happens after
  126. // processing.
  127. // * Print a happy message and return the image ID.
  128. // * NOT tag the image, that is responsibility of the caller.
  129. //
  130. func (b *Builder) Build() (string, error) {
  131. // If Dockerfile was not parsed yet, extract it from the Context
  132. if b.dockerfile == nil {
  133. if err := b.readDockerfile(); err != nil {
  134. return "", err
  135. }
  136. }
  137. var shortImgID string
  138. for i, n := range b.dockerfile.Children {
  139. select {
  140. case <-b.cancelled:
  141. logrus.Debug("Builder: build cancelled!")
  142. fmt.Fprintf(b.Stdout, "Build cancelled")
  143. return "", fmt.Errorf("Build cancelled")
  144. default:
  145. // Not cancelled yet, keep going...
  146. }
  147. if err := b.dispatch(i, n); err != nil {
  148. if b.ForceRemove {
  149. b.clearTmp()
  150. }
  151. return "", err
  152. }
  153. shortImgID = stringid.TruncateID(b.image)
  154. fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
  155. if b.Remove {
  156. b.clearTmp()
  157. }
  158. }
  159. // check if there are any leftover build-args that were passed but not
  160. // consumed during build. Return an error, if there are any.
  161. leftoverArgs := []string{}
  162. for arg := range b.BuildArgs {
  163. if !b.isBuildArgAllowed(arg) {
  164. leftoverArgs = append(leftoverArgs, arg)
  165. }
  166. }
  167. if len(leftoverArgs) > 0 {
  168. return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
  169. }
  170. if b.image == "" {
  171. return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
  172. }
  173. fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
  174. return b.image, nil
  175. }
  176. // Cancel cancels an ongoing Dockerfile build.
  177. func (b *Builder) Cancel() {
  178. b.cancelOnce.Do(func() {
  179. close(b.cancelled)
  180. })
  181. }
  182. // BuildFromConfig will do build directly from parameter 'changes', which comes
  183. // from Dockerfile entries, it will:
  184. // - call parse.Parse() to get AST root from Dockerfile entries
  185. // - do build by calling builder.dispatch() to call all entries' handling routines
  186. // TODO: remove?
  187. func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) {
  188. ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
  189. if err != nil {
  190. return nil, err
  191. }
  192. // ensure that the commands are valid
  193. for _, n := range ast.Children {
  194. if !validCommitCommands[n.Value] {
  195. return nil, fmt.Errorf("%s is not a valid change command", n.Value)
  196. }
  197. }
  198. b, err := NewBuilder(nil, nil, nil, nil)
  199. if err != nil {
  200. return nil, err
  201. }
  202. b.runConfig = config
  203. b.Stdout = ioutil.Discard
  204. b.Stderr = ioutil.Discard
  205. b.disableCommit = true
  206. for i, n := range ast.Children {
  207. if err := b.dispatch(i, n); err != nil {
  208. return nil, err
  209. }
  210. }
  211. return b.runConfig, nil
  212. }