github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/api/server/router/build/build_routes.go (about) 1 package build 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "strconv" 12 "strings" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/docker/api/server/httputils" 16 "github.com/docker/docker/builder" 17 "github.com/docker/docker/builder/dockerfile" 18 "github.com/docker/docker/daemon/daemonbuilder" 19 "github.com/docker/docker/pkg/archive" 20 "github.com/docker/docker/pkg/chrootarchive" 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/docker/reference" 25 "github.com/docker/docker/utils" 26 "github.com/docker/engine-api/types" 27 "github.com/docker/engine-api/types/container" 28 "github.com/docker/go-units" 29 "golang.org/x/net/context" 30 ) 31 32 // sanitizeRepoAndTags parses the raw "t" parameter received from the client 33 // to a slice of repoAndTag. 34 // It also validates each repoName and tag. 35 func sanitizeRepoAndTags(names []string) ([]reference.Named, error) { 36 var ( 37 repoAndTags []reference.Named 38 // This map is used for deduplicating the "-t" parameter. 39 uniqNames = make(map[string]struct{}) 40 ) 41 for _, repo := range names { 42 if repo == "" { 43 continue 44 } 45 46 ref, err := reference.ParseNamed(repo) 47 if err != nil { 48 return nil, err 49 } 50 51 ref = reference.WithDefaultTag(ref) 52 53 if _, isCanonical := ref.(reference.Canonical); isCanonical { 54 return nil, errors.New("build tag cannot contain a digest") 55 } 56 57 if _, isTagged := ref.(reference.NamedTagged); !isTagged { 58 ref, err = reference.WithTag(ref, reference.DefaultTag) 59 } 60 61 nameWithTag := ref.String() 62 63 if _, exists := uniqNames[nameWithTag]; !exists { 64 uniqNames[nameWithTag] = struct{}{} 65 repoAndTags = append(repoAndTags, ref) 66 } 67 } 68 return repoAndTags, nil 69 } 70 71 func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) { 72 version := httputils.VersionFromContext(ctx) 73 options := &types.ImageBuildOptions{} 74 if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { 75 options.Remove = true 76 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 77 options.Remove = true 78 } else { 79 options.Remove = httputils.BoolValue(r, "rm") 80 } 81 if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { 82 options.PullParent = true 83 } 84 85 options.Dockerfile = r.FormValue("dockerfile") 86 options.SuppressOutput = httputils.BoolValue(r, "q") 87 options.NoCache = httputils.BoolValue(r, "nocache") 88 options.ForceRemove = httputils.BoolValue(r, "forcerm") 89 options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") 90 options.Memory = httputils.Int64ValueOrZero(r, "memory") 91 options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") 92 options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") 93 options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") 94 options.CPUSetCPUs = r.FormValue("cpusetcpus") 95 options.CPUSetMems = r.FormValue("cpusetmems") 96 options.CgroupParent = r.FormValue("cgroupparent") 97 98 if r.Form.Get("shmsize") != "" { 99 shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) 100 if err != nil { 101 return nil, err 102 } 103 options.ShmSize = shmSize 104 } 105 106 if i := container.IsolationLevel(r.FormValue("isolation")); i != "" { 107 if !container.IsolationLevel.IsValid(i) { 108 return nil, fmt.Errorf("Unsupported isolation: %q", i) 109 } 110 options.IsolationLevel = i 111 } 112 113 var buildUlimits = []*units.Ulimit{} 114 ulimitsJSON := r.FormValue("ulimits") 115 if ulimitsJSON != "" { 116 if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { 117 return nil, err 118 } 119 options.Ulimits = buildUlimits 120 } 121 122 var buildArgs = map[string]string{} 123 buildArgsJSON := r.FormValue("buildargs") 124 if buildArgsJSON != "" { 125 if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil { 126 return nil, err 127 } 128 options.BuildArgs = buildArgs 129 } 130 return options, nil 131 } 132 133 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 134 var ( 135 authConfigs = map[string]types.AuthConfig{} 136 authConfigsEncoded = r.Header.Get("X-Registry-Config") 137 notVerboseBuffer = bytes.NewBuffer(nil) 138 ) 139 140 if authConfigsEncoded != "" { 141 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 142 if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { 143 // for a pull it is not an error if no auth was given 144 // to increase compatibility with the existing api it is defaulting 145 // to be empty. 146 } 147 } 148 149 w.Header().Set("Content-Type", "application/json") 150 151 output := ioutils.NewWriteFlusher(w) 152 defer output.Close() 153 sf := streamformatter.NewJSONStreamFormatter() 154 errf := func(err error) error { 155 if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 { 156 output.Write(notVerboseBuffer.Bytes()) 157 } 158 // Do not write the error in the http output if it's still empty. 159 // This prevents from writing a 200(OK) when there is an internal error. 160 if !output.Flushed() { 161 return err 162 } 163 _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err)))) 164 if err != nil { 165 logrus.Warnf("could not write error response: %v", err) 166 } 167 return nil 168 } 169 170 buildOptions, err := newImageBuildOptions(ctx, r) 171 if err != nil { 172 return errf(err) 173 } 174 175 repoAndTags, err := sanitizeRepoAndTags(r.Form["t"]) 176 if err != nil { 177 return errf(err) 178 } 179 180 remoteURL := r.FormValue("remote") 181 182 // Currently, only used if context is from a remote url. 183 // Look at code in DetectContextFromRemoteURL for more information. 184 createProgressReader := func(in io.ReadCloser) io.ReadCloser { 185 progressOutput := sf.NewProgressOutput(output, true) 186 if buildOptions.SuppressOutput { 187 progressOutput = sf.NewProgressOutput(notVerboseBuffer, true) 188 } 189 return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL) 190 } 191 192 var ( 193 context builder.ModifiableContext 194 dockerfileName string 195 ) 196 context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader) 197 if err != nil { 198 return errf(err) 199 } 200 defer func() { 201 if err := context.Close(); err != nil { 202 logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) 203 } 204 }() 205 if len(dockerfileName) > 0 { 206 buildOptions.Dockerfile = dockerfileName 207 } 208 209 uidMaps, gidMaps := br.backend.GetUIDGIDMaps() 210 defaultArchiver := &archive.Archiver{ 211 Untar: chrootarchive.Untar, 212 UIDMaps: uidMaps, 213 GIDMaps: gidMaps, 214 } 215 216 docker := &daemonbuilder.Docker{ 217 Daemon: br.backend, 218 OutOld: output, 219 AuthConfigs: authConfigs, 220 Archiver: defaultArchiver, 221 } 222 if buildOptions.SuppressOutput { 223 docker.OutOld = notVerboseBuffer 224 } 225 226 b, err := dockerfile.NewBuilder( 227 buildOptions, // result of newBuildConfig 228 docker, 229 builder.DockerIgnoreContext{ModifiableContext: context}, 230 nil) 231 if err != nil { 232 return errf(err) 233 } 234 b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} 235 b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf} 236 if buildOptions.SuppressOutput { 237 b.Stdout = &streamformatter.StdoutFormatter{Writer: notVerboseBuffer, StreamFormatter: sf} 238 b.Stderr = &streamformatter.StderrFormatter{Writer: notVerboseBuffer, StreamFormatter: sf} 239 } 240 241 if closeNotifier, ok := w.(http.CloseNotifier); ok { 242 finished := make(chan struct{}) 243 defer close(finished) 244 go func() { 245 select { 246 case <-finished: 247 case <-closeNotifier.CloseNotify(): 248 logrus.Infof("Client disconnected, cancelling job: build") 249 b.Cancel() 250 } 251 }() 252 } 253 254 imgID, err := b.Build() 255 if err != nil { 256 return errf(err) 257 } 258 259 for _, rt := range repoAndTags { 260 if err := br.backend.TagImage(rt, imgID); err != nil { 261 return errf(err) 262 } 263 } 264 265 // Everything worked so if -q was provided the output from the daemon 266 // should be just the image ID and we'll print that to stdout. 267 if buildOptions.SuppressOutput { 268 stdout := &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} 269 fmt.Fprintf(stdout, "%s\n", string(imgID)) 270 } 271 272 return nil 273 }