github.com/hms58/moby@v1.13.1/api/server/router/build/build_routes.go (about) 1 package build 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync" 14 15 "github.com/Sirupsen/logrus" 16 "github.com/docker/docker/api/server/httputils" 17 "github.com/docker/docker/api/types" 18 "github.com/docker/docker/api/types/backend" 19 "github.com/docker/docker/api/types/container" 20 "github.com/docker/docker/api/types/versions" 21 "github.com/docker/docker/pkg/ioutils" 22 "github.com/docker/docker/pkg/progress" 23 "github.com/docker/docker/pkg/streamformatter" 24 "github.com/docker/go-units" 25 "golang.org/x/net/context" 26 ) 27 28 func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) { 29 version := httputils.VersionFromContext(ctx) 30 options := &types.ImageBuildOptions{} 31 if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") { 32 options.Remove = true 33 } else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") { 34 options.Remove = true 35 } else { 36 options.Remove = httputils.BoolValue(r, "rm") 37 } 38 if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") { 39 options.PullParent = true 40 } 41 42 options.Dockerfile = r.FormValue("dockerfile") 43 options.SuppressOutput = httputils.BoolValue(r, "q") 44 options.NoCache = httputils.BoolValue(r, "nocache") 45 options.ForceRemove = httputils.BoolValue(r, "forcerm") 46 options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") 47 options.Memory = httputils.Int64ValueOrZero(r, "memory") 48 options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") 49 options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") 50 options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") 51 options.CPUSetCPUs = r.FormValue("cpusetcpus") 52 options.CPUSetMems = r.FormValue("cpusetmems") 53 options.CgroupParent = r.FormValue("cgroupparent") 54 options.NetworkMode = r.FormValue("networkmode") 55 options.Tags = r.Form["t"] 56 options.SecurityOpt = r.Form["securityopt"] 57 options.Squash = httputils.BoolValue(r, "squash") 58 59 if r.Form.Get("shmsize") != "" { 60 shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) 61 if err != nil { 62 return nil, err 63 } 64 options.ShmSize = shmSize 65 } 66 67 if i := container.Isolation(r.FormValue("isolation")); i != "" { 68 if !container.Isolation.IsValid(i) { 69 return nil, fmt.Errorf("Unsupported isolation: %q", i) 70 } 71 options.Isolation = i 72 } 73 74 if runtime.GOOS != "windows" && options.SecurityOpt != nil { 75 return nil, fmt.Errorf("the daemon on this platform does not support --security-opt to build") 76 } 77 78 var buildUlimits = []*units.Ulimit{} 79 ulimitsJSON := r.FormValue("ulimits") 80 if ulimitsJSON != "" { 81 if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil { 82 return nil, err 83 } 84 options.Ulimits = buildUlimits 85 } 86 87 var buildArgs = map[string]*string{} 88 buildArgsJSON := r.FormValue("buildargs") 89 90 // Note that there are two ways a --build-arg might appear in the 91 // json of the query param: 92 // "foo":"bar" 93 // and "foo":nil 94 // The first is the normal case, ie. --build-arg foo=bar 95 // or --build-arg foo 96 // where foo's value was picked up from an env var. 97 // The second ("foo":nil) is where they put --build-arg foo 98 // but "foo" isn't set as an env var. In that case we can't just drop 99 // the fact they mentioned it, we need to pass that along to the builder 100 // so that it can print a warning about "foo" being unused if there is 101 // no "ARG foo" in the Dockerfile. 102 if buildArgsJSON != "" { 103 if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil { 104 return nil, err 105 } 106 options.BuildArgs = buildArgs 107 } 108 109 var labels = map[string]string{} 110 labelsJSON := r.FormValue("labels") 111 if labelsJSON != "" { 112 if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil { 113 return nil, err 114 } 115 options.Labels = labels 116 } 117 118 var cacheFrom = []string{} 119 cacheFromJSON := r.FormValue("cachefrom") 120 if cacheFromJSON != "" { 121 if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil { 122 return nil, err 123 } 124 options.CacheFrom = cacheFrom 125 } 126 127 return options, nil 128 } 129 130 type syncWriter struct { 131 w io.Writer 132 mu sync.Mutex 133 } 134 135 func (s *syncWriter) Write(b []byte) (count int, err error) { 136 s.mu.Lock() 137 count, err = s.w.Write(b) 138 s.mu.Unlock() 139 return 140 } 141 142 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 143 var ( 144 authConfigs = map[string]types.AuthConfig{} 145 authConfigsEncoded = r.Header.Get("X-Registry-Config") 146 notVerboseBuffer = bytes.NewBuffer(nil) 147 ) 148 149 if authConfigsEncoded != "" { 150 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 151 if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { 152 // for a pull it is not an error if no auth was given 153 // to increase compatibility with the existing api it is defaulting 154 // to be empty. 155 } 156 } 157 158 w.Header().Set("Content-Type", "application/json") 159 160 output := ioutils.NewWriteFlusher(w) 161 defer output.Close() 162 sf := streamformatter.NewJSONStreamFormatter() 163 errf := func(err error) error { 164 if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 { 165 output.Write(notVerboseBuffer.Bytes()) 166 } 167 // Do not write the error in the http output if it's still empty. 168 // This prevents from writing a 200(OK) when there is an internal error. 169 if !output.Flushed() { 170 return err 171 } 172 _, err = w.Write(sf.FormatError(err)) 173 if err != nil { 174 logrus.Warnf("could not write error response: %v", err) 175 } 176 return nil 177 } 178 179 buildOptions, err := newImageBuildOptions(ctx, r) 180 if err != nil { 181 return errf(err) 182 } 183 buildOptions.AuthConfigs = authConfigs 184 185 remoteURL := r.FormValue("remote") 186 187 // Currently, only used if context is from a remote url. 188 // Look at code in DetectContextFromRemoteURL for more information. 189 createProgressReader := func(in io.ReadCloser) io.ReadCloser { 190 progressOutput := sf.NewProgressOutput(output, true) 191 if buildOptions.SuppressOutput { 192 progressOutput = sf.NewProgressOutput(notVerboseBuffer, true) 193 } 194 return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL) 195 } 196 197 out := io.Writer(output) 198 if buildOptions.SuppressOutput { 199 out = notVerboseBuffer 200 } 201 out = &syncWriter{w: out} 202 stdout := &streamformatter.StdoutFormatter{Writer: out, StreamFormatter: sf} 203 stderr := &streamformatter.StderrFormatter{Writer: out, StreamFormatter: sf} 204 205 pg := backend.ProgressWriter{ 206 Output: out, 207 StdoutFormatter: stdout, 208 StderrFormatter: stderr, 209 ProgressReaderFunc: createProgressReader, 210 } 211 212 imgID, err := br.backend.BuildFromContext(ctx, r.Body, remoteURL, buildOptions, pg) 213 if err != nil { 214 return errf(err) 215 } 216 217 // Everything worked so if -q was provided the output from the daemon 218 // should be just the image ID and we'll print that to stdout. 219 if buildOptions.SuppressOutput { 220 stdout := &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} 221 fmt.Fprintf(stdout, "%s\n", string(imgID)) 222 } 223 224 return nil 225 }