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