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