github.com/afein/docker@v1.8.2/builder/job.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "strings" 11 "sync" 12 13 "github.com/docker/docker/api" 14 "github.com/docker/docker/builder/parser" 15 "github.com/docker/docker/cliconfig" 16 "github.com/docker/docker/daemon" 17 "github.com/docker/docker/graph/tags" 18 "github.com/docker/docker/pkg/archive" 19 "github.com/docker/docker/pkg/httputils" 20 "github.com/docker/docker/pkg/parsers" 21 "github.com/docker/docker/pkg/progressreader" 22 "github.com/docker/docker/pkg/streamformatter" 23 "github.com/docker/docker/pkg/stringid" 24 "github.com/docker/docker/pkg/ulimit" 25 "github.com/docker/docker/pkg/urlutil" 26 "github.com/docker/docker/registry" 27 "github.com/docker/docker/runconfig" 28 "github.com/docker/docker/utils" 29 ) 30 31 // When downloading remote contexts, limit the amount (in bytes) 32 // to be read from the response body in order to detect its Content-Type 33 const maxPreambleLength = 100 34 35 // whitelist of commands allowed for a commit/import 36 var validCommitCommands = map[string]bool{ 37 "cmd": true, 38 "entrypoint": true, 39 "env": true, 40 "expose": true, 41 "label": true, 42 "onbuild": true, 43 "user": true, 44 "volume": true, 45 "workdir": true, 46 } 47 48 // Config contains all configs for a build job 49 type Config struct { 50 DockerfileName string 51 RemoteURL string 52 RepoName string 53 SuppressOutput bool 54 NoCache bool 55 Remove bool 56 ForceRemove bool 57 Pull bool 58 Memory int64 59 MemorySwap int64 60 CPUShares int64 61 CPUPeriod int64 62 CPUQuota int64 63 CPUSetCpus string 64 CPUSetMems string 65 CgroupParent string 66 Ulimits []*ulimit.Ulimit 67 AuthConfigs map[string]cliconfig.AuthConfig 68 69 Stdout io.Writer 70 Context io.ReadCloser 71 // When closed, the job has been cancelled. 72 // Note: not all jobs implement cancellation. 73 // See Job.Cancel() and Job.WaitCancelled() 74 cancelled chan struct{} 75 cancelOnce sync.Once 76 } 77 78 // Cancel signals the build job to cancel 79 func (b *Config) Cancel() { 80 b.cancelOnce.Do(func() { 81 close(b.cancelled) 82 }) 83 } 84 85 // WaitCancelled returns a channel which is closed ("never blocks") when 86 // the job is cancelled. 87 func (b *Config) WaitCancelled() <-chan struct{} { 88 return b.cancelled 89 } 90 91 // NewBuildConfig returns a new Config struct 92 func NewBuildConfig() *Config { 93 return &Config{ 94 AuthConfigs: map[string]cliconfig.AuthConfig{}, 95 cancelled: make(chan struct{}), 96 } 97 } 98 99 // Build is the main interface of the package, it gathers the Builder 100 // struct and calls builder.Run() to do all the real build job. 101 func Build(d *daemon.Daemon, buildConfig *Config) error { 102 var ( 103 repoName string 104 tag string 105 context io.ReadCloser 106 ) 107 sf := streamformatter.NewJSONStreamFormatter() 108 109 repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName) 110 if repoName != "" { 111 if err := registry.ValidateRepositoryName(repoName); err != nil { 112 return err 113 } 114 if len(tag) > 0 { 115 if err := tags.ValidateTagName(tag); err != nil { 116 return err 117 } 118 } 119 } 120 121 if buildConfig.RemoteURL == "" { 122 context = ioutil.NopCloser(buildConfig.Context) 123 } else if urlutil.IsGitURL(buildConfig.RemoteURL) { 124 root, err := utils.GitClone(buildConfig.RemoteURL) 125 if err != nil { 126 return err 127 } 128 defer os.RemoveAll(root) 129 130 c, err := archive.Tar(root, archive.Uncompressed) 131 if err != nil { 132 return err 133 } 134 context = c 135 } else if urlutil.IsURL(buildConfig.RemoteURL) { 136 f, err := httputils.Download(buildConfig.RemoteURL) 137 if err != nil { 138 return fmt.Errorf("Error downloading remote context %s: %v", buildConfig.RemoteURL, err) 139 } 140 defer f.Body.Close() 141 ct := f.Header.Get("Content-Type") 142 clen := int(f.ContentLength) 143 contentType, bodyReader, err := inspectResponse(ct, f.Body, clen) 144 145 defer bodyReader.Close() 146 147 if err != nil { 148 return fmt.Errorf("Error detecting content type for remote %s: %v", buildConfig.RemoteURL, err) 149 } 150 if contentType == httputils.MimeTypes.TextPlain { 151 dockerFile, err := ioutil.ReadAll(bodyReader) 152 if err != nil { 153 return err 154 } 155 156 // When we're downloading just a Dockerfile put it in 157 // the default name - don't allow the client to move/specify it 158 buildConfig.DockerfileName = api.DefaultDockerfileName 159 160 c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile)) 161 if err != nil { 162 return err 163 } 164 context = c 165 } else { 166 // Pass through - this is a pre-packaged context, presumably 167 // with a Dockerfile with the right name inside it. 168 prCfg := progressreader.Config{ 169 In: bodyReader, 170 Out: buildConfig.Stdout, 171 Formatter: sf, 172 Size: clen, 173 NewLines: true, 174 ID: "Downloading context", 175 Action: buildConfig.RemoteURL, 176 } 177 context = progressreader.New(prCfg) 178 } 179 } 180 181 defer context.Close() 182 183 builder := &builder{ 184 Daemon: d, 185 OutStream: &streamformatter.StdoutFormater{ 186 Writer: buildConfig.Stdout, 187 StreamFormatter: sf, 188 }, 189 ErrStream: &streamformatter.StderrFormater{ 190 Writer: buildConfig.Stdout, 191 StreamFormatter: sf, 192 }, 193 Verbose: !buildConfig.SuppressOutput, 194 UtilizeCache: !buildConfig.NoCache, 195 Remove: buildConfig.Remove, 196 ForceRemove: buildConfig.ForceRemove, 197 Pull: buildConfig.Pull, 198 OutOld: buildConfig.Stdout, 199 StreamFormatter: sf, 200 AuthConfigs: buildConfig.AuthConfigs, 201 dockerfileName: buildConfig.DockerfileName, 202 cpuShares: buildConfig.CPUShares, 203 cpuPeriod: buildConfig.CPUPeriod, 204 cpuQuota: buildConfig.CPUQuota, 205 cpuSetCpus: buildConfig.CPUSetCpus, 206 cpuSetMems: buildConfig.CPUSetMems, 207 cgroupParent: buildConfig.CgroupParent, 208 memory: buildConfig.Memory, 209 memorySwap: buildConfig.MemorySwap, 210 ulimits: buildConfig.Ulimits, 211 cancelled: buildConfig.WaitCancelled(), 212 id: stringid.GenerateRandomID(), 213 } 214 215 defer func() { 216 builder.Daemon.Graph().Release(builder.id, builder.activeImages...) 217 }() 218 219 id, err := builder.Run(context) 220 if err != nil { 221 return err 222 } 223 if repoName != "" { 224 return d.Repositories().Tag(repoName, tag, id, true) 225 } 226 return nil 227 } 228 229 // BuildFromConfig will do build directly from parameter 'changes', which comes 230 // from Dockerfile entries, it will: 231 // 232 // - call parse.Parse() to get AST root from Dockerfile entries 233 // - do build by calling builder.dispatch() to call all entries' handling routines 234 func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) { 235 ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) 236 if err != nil { 237 return nil, err 238 } 239 240 // ensure that the commands are valid 241 for _, n := range ast.Children { 242 if !validCommitCommands[n.Value] { 243 return nil, fmt.Errorf("%s is not a valid change command", n.Value) 244 } 245 } 246 247 builder := &builder{ 248 Daemon: d, 249 Config: c, 250 OutStream: ioutil.Discard, 251 ErrStream: ioutil.Discard, 252 disableCommit: true, 253 } 254 255 for i, n := range ast.Children { 256 if err := builder.dispatch(i, n); err != nil { 257 return nil, err 258 } 259 } 260 261 return builder.Config, nil 262 } 263 264 // CommitConfig contains build configs for commit operation 265 type CommitConfig struct { 266 Pause bool 267 Repo string 268 Tag string 269 Author string 270 Comment string 271 Changes []string 272 Config *runconfig.Config 273 } 274 275 // Commit will create a new image from a container's changes 276 func Commit(name string, d *daemon.Daemon, c *CommitConfig) (string, error) { 277 container, err := d.Get(name) 278 if err != nil { 279 return "", err 280 } 281 282 if c.Config == nil { 283 c.Config = &runconfig.Config{} 284 } 285 286 newConfig, err := BuildFromConfig(d, c.Config, c.Changes) 287 if err != nil { 288 return "", err 289 } 290 291 if err := runconfig.Merge(newConfig, container.Config); err != nil { 292 return "", err 293 } 294 295 commitCfg := &daemon.ContainerCommitConfig{ 296 Pause: c.Pause, 297 Repo: c.Repo, 298 Tag: c.Tag, 299 Author: c.Author, 300 Comment: c.Comment, 301 Config: newConfig, 302 } 303 304 img, err := d.Commit(container, commitCfg) 305 if err != nil { 306 return "", err 307 } 308 309 return img.ID, nil 310 } 311 312 // inspectResponse looks into the http response data at r to determine whether its 313 // content-type is on the list of acceptable content types for remote build contexts. 314 // This function returns: 315 // - a string representation of the detected content-type 316 // - an io.Reader for the response body 317 // - an error value which will be non-nil either when something goes wrong while 318 // reading bytes from r or when the detected content-type is not acceptable. 319 func inspectResponse(ct string, r io.ReadCloser, clen int) (string, io.ReadCloser, error) { 320 plen := clen 321 if plen <= 0 || plen > maxPreambleLength { 322 plen = maxPreambleLength 323 } 324 325 preamble := make([]byte, plen, plen) 326 rlen, err := r.Read(preamble) 327 if rlen == 0 { 328 return ct, r, errors.New("Empty response") 329 } 330 if err != nil && err != io.EOF { 331 return ct, r, err 332 } 333 334 preambleR := bytes.NewReader(preamble) 335 bodyReader := ioutil.NopCloser(io.MultiReader(preambleR, r)) 336 // Some web servers will use application/octet-stream as the default 337 // content type for files without an extension (e.g. 'Dockerfile') 338 // so if we receive this value we better check for text content 339 contentType := ct 340 if len(ct) == 0 || ct == httputils.MimeTypes.OctetStream { 341 contentType, _, err = httputils.DetectContentType(preamble) 342 if err != nil { 343 return contentType, bodyReader, err 344 } 345 } 346 347 contentType = selectAcceptableMIME(contentType) 348 var cterr error 349 if len(contentType) == 0 { 350 cterr = fmt.Errorf("unsupported Content-Type %q", ct) 351 contentType = ct 352 } 353 354 return contentType, bodyReader, cterr 355 }