builder.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. package dockerfile
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "strings"
  8. "github.com/Sirupsen/logrus"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/backend"
  11. "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/builder"
  13. "github.com/docker/docker/builder/dockerfile/command"
  14. "github.com/docker/docker/builder/dockerfile/parser"
  15. "github.com/docker/docker/builder/remotecontext"
  16. "github.com/docker/docker/pkg/archive"
  17. "github.com/docker/docker/pkg/chrootarchive"
  18. "github.com/docker/docker/pkg/idtools"
  19. "github.com/docker/docker/pkg/streamformatter"
  20. "github.com/docker/docker/pkg/stringid"
  21. "github.com/pkg/errors"
  22. "golang.org/x/net/context"
  23. "golang.org/x/sync/syncmap"
  24. )
  25. var validCommitCommands = map[string]bool{
  26. "cmd": true,
  27. "entrypoint": true,
  28. "healthcheck": true,
  29. "env": true,
  30. "expose": true,
  31. "label": true,
  32. "onbuild": true,
  33. "user": true,
  34. "volume": true,
  35. "workdir": true,
  36. }
  37. // BuildManager is shared across all Builder objects
  38. type BuildManager struct {
  39. archiver *archive.Archiver
  40. backend builder.Backend
  41. pathCache pathCache // TODO: make this persistent
  42. }
  43. // NewBuildManager creates a BuildManager
  44. func NewBuildManager(b builder.Backend, idMappings *idtools.IDMappings) *BuildManager {
  45. return &BuildManager{
  46. backend: b,
  47. pathCache: &syncmap.Map{},
  48. archiver: chrootarchive.NewArchiver(idMappings),
  49. }
  50. }
  51. // Build starts a new build from a BuildConfig
  52. func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (*builder.Result, error) {
  53. buildsTriggered.Inc()
  54. if config.Options.Dockerfile == "" {
  55. config.Options.Dockerfile = builder.DefaultDockerfileName
  56. }
  57. source, dockerfile, err := remotecontext.Detect(config)
  58. if err != nil {
  59. return nil, err
  60. }
  61. if source != nil {
  62. defer func() {
  63. if err := source.Close(); err != nil {
  64. logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
  65. }
  66. }()
  67. }
  68. builderOptions := builderOptions{
  69. Options: config.Options,
  70. ProgressWriter: config.ProgressWriter,
  71. Backend: bm.backend,
  72. PathCache: bm.pathCache,
  73. Archiver: bm.archiver,
  74. }
  75. return newBuilder(ctx, builderOptions).build(source, dockerfile)
  76. }
  77. // builderOptions are the dependencies required by the builder
  78. type builderOptions struct {
  79. Options *types.ImageBuildOptions
  80. Backend builder.Backend
  81. ProgressWriter backend.ProgressWriter
  82. PathCache pathCache
  83. Archiver *archive.Archiver
  84. }
  85. // Builder is a Dockerfile builder
  86. // It implements the builder.Backend interface.
  87. type Builder struct {
  88. options *types.ImageBuildOptions
  89. Stdout io.Writer
  90. Stderr io.Writer
  91. Aux *streamformatter.AuxFormatter
  92. Output io.Writer
  93. docker builder.Backend
  94. clientCtx context.Context
  95. archiver *archive.Archiver
  96. buildStages *buildStages
  97. disableCommit bool
  98. buildArgs *buildArgs
  99. imageSources *imageSources
  100. pathCache pathCache
  101. containerManager *containerManager
  102. imageProber ImageProber
  103. }
  104. // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
  105. func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
  106. config := options.Options
  107. if config == nil {
  108. config = new(types.ImageBuildOptions)
  109. }
  110. b := &Builder{
  111. clientCtx: clientCtx,
  112. options: config,
  113. Stdout: options.ProgressWriter.StdoutFormatter,
  114. Stderr: options.ProgressWriter.StderrFormatter,
  115. Aux: options.ProgressWriter.AuxFormatter,
  116. Output: options.ProgressWriter.Output,
  117. docker: options.Backend,
  118. archiver: options.Archiver,
  119. buildArgs: newBuildArgs(config.BuildArgs),
  120. buildStages: newBuildStages(),
  121. imageSources: newImageSources(clientCtx, options),
  122. pathCache: options.PathCache,
  123. imageProber: newImageProber(options.Backend, config.CacheFrom, config.NoCache),
  124. containerManager: newContainerManager(options.Backend),
  125. }
  126. return b
  127. }
  128. // Build runs the Dockerfile builder by parsing the Dockerfile and executing
  129. // the instructions from the file.
  130. func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
  131. defer b.imageSources.Unmount()
  132. addNodesForLabelOption(dockerfile.AST, b.options.Labels)
  133. if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
  134. buildsFailed.WithValues(metricsDockerfileSyntaxError).Inc()
  135. return nil, err
  136. }
  137. dispatchState, err := b.dispatchDockerfileWithCancellation(dockerfile, source)
  138. if err != nil {
  139. return nil, err
  140. }
  141. if b.options.Target != "" && !dispatchState.isCurrentStage(b.options.Target) {
  142. buildsFailed.WithValues(metricsBuildTargetNotReachableError).Inc()
  143. return nil, errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
  144. }
  145. b.buildArgs.WarnOnUnusedBuildArgs(b.Stderr)
  146. if dispatchState.imageID == "" {
  147. buildsFailed.WithValues(metricsDockerfileEmptyError).Inc()
  148. return nil, errors.New("No image was generated. Is your Dockerfile empty?")
  149. }
  150. return &builder.Result{ImageID: dispatchState.imageID, FromImage: dispatchState.baseImage}, nil
  151. }
  152. func emitImageID(aux *streamformatter.AuxFormatter, state *dispatchState) error {
  153. if aux == nil || state.imageID == "" {
  154. return nil
  155. }
  156. return aux.Emit(types.BuildResult{ID: state.imageID})
  157. }
  158. func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Result, source builder.Source) (*dispatchState, error) {
  159. shlex := NewShellLex(dockerfile.EscapeToken)
  160. state := newDispatchState()
  161. total := len(dockerfile.AST.Children)
  162. var err error
  163. for i, n := range dockerfile.AST.Children {
  164. select {
  165. case <-b.clientCtx.Done():
  166. logrus.Debug("Builder: build cancelled!")
  167. fmt.Fprint(b.Stdout, "Build cancelled")
  168. buildsFailed.WithValues(metricsBuildCanceled).Inc()
  169. return nil, errors.New("Build cancelled")
  170. default:
  171. // Not cancelled yet, keep going...
  172. }
  173. // If this is a FROM and we have a previous image then
  174. // emit an aux message for that image since it is the
  175. // end of the previous stage
  176. if n.Value == command.From {
  177. if err := emitImageID(b.Aux, state); err != nil {
  178. return nil, err
  179. }
  180. }
  181. if n.Value == command.From && state.isCurrentStage(b.options.Target) {
  182. break
  183. }
  184. opts := dispatchOptions{
  185. state: state,
  186. stepMsg: formatStep(i, total),
  187. node: n,
  188. shlex: shlex,
  189. source: source,
  190. }
  191. if state, err = b.dispatch(opts); err != nil {
  192. if b.options.ForceRemove {
  193. b.containerManager.RemoveAll(b.Stdout)
  194. }
  195. return nil, err
  196. }
  197. fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(state.imageID))
  198. if b.options.Remove {
  199. b.containerManager.RemoveAll(b.Stdout)
  200. }
  201. }
  202. // Emit a final aux message for the final image
  203. if err := emitImageID(b.Aux, state); err != nil {
  204. return nil, err
  205. }
  206. return state, nil
  207. }
  208. func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
  209. if len(labels) == 0 {
  210. return
  211. }
  212. node := parser.NodeFromLabels(labels)
  213. dockerfile.Children = append(dockerfile.Children, node)
  214. }
  215. // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
  216. // It will:
  217. // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
  218. // - Do build by calling builder.dispatch() to call all entries' handling routines
  219. //
  220. // BuildFromConfig is used by the /commit endpoint, with the changes
  221. // coming from the query parameter of the same name.
  222. //
  223. // TODO: Remove?
  224. func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
  225. if len(changes) == 0 {
  226. return config, nil
  227. }
  228. b := newBuilder(context.Background(), builderOptions{
  229. Options: &types.ImageBuildOptions{NoCache: true},
  230. })
  231. dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
  232. if err != nil {
  233. return nil, err
  234. }
  235. // ensure that the commands are valid
  236. for _, n := range dockerfile.AST.Children {
  237. if !validCommitCommands[n.Value] {
  238. return nil, fmt.Errorf("%s is not a valid change command", n.Value)
  239. }
  240. }
  241. b.Stdout = ioutil.Discard
  242. b.Stderr = ioutil.Discard
  243. b.disableCommit = true
  244. if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
  245. return nil, err
  246. }
  247. dispatchState := newDispatchState()
  248. dispatchState.runConfig = config
  249. return dispatchFromDockerfile(b, dockerfile, dispatchState, nil)
  250. }
  251. func checkDispatchDockerfile(dockerfile *parser.Node) error {
  252. for _, n := range dockerfile.Children {
  253. if err := checkDispatch(n); err != nil {
  254. return errors.Wrapf(err, "Dockerfile parse error line %d", n.StartLine)
  255. }
  256. }
  257. return nil
  258. }
  259. func dispatchFromDockerfile(b *Builder, result *parser.Result, dispatchState *dispatchState, source builder.Source) (*container.Config, error) {
  260. shlex := NewShellLex(result.EscapeToken)
  261. ast := result.AST
  262. total := len(ast.Children)
  263. for i, n := range ast.Children {
  264. opts := dispatchOptions{
  265. state: dispatchState,
  266. stepMsg: formatStep(i, total),
  267. node: n,
  268. shlex: shlex,
  269. source: source,
  270. }
  271. if _, err := b.dispatch(opts); err != nil {
  272. return nil, err
  273. }
  274. }
  275. return dispatchState.runConfig, nil
  276. }