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