github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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/engine-api/types" 17 "github.com/docker/engine-api/types/container" 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 // Builder is a Dockerfile builder 45 // It implements the builder.Backend interface. 46 type Builder struct { 47 options *types.ImageBuildOptions 48 49 Stdout io.Writer 50 Stderr io.Writer 51 52 docker builder.Backend 53 context builder.Context 54 55 dockerfile *parser.Node 56 runConfig *container.Config // runconfig for cmd, run, entrypoint etc. 57 flags *BFlags 58 tmpContainers map[string]struct{} 59 image string // imageID 60 noBaseImage bool 61 maintainer string 62 cmdSet bool 63 disableCommit bool 64 cacheBusted bool 65 cancelled chan struct{} 66 cancelOnce sync.Once 67 allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'. 68 69 // TODO: remove once docker.Commit can receive a tag 70 id string 71 Output io.Writer 72 } 73 74 // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. 75 // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, 76 // will be read from the Context passed to Build(). 77 func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { 78 if config == nil { 79 config = new(types.ImageBuildOptions) 80 } 81 if config.BuildArgs == nil { 82 config.BuildArgs = make(map[string]string) 83 } 84 b = &Builder{ 85 options: config, 86 Stdout: os.Stdout, 87 Stderr: os.Stderr, 88 docker: backend, 89 context: context, 90 runConfig: new(container.Config), 91 tmpContainers: map[string]struct{}{}, 92 cancelled: make(chan struct{}), 93 id: stringid.GenerateNonCryptoID(), 94 allowedBuildArgs: make(map[string]bool), 95 } 96 if dockerfile != nil { 97 b.dockerfile, err = parser.Parse(dockerfile) 98 if err != nil { 99 return nil, err 100 } 101 } 102 103 return b, nil 104 } 105 106 // Build runs the Dockerfile builder from a context and a docker object that allows to make calls 107 // to Docker. 108 // 109 // This will (barring errors): 110 // 111 // * read the dockerfile from context 112 // * parse the dockerfile if not already parsed 113 // * walk the AST and execute it by dispatching to handlers. If Remove 114 // or ForceRemove is set, additional cleanup around containers happens after 115 // processing. 116 // * Print a happy message and return the image ID. 117 // * NOT tag the image, that is responsibility of the caller. 118 // 119 func (b *Builder) Build() (string, error) { 120 // If Dockerfile was not parsed yet, extract it from the Context 121 if b.dockerfile == nil { 122 if err := b.readDockerfile(); err != nil { 123 return "", err 124 } 125 } 126 127 var shortImgID string 128 for i, n := range b.dockerfile.Children { 129 select { 130 case <-b.cancelled: 131 logrus.Debug("Builder: build cancelled!") 132 fmt.Fprintf(b.Stdout, "Build cancelled") 133 return "", fmt.Errorf("Build cancelled") 134 default: 135 // Not cancelled yet, keep going... 136 } 137 if err := b.dispatch(i, n); err != nil { 138 if b.options.ForceRemove { 139 b.clearTmp() 140 } 141 return "", err 142 } 143 shortImgID = stringid.TruncateID(b.image) 144 fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID) 145 if b.options.Remove { 146 b.clearTmp() 147 } 148 } 149 150 // check if there are any leftover build-args that were passed but not 151 // consumed during build. Return an error, if there are any. 152 leftoverArgs := []string{} 153 for arg := range b.options.BuildArgs { 154 if !b.isBuildArgAllowed(arg) { 155 leftoverArgs = append(leftoverArgs, arg) 156 } 157 } 158 if len(leftoverArgs) > 0 { 159 return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs) 160 } 161 162 if b.image == "" { 163 return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") 164 } 165 166 fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID) 167 return b.image, nil 168 } 169 170 // Cancel cancels an ongoing Dockerfile build. 171 func (b *Builder) Cancel() { 172 b.cancelOnce.Do(func() { 173 close(b.cancelled) 174 }) 175 } 176 177 // BuildFromConfig will do build directly from parameter 'changes', which comes 178 // from Dockerfile entries, it will: 179 // - call parse.Parse() to get AST root from Dockerfile entries 180 // - do build by calling builder.dispatch() to call all entries' handling routines 181 // TODO: remove? 182 func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) { 183 ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) 184 if err != nil { 185 return nil, err 186 } 187 188 // ensure that the commands are valid 189 for _, n := range ast.Children { 190 if !validCommitCommands[n.Value] { 191 return nil, fmt.Errorf("%s is not a valid change command", n.Value) 192 } 193 } 194 195 b, err := NewBuilder(nil, nil, nil, nil) 196 if err != nil { 197 return nil, err 198 } 199 b.runConfig = config 200 b.Stdout = ioutil.Discard 201 b.Stderr = ioutil.Discard 202 b.disableCommit = true 203 204 for i, n := range ast.Children { 205 if err := b.dispatch(i, n); err != nil { 206 return nil, err 207 } 208 } 209 210 return b.runConfig, nil 211 }