| package build |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "encoding/json" |
| "fmt" |
| "io" |
| "net/http" |
| "strconv" |
| "strings" |
| "sync" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/docker/docker/api/server/httputils" |
| "github.com/docker/docker/api/types/backend" |
| "github.com/docker/docker/pkg/ioutils" |
| "github.com/docker/docker/pkg/progress" |
| "github.com/docker/docker/pkg/streamformatter" |
| "github.com/docker/engine-api/types" |
| "github.com/docker/engine-api/types/container" |
| "github.com/docker/engine-api/types/versions" |
| "github.com/docker/go-units" |
| "golang.org/x/net/context" |
| ) |
| |
| func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) { |
| version := httputils.VersionFromContext(ctx) |
| options := &types.ImageBuildOptions{} |
| if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") { |
| options.Remove = true |
| } else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") { |
| options.Remove = true |
| } else { |
| options.Remove = httputils.BoolValue(r, "rm") |
| } |
| if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") { |
| options.PullParent = true |
| } |
| |
| options.Dockerfile = r.FormValue("dockerfile") |
| options.SuppressOutput = httputils.BoolValue(r, "q") |
| options.NoCache = httputils.BoolValue(r, "nocache") |
| options.ForceRemove = httputils.BoolValue(r, "forcerm") |
| options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") |
| options.Memory = httputils.Int64ValueOrZero(r, "memory") |
| options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") |
| options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") |
| options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") |
| options.CPUSetCPUs = r.FormValue("cpusetcpus") |
| options.CPUSetMems = r.FormValue("cpusetmems") |
| options.CgroupParent = r.FormValue("cgroupparent") |
| options.Tags = r.Form["t"] |
| |
| if r.Form.Get("shmsize") != "" { |
| shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| options.ShmSize = shmSize |
| } |
| |
| if i := container.Isolation(r.FormValue("isolation")); i != "" { |
| if !container.Isolation.IsValid(i) { |
| return nil, fmt.Errorf("Unsupported isolation: %q", i) |
| } |
| options.Isolation = i |
| } |
| |
| var buildUlimits = []*units.Ulimit{} |
| ulimitsJSON := r.FormValue("ulimits") |
| if ulimitsJSON != "" { |
| if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { |
| return nil, err |
| } |
| options.Ulimits = buildUlimits |
| } |
| |
| var buildArgs = map[string]string{} |
| buildArgsJSON := r.FormValue("buildargs") |
| if buildArgsJSON != "" { |
| if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil { |
| return nil, err |
| } |
| options.BuildArgs = buildArgs |
| } |
| var labels = map[string]string{} |
| labelsJSON := r.FormValue("labels") |
| if labelsJSON != "" { |
| if err := json.NewDecoder(strings.NewReader(labelsJSON)).Decode(&labels); err != nil { |
| return nil, err |
| } |
| options.Labels = labels |
| } |
| |
| return options, nil |
| } |
| |
| type syncWriter struct { |
| w io.Writer |
| mu sync.Mutex |
| } |
| |
| func (s *syncWriter) Write(b []byte) (count int, err error) { |
| s.mu.Lock() |
| count, err = s.w.Write(b) |
| s.mu.Unlock() |
| return |
| } |
| |
| func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
| var ( |
| authConfigs = map[string]types.AuthConfig{} |
| authConfigsEncoded = r.Header.Get("X-Registry-Config") |
| notVerboseBuffer = bytes.NewBuffer(nil) |
| ) |
| |
| if authConfigsEncoded != "" { |
| authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) |
| if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { |
| // for a pull it is not an error if no auth was given |
| // to increase compatibility with the existing api it is defaulting |
| // to be empty. |
| } |
| } |
| |
| w.Header().Set("Content-Type", "application/json") |
| |
| output := ioutils.NewWriteFlusher(w) |
| defer output.Close() |
| sf := streamformatter.NewJSONStreamFormatter() |
| errf := func(err error) error { |
| if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 { |
| output.Write(notVerboseBuffer.Bytes()) |
| } |
| // Do not write the error in the http output if it's still empty. |
| // This prevents from writing a 200(OK) when there is an internal error. |
| if !output.Flushed() { |
| return err |
| } |
| _, err = w.Write(sf.FormatError(err)) |
| if err != nil { |
| logrus.Warnf("could not write error response: %v", err) |
| } |
| return nil |
| } |
| |
| buildOptions, err := newImageBuildOptions(ctx, r) |
| if err != nil { |
| return errf(err) |
| } |
| buildOptions.AuthConfigs = authConfigs |
| |
| remoteURL := r.FormValue("remote") |
| |
| // Currently, only used if context is from a remote url. |
| // Look at code in DetectContextFromRemoteURL for more information. |
| createProgressReader := func(in io.ReadCloser) io.ReadCloser { |
| progressOutput := sf.NewProgressOutput(output, true) |
| if buildOptions.SuppressOutput { |
| progressOutput = sf.NewProgressOutput(notVerboseBuffer, true) |
| } |
| return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL) |
| } |
| |
| var out io.Writer = output |
| if buildOptions.SuppressOutput { |
| out = notVerboseBuffer |
| } |
| out = &syncWriter{w: out} |
| stdout := &streamformatter.StdoutFormatter{Writer: out, StreamFormatter: sf} |
| stderr := &streamformatter.StderrFormatter{Writer: out, StreamFormatter: sf} |
| |
| pg := backend.ProgressWriter{ |
| Output: out, |
| StdoutFormatter: stdout, |
| StderrFormatter: stderr, |
| ProgressReaderFunc: createProgressReader, |
| } |
| |
| imgID, err := br.backend.BuildFromContext(ctx, r.Body, remoteURL, buildOptions, pg) |
| if err != nil { |
| return errf(err) |
| } |
| |
| // Everything worked so if -q was provided the output from the daemon |
| // should be just the image ID and we'll print that to stdout. |
| if buildOptions.SuppressOutput { |
| stdout := &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} |
| fmt.Fprintf(stdout, "%s\n", string(imgID)) |
| } |
| |
| return nil |
| } |