github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/builder/dockerfile/builder.go (about) 1 package dockerfile 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "strings" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/api/types/backend" 15 "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/builder" 17 "github.com/docker/docker/builder/dockerfile/parser" 18 "github.com/docker/docker/image" 19 "github.com/docker/docker/pkg/stringid" 20 "github.com/docker/docker/reference" 21 "golang.org/x/net/context" 22 ) 23 24 var validCommitCommands = map[string]bool{ 25 "cmd": true, 26 "entrypoint": true, 27 "healthcheck": true, 28 "env": true, 29 "expose": true, 30 "label": true, 31 "onbuild": true, 32 "user": true, 33 "volume": true, 34 "workdir": true, 35 } 36 37 // BuiltinAllowedBuildArgs is list of built-in allowed build args 38 var BuiltinAllowedBuildArgs = map[string]bool{ 39 "HTTP_PROXY": true, 40 "http_proxy": true, 41 "HTTPS_PROXY": true, 42 "https_proxy": true, 43 "FTP_PROXY": true, 44 "ftp_proxy": true, 45 "NO_PROXY": true, 46 "no_proxy": true, 47 } 48 49 // Builder is a Dockerfile builder 50 // It implements the builder.Backend interface. 51 type Builder struct { 52 options *types.ImageBuildOptions 53 54 Stdout io.Writer 55 Stderr io.Writer 56 Output io.Writer 57 58 docker builder.Backend 59 context builder.Context 60 clientCtx context.Context 61 cancel context.CancelFunc 62 63 dockerfile *parser.Node 64 runConfig *container.Config // runconfig for cmd, run, entrypoint etc. 65 flags *BFlags 66 tmpContainers map[string]struct{} 67 image string // imageID 68 noBaseImage bool 69 maintainer string 70 cmdSet bool 71 disableCommit bool 72 cacheBusted bool 73 allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'. 74 directive parser.Directive 75 76 // TODO: remove once docker.Commit can receive a tag 77 id string 78 79 imageCache builder.ImageCache 80 } 81 82 // BuildManager implements builder.Backend and is shared across all Builder objects. 83 type BuildManager struct { 84 backend builder.Backend 85 } 86 87 // NewBuildManager creates a BuildManager. 88 func NewBuildManager(b builder.Backend) (bm *BuildManager) { 89 return &BuildManager{backend: b} 90 } 91 92 // BuildFromContext builds a new image from a given context. 93 func (bm *BuildManager) BuildFromContext(ctx context.Context, src io.ReadCloser, remote string, buildOptions *types.ImageBuildOptions, pg backend.ProgressWriter) (string, error) { 94 buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(src, remote, pg.ProgressReaderFunc) 95 if err != nil { 96 return "", err 97 } 98 defer func() { 99 if err := buildContext.Close(); err != nil { 100 logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) 101 } 102 }() 103 if len(dockerfileName) > 0 { 104 buildOptions.Dockerfile = dockerfileName 105 } 106 b, err := NewBuilder(ctx, buildOptions, bm.backend, builder.DockerIgnoreContext{ModifiableContext: buildContext}, nil) 107 if err != nil { 108 return "", err 109 } 110 return b.build(pg.StdoutFormatter, pg.StderrFormatter, pg.Output) 111 } 112 113 // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. 114 // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, 115 // will be read from the Context passed to Build(). 116 func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { 117 if config == nil { 118 config = new(types.ImageBuildOptions) 119 } 120 if config.BuildArgs == nil { 121 config.BuildArgs = make(map[string]string) 122 } 123 ctx, cancel := context.WithCancel(clientCtx) 124 b = &Builder{ 125 clientCtx: ctx, 126 cancel: cancel, 127 options: config, 128 Stdout: os.Stdout, 129 Stderr: os.Stderr, 130 docker: backend, 131 context: buildContext, 132 runConfig: new(container.Config), 133 tmpContainers: map[string]struct{}{}, 134 id: stringid.GenerateNonCryptoID(), 135 allowedBuildArgs: make(map[string]bool), 136 directive: parser.Directive{ 137 EscapeSeen: false, 138 LookingForDirectives: true, 139 }, 140 } 141 if icb, ok := backend.(builder.ImageCacheBuilder); ok { 142 b.imageCache = icb.MakeImageCache(config.CacheFrom) 143 } 144 145 parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape 146 147 if dockerfile != nil { 148 b.dockerfile, err = parser.Parse(dockerfile, &b.directive) 149 if err != nil { 150 return nil, err 151 } 152 } 153 154 return b, nil 155 } 156 157 // sanitizeRepoAndTags parses the raw "t" parameter received from the client 158 // to a slice of repoAndTag. 159 // It also validates each repoName and tag. 160 func sanitizeRepoAndTags(names []string) ([]reference.Named, error) { 161 var ( 162 repoAndTags []reference.Named 163 // This map is used for deduplicating the "-t" parameter. 164 uniqNames = make(map[string]struct{}) 165 ) 166 for _, repo := range names { 167 if repo == "" { 168 continue 169 } 170 171 ref, err := reference.ParseNamed(repo) 172 if err != nil { 173 return nil, err 174 } 175 176 ref = reference.WithDefaultTag(ref) 177 178 if _, isCanonical := ref.(reference.Canonical); isCanonical { 179 return nil, errors.New("build tag cannot contain a digest") 180 } 181 182 if _, isTagged := ref.(reference.NamedTagged); !isTagged { 183 ref, err = reference.WithTag(ref, reference.DefaultTag) 184 if err != nil { 185 return nil, err 186 } 187 } 188 189 nameWithTag := ref.String() 190 191 if _, exists := uniqNames[nameWithTag]; !exists { 192 uniqNames[nameWithTag] = struct{}{} 193 repoAndTags = append(repoAndTags, ref) 194 } 195 } 196 return repoAndTags, nil 197 } 198 199 // build runs the Dockerfile builder from a context and a docker object that allows to make calls 200 // to Docker. 201 // 202 // This will (barring errors): 203 // 204 // * read the dockerfile from context 205 // * parse the dockerfile if not already parsed 206 // * walk the AST and execute it by dispatching to handlers. If Remove 207 // or ForceRemove is set, additional cleanup around containers happens after 208 // processing. 209 // * Tag image, if applicable. 210 // * Print a happy message and return the image ID. 211 // 212 func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) { 213 b.Stdout = stdout 214 b.Stderr = stderr 215 b.Output = out 216 217 // If Dockerfile was not parsed yet, extract it from the Context 218 if b.dockerfile == nil { 219 if err := b.readDockerfile(); err != nil { 220 return "", err 221 } 222 } 223 224 repoAndTags, err := sanitizeRepoAndTags(b.options.Tags) 225 if err != nil { 226 return "", err 227 } 228 229 if len(b.options.Labels) > 0 { 230 line := "LABEL " 231 for k, v := range b.options.Labels { 232 line += fmt.Sprintf("%q=%q ", k, v) 233 } 234 _, node, err := parser.ParseLine(line, &b.directive) 235 if err != nil { 236 return "", err 237 } 238 b.dockerfile.Children = append(b.dockerfile.Children, node) 239 } 240 241 var shortImgID string 242 total := len(b.dockerfile.Children) 243 for _, n := range b.dockerfile.Children { 244 if err := b.checkDispatch(n, false); err != nil { 245 return "", err 246 } 247 } 248 249 for i, n := range b.dockerfile.Children { 250 select { 251 case <-b.clientCtx.Done(): 252 logrus.Debug("Builder: build cancelled!") 253 fmt.Fprintf(b.Stdout, "Build cancelled") 254 return "", fmt.Errorf("Build cancelled") 255 default: 256 // Not cancelled yet, keep going... 257 } 258 259 if err := b.dispatch(i, total, n); err != nil { 260 if b.options.ForceRemove { 261 b.clearTmp() 262 } 263 return "", err 264 } 265 266 shortImgID = stringid.TruncateID(b.image) 267 fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID) 268 if b.options.Remove { 269 b.clearTmp() 270 } 271 } 272 273 // check if there are any leftover build-args that were passed but not 274 // consumed during build. Return an error, if there are any. 275 leftoverArgs := []string{} 276 for arg := range b.options.BuildArgs { 277 if !b.isBuildArgAllowed(arg) { 278 leftoverArgs = append(leftoverArgs, arg) 279 } 280 } 281 if len(leftoverArgs) > 0 { 282 return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs) 283 } 284 285 if b.image == "" { 286 return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") 287 } 288 289 imageID := image.ID(b.image) 290 for _, rt := range repoAndTags { 291 if err := b.docker.TagImageWithReference(imageID, rt); err != nil { 292 return "", err 293 } 294 } 295 296 fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID) 297 return b.image, nil 298 } 299 300 // Cancel cancels an ongoing Dockerfile build. 301 func (b *Builder) Cancel() { 302 b.cancel() 303 } 304 305 // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile 306 // It will: 307 // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries. 308 // - Do build by calling builder.dispatch() to call all entries' handling routines 309 // 310 // BuildFromConfig is used by the /commit endpoint, with the changes 311 // coming from the query parameter of the same name. 312 // 313 // TODO: Remove? 314 func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) { 315 b, err := NewBuilder(context.Background(), nil, nil, nil, nil) 316 if err != nil { 317 return nil, err 318 } 319 320 ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), &b.directive) 321 if err != nil { 322 return nil, err 323 } 324 325 // ensure that the commands are valid 326 for _, n := range ast.Children { 327 if !validCommitCommands[n.Value] { 328 return nil, fmt.Errorf("%s is not a valid change command", n.Value) 329 } 330 } 331 332 b.runConfig = config 333 b.Stdout = ioutil.Discard 334 b.Stderr = ioutil.Discard 335 b.disableCommit = true 336 337 total := len(ast.Children) 338 for _, n := range ast.Children { 339 if err := b.checkDispatch(n, false); err != nil { 340 return nil, err 341 } 342 } 343 344 for i, n := range ast.Children { 345 if err := b.dispatch(i, total, n); err != nil { 346 return nil, err 347 } 348 } 349 350 return b.runConfig, nil 351 }