github.com/tompao/docker@v1.9.1/builder/dockerfile/builder.go (about) 1 package dockerfile 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "runtime" 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/daemon" 17 "github.com/docker/docker/pkg/stringid" 18 "github.com/docker/docker/pkg/ulimit" 19 "github.com/docker/docker/runconfig" 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 // Config constitutes the configuration for a Dockerfile builder. 47 type Config struct { 48 // only used if Dockerfile has to be extracted from Context 49 DockerfileName string 50 51 Verbose bool 52 UseCache bool 53 Remove bool 54 ForceRemove bool 55 Pull bool 56 BuildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'. 57 58 // resource constraints 59 // TODO: factor out to be reused with Run ? 60 61 Memory int64 62 MemorySwap int64 63 CPUShares int64 64 CPUPeriod int64 65 CPUQuota int64 66 CPUSetCpus string 67 CPUSetMems string 68 CgroupParent string 69 Ulimits []*ulimit.Ulimit 70 } 71 72 // Builder is a Dockerfile builder 73 // It implements the builder.Builder interface. 74 type Builder struct { 75 *Config 76 77 Stdout io.Writer 78 Stderr io.Writer 79 80 docker builder.Docker 81 context builder.Context 82 83 dockerfile *parser.Node 84 runConfig *runconfig.Config // runconfig for cmd, run, entrypoint etc. 85 flags *BFlags 86 tmpContainers map[string]struct{} 87 image string // imageID 88 noBaseImage bool 89 maintainer string 90 cmdSet bool 91 disableCommit bool 92 cacheBusted bool 93 cancelled chan struct{} 94 cancelOnce sync.Once 95 allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'. 96 97 // TODO: remove once docker.Commit can receive a tag 98 id string 99 activeImages []string 100 } 101 102 // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. 103 // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, 104 // will be read from the Context passed to Build(). 105 func NewBuilder(config *Config, docker builder.Docker, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { 106 if config == nil { 107 config = new(Config) 108 } 109 if config.BuildArgs == nil { 110 config.BuildArgs = make(map[string]string) 111 } 112 b = &Builder{ 113 Config: config, 114 Stdout: os.Stdout, 115 Stderr: os.Stderr, 116 docker: docker, 117 context: context, 118 runConfig: new(runconfig.Config), 119 tmpContainers: map[string]struct{}{}, 120 cancelled: make(chan struct{}), 121 id: stringid.GenerateNonCryptoID(), 122 allowedBuildArgs: make(map[string]bool), 123 } 124 if dockerfile != nil { 125 b.dockerfile, err = parser.Parse(dockerfile) 126 if err != nil { 127 return nil, err 128 } 129 } 130 131 return b, nil 132 } 133 134 // Build runs the Dockerfile builder from a context and a docker object that allows to make calls 135 // to Docker. 136 // 137 // This will (barring errors): 138 // 139 // * read the dockerfile from context 140 // * parse the dockerfile if not already parsed 141 // * walk the AST and execute it by dispatching to handlers. If Remove 142 // or ForceRemove is set, additional cleanup around containers happens after 143 // processing. 144 // * Print a happy message and return the image ID. 145 // * NOT tag the image, that is responsibility of the caller. 146 // 147 func (b *Builder) Build() (string, error) { 148 // TODO: remove once b.docker.Commit can take a tag parameter. 149 defer func() { 150 b.docker.Release(b.id, b.activeImages) 151 }() 152 153 // If Dockerfile was not parsed yet, extract it from the Context 154 if b.dockerfile == nil { 155 if err := b.readDockerfile(); err != nil { 156 return "", err 157 } 158 } 159 160 var shortImgID string 161 for i, n := range b.dockerfile.Children { 162 select { 163 case <-b.cancelled: 164 logrus.Debug("Builder: build cancelled!") 165 fmt.Fprintf(b.Stdout, "Build cancelled") 166 return "", fmt.Errorf("Build cancelled") 167 default: 168 // Not cancelled yet, keep going... 169 } 170 if err := b.dispatch(i, n); err != nil { 171 if b.ForceRemove { 172 b.clearTmp() 173 } 174 return "", err 175 } 176 shortImgID = stringid.TruncateID(b.image) 177 fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID) 178 if b.Remove { 179 b.clearTmp() 180 } 181 } 182 183 // check if there are any leftover build-args that were passed but not 184 // consumed during build. Return an error, if there are any. 185 leftoverArgs := []string{} 186 for arg := range b.BuildArgs { 187 if !b.isBuildArgAllowed(arg) { 188 leftoverArgs = append(leftoverArgs, arg) 189 } 190 } 191 if len(leftoverArgs) > 0 { 192 return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs) 193 } 194 195 if b.image == "" { 196 return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") 197 } 198 199 fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID) 200 return b.image, nil 201 } 202 203 // Cancel cancels an ongoing Dockerfile build. 204 func (b *Builder) Cancel() { 205 b.cancelOnce.Do(func() { 206 close(b.cancelled) 207 }) 208 } 209 210 // CommitConfig contains build configs for commit operation 211 type CommitConfig struct { 212 Pause bool 213 Repo string 214 Tag string 215 Author string 216 Comment string 217 Changes []string 218 Config *runconfig.Config 219 } 220 221 // BuildFromConfig will do build directly from parameter 'changes', which comes 222 // from Dockerfile entries, it will: 223 // - call parse.Parse() to get AST root from Dockerfile entries 224 // - do build by calling builder.dispatch() to call all entries' handling routines 225 // TODO: remove? 226 func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) { 227 ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) 228 if err != nil { 229 return nil, err 230 } 231 232 // ensure that the commands are valid 233 for _, n := range ast.Children { 234 if !validCommitCommands[n.Value] { 235 return nil, fmt.Errorf("%s is not a valid change command", n.Value) 236 } 237 } 238 239 b, err := NewBuilder(nil, nil, nil, nil) 240 if err != nil { 241 return nil, err 242 } 243 b.runConfig = config 244 b.Stdout = ioutil.Discard 245 b.Stderr = ioutil.Discard 246 b.disableCommit = true 247 248 for i, n := range ast.Children { 249 if err := b.dispatch(i, n); err != nil { 250 return nil, err 251 } 252 } 253 254 return b.runConfig, nil 255 } 256 257 // Commit will create a new image from a container's changes 258 // TODO: remove daemon, make Commit a method on *Builder ? 259 func Commit(container *daemon.Container, d *daemon.Daemon, c *CommitConfig) (string, error) { 260 // It is not possible to commit a running container on Windows 261 if runtime.GOOS == "windows" && container.IsRunning() { 262 return "", fmt.Errorf("Windows does not support commit of a running container") 263 } 264 265 if c.Config == nil { 266 c.Config = &runconfig.Config{} 267 } 268 269 newConfig, err := BuildFromConfig(c.Config, c.Changes) 270 if err != nil { 271 return "", err 272 } 273 274 if err := runconfig.Merge(newConfig, container.Config); err != nil { 275 return "", err 276 } 277 278 commitCfg := &daemon.ContainerCommitConfig{ 279 Pause: c.Pause, 280 Repo: c.Repo, 281 Tag: c.Tag, 282 Author: c.Author, 283 Comment: c.Comment, 284 Config: newConfig, 285 } 286 287 img, err := d.Commit(container, commitCfg) 288 if err != nil { 289 return "", err 290 } 291 return img.ID, nil 292 }