github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/builder/dockerfile/builder.go (about) 1 package dockerfile 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "strings" 10 "sync" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/builder" 14 "github.com/docker/docker/builder/dockerfile/parser" 15 "github.com/docker/docker/pkg/stringid" 16 "github.com/docker/docker/pkg/ulimit" 17 "github.com/docker/docker/runconfig" 18 ) 19 20 var validCommitCommands = map[string]bool{ 21 "cmd": true, 22 "entrypoint": true, 23 "env": true, 24 "expose": true, 25 "label": true, 26 "onbuild": true, 27 "user": true, 28 "volume": true, 29 "workdir": true, 30 } 31 32 // BuiltinAllowedBuildArgs is list of built-in allowed build args 33 var BuiltinAllowedBuildArgs = map[string]bool{ 34 "HTTP_PROXY": true, 35 "http_proxy": true, 36 "HTTPS_PROXY": true, 37 "https_proxy": true, 38 "FTP_PROXY": true, 39 "ftp_proxy": true, 40 "NO_PROXY": true, 41 "no_proxy": true, 42 } 43 44 // Config constitutes the configuration for a Dockerfile builder. 45 type Config struct { 46 // only used if Dockerfile has to be extracted from Context 47 DockerfileName string 48 49 Verbose bool 50 UseCache bool 51 Remove bool 52 ForceRemove bool 53 Pull bool 54 BuildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'. 55 Isolation runconfig.IsolationLevel 56 57 // resource constraints 58 // TODO: factor out to be reused with Run ? 59 60 Memory int64 61 MemorySwap int64 62 ShmSize *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.Backend 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 } 100 101 // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. 102 // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, 103 // will be read from the Context passed to Build(). 104 func NewBuilder(config *Config, docker builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { 105 if config == nil { 106 config = new(Config) 107 } 108 if config.BuildArgs == nil { 109 config.BuildArgs = make(map[string]string) 110 } 111 b = &Builder{ 112 Config: config, 113 Stdout: os.Stdout, 114 Stderr: os.Stderr, 115 docker: docker, 116 context: context, 117 runConfig: new(runconfig.Config), 118 tmpContainers: map[string]struct{}{}, 119 cancelled: make(chan struct{}), 120 id: stringid.GenerateNonCryptoID(), 121 allowedBuildArgs: make(map[string]bool), 122 } 123 if dockerfile != nil { 124 b.dockerfile, err = parser.Parse(dockerfile) 125 if err != nil { 126 return nil, err 127 } 128 } 129 130 return b, nil 131 } 132 133 // Build runs the Dockerfile builder from a context and a docker object that allows to make calls 134 // to Docker. 135 // 136 // This will (barring errors): 137 // 138 // * read the dockerfile from context 139 // * parse the dockerfile if not already parsed 140 // * walk the AST and execute it by dispatching to handlers. If Remove 141 // or ForceRemove is set, additional cleanup around containers happens after 142 // processing. 143 // * Print a happy message and return the image ID. 144 // * NOT tag the image, that is responsibility of the caller. 145 // 146 func (b *Builder) Build() (string, error) { 147 // If Dockerfile was not parsed yet, extract it from the Context 148 if b.dockerfile == nil { 149 if err := b.readDockerfile(); err != nil { 150 return "", err 151 } 152 } 153 154 var shortImgID string 155 for i, n := range b.dockerfile.Children { 156 select { 157 case <-b.cancelled: 158 logrus.Debug("Builder: build cancelled!") 159 fmt.Fprintf(b.Stdout, "Build cancelled") 160 return "", fmt.Errorf("Build cancelled") 161 default: 162 // Not cancelled yet, keep going... 163 } 164 if err := b.dispatch(i, n); err != nil { 165 if b.ForceRemove { 166 b.clearTmp() 167 } 168 return "", err 169 } 170 shortImgID = stringid.TruncateID(b.image) 171 fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID) 172 if b.Remove { 173 b.clearTmp() 174 } 175 } 176 177 // check if there are any leftover build-args that were passed but not 178 // consumed during build. Return an error, if there are any. 179 leftoverArgs := []string{} 180 for arg := range b.BuildArgs { 181 if !b.isBuildArgAllowed(arg) { 182 leftoverArgs = append(leftoverArgs, arg) 183 } 184 } 185 if len(leftoverArgs) > 0 { 186 return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs) 187 } 188 189 if b.image == "" { 190 return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") 191 } 192 193 fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID) 194 return b.image, nil 195 } 196 197 // Cancel cancels an ongoing Dockerfile build. 198 func (b *Builder) Cancel() { 199 b.cancelOnce.Do(func() { 200 close(b.cancelled) 201 }) 202 } 203 204 // BuildFromConfig will do build directly from parameter 'changes', which comes 205 // from Dockerfile entries, it will: 206 // - call parse.Parse() to get AST root from Dockerfile entries 207 // - do build by calling builder.dispatch() to call all entries' handling routines 208 // TODO: remove? 209 func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) { 210 ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) 211 if err != nil { 212 return nil, err 213 } 214 215 // ensure that the commands are valid 216 for _, n := range ast.Children { 217 if !validCommitCommands[n.Value] { 218 return nil, fmt.Errorf("%s is not a valid change command", n.Value) 219 } 220 } 221 222 b, err := NewBuilder(nil, nil, nil, nil) 223 if err != nil { 224 return nil, err 225 } 226 b.runConfig = config 227 b.Stdout = ioutil.Discard 228 b.Stderr = ioutil.Discard 229 b.disableCommit = true 230 231 for i, n := range ast.Children { 232 if err := b.dispatch(i, n); err != nil { 233 return nil, err 234 } 235 } 236 237 return b.runConfig, nil 238 }