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