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