github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/api/server/image.go (about) 1 package server 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/builder" 15 "github.com/docker/docker/cliconfig" 16 "github.com/docker/docker/context" 17 "github.com/docker/docker/graph" 18 "github.com/docker/docker/pkg/ioutils" 19 "github.com/docker/docker/pkg/parsers" 20 "github.com/docker/docker/pkg/streamformatter" 21 "github.com/docker/docker/pkg/ulimit" 22 "github.com/docker/docker/runconfig" 23 "github.com/docker/docker/utils" 24 ) 25 26 func (s *Server) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 27 if err := parseForm(r); err != nil { 28 return err 29 } 30 31 if err := checkForJSON(r); err != nil { 32 return err 33 } 34 35 cname := r.Form.Get("container") 36 37 pause := boolValue(r, "pause") 38 version := ctx.Version() 39 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 40 pause = true 41 } 42 43 c, _, err := runconfig.DecodeContainerConfig(r.Body) 44 if err != nil && err != io.EOF { //Do not fail if body is empty. 45 return err 46 } 47 48 commitCfg := &builder.CommitConfig{ 49 Pause: pause, 50 Repo: r.Form.Get("repo"), 51 Tag: r.Form.Get("tag"), 52 Author: r.Form.Get("author"), 53 Comment: r.Form.Get("comment"), 54 Changes: r.Form["changes"], 55 Config: c, 56 } 57 58 imgID, err := builder.Commit(cname, s.daemon, commitCfg) 59 if err != nil { 60 return err 61 } 62 63 return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ 64 ID: imgID, 65 }) 66 } 67 68 // Creates an image from Pull or from Import 69 func (s *Server) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 70 if err := parseForm(r); err != nil { 71 return err 72 } 73 74 var ( 75 image = r.Form.Get("fromImage") 76 repo = r.Form.Get("repo") 77 tag = r.Form.Get("tag") 78 message = r.Form.Get("message") 79 ) 80 authEncoded := r.Header.Get("X-Registry-Auth") 81 authConfig := &cliconfig.AuthConfig{} 82 if authEncoded != "" { 83 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 84 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 85 // for a pull it is not an error if no auth was given 86 // to increase compatibility with the existing api it is defaulting to be empty 87 authConfig = &cliconfig.AuthConfig{} 88 } 89 } 90 91 var ( 92 err error 93 output = ioutils.NewWriteFlusher(w) 94 ) 95 96 w.Header().Set("Content-Type", "application/json") 97 98 if image != "" { //pull 99 if tag == "" { 100 image, tag = parsers.ParseRepositoryTag(image) 101 } 102 metaHeaders := map[string][]string{} 103 for k, v := range r.Header { 104 if strings.HasPrefix(k, "X-Meta-") { 105 metaHeaders[k] = v 106 } 107 } 108 109 imagePullConfig := &graph.ImagePullConfig{ 110 MetaHeaders: metaHeaders, 111 AuthConfig: authConfig, 112 OutStream: output, 113 } 114 115 err = s.daemon.Repositories().Pull(image, tag, imagePullConfig) 116 } else { //import 117 if tag == "" { 118 repo, tag = parsers.ParseRepositoryTag(repo) 119 } 120 121 src := r.Form.Get("fromSrc") 122 123 // 'err' MUST NOT be defined within this block, we need any error 124 // generated from the download to be available to the output 125 // stream processing below 126 var newConfig *runconfig.Config 127 newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"]) 128 if err != nil { 129 return err 130 } 131 132 err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig) 133 } 134 if err != nil { 135 if !output.Flushed() { 136 return err 137 } 138 sf := streamformatter.NewJSONStreamFormatter() 139 output.Write(sf.FormatError(err)) 140 } 141 142 return nil 143 } 144 145 func (s *Server) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 146 if vars == nil { 147 return fmt.Errorf("Missing parameter") 148 } 149 150 metaHeaders := map[string][]string{} 151 for k, v := range r.Header { 152 if strings.HasPrefix(k, "X-Meta-") { 153 metaHeaders[k] = v 154 } 155 } 156 if err := parseForm(r); err != nil { 157 return err 158 } 159 authConfig := &cliconfig.AuthConfig{} 160 161 authEncoded := r.Header.Get("X-Registry-Auth") 162 if authEncoded != "" { 163 // the new format is to handle the authConfig as a header 164 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 165 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 166 // to increase compatibility to existing api it is defaulting to be empty 167 authConfig = &cliconfig.AuthConfig{} 168 } 169 } else { 170 // the old format is supported for compatibility if there was no authConfig header 171 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 172 return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) 173 } 174 } 175 176 name := vars["name"] 177 output := ioutils.NewWriteFlusher(w) 178 imagePushConfig := &graph.ImagePushConfig{ 179 MetaHeaders: metaHeaders, 180 AuthConfig: authConfig, 181 Tag: r.Form.Get("tag"), 182 OutStream: output, 183 } 184 185 w.Header().Set("Content-Type", "application/json") 186 187 if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { 188 if !output.Flushed() { 189 return err 190 } 191 sf := streamformatter.NewJSONStreamFormatter() 192 output.Write(sf.FormatError(err)) 193 } 194 return nil 195 } 196 197 func (s *Server) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 198 if vars == nil { 199 return fmt.Errorf("Missing parameter") 200 } 201 if err := parseForm(r); err != nil { 202 return err 203 } 204 205 w.Header().Set("Content-Type", "application/x-tar") 206 207 output := ioutils.NewWriteFlusher(w) 208 var names []string 209 if name, ok := vars["name"]; ok { 210 names = []string{name} 211 } else { 212 names = r.Form["names"] 213 } 214 215 if err := s.daemon.Repositories().ImageExport(names, output); err != nil { 216 if !output.Flushed() { 217 return err 218 } 219 sf := streamformatter.NewJSONStreamFormatter() 220 output.Write(sf.FormatError(err)) 221 } 222 return nil 223 } 224 225 func (s *Server) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 226 return s.daemon.Repositories().Load(r.Body, w) 227 } 228 229 func (s *Server) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 230 if err := parseForm(r); err != nil { 231 return err 232 } 233 if vars == nil { 234 return fmt.Errorf("Missing parameter") 235 } 236 237 name := vars["name"] 238 239 if name == "" { 240 return fmt.Errorf("image name cannot be blank") 241 } 242 243 force := boolValue(r, "force") 244 prune := !boolValue(r, "noprune") 245 246 list, err := s.daemon.ImageDelete(name, force, prune) 247 if err != nil { 248 return err 249 } 250 251 return writeJSON(w, http.StatusOK, list) 252 } 253 254 func (s *Server) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 255 if vars == nil { 256 return fmt.Errorf("Missing parameter") 257 } 258 259 imageInspect, err := s.daemon.Repositories().Lookup(vars["name"]) 260 if err != nil { 261 return err 262 } 263 264 return writeJSON(w, http.StatusOK, imageInspect) 265 } 266 267 func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 268 var ( 269 authConfigs = map[string]cliconfig.AuthConfig{} 270 authConfigsEncoded = r.Header.Get("X-Registry-Config") 271 buildConfig = builder.NewBuildConfig() 272 ) 273 274 if authConfigsEncoded != "" { 275 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 276 if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { 277 // for a pull it is not an error if no auth was given 278 // to increase compatibility with the existing api it is defaulting 279 // to be empty. 280 } 281 } 282 283 w.Header().Set("Content-Type", "application/json") 284 285 version := ctx.Version() 286 if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { 287 buildConfig.Remove = true 288 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 289 buildConfig.Remove = true 290 } else { 291 buildConfig.Remove = boolValue(r, "rm") 292 } 293 if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { 294 buildConfig.Pull = true 295 } 296 297 output := ioutils.NewWriteFlusher(w) 298 buildConfig.Stdout = output 299 buildConfig.Context = r.Body 300 301 buildConfig.RemoteURL = r.FormValue("remote") 302 buildConfig.DockerfileName = r.FormValue("dockerfile") 303 buildConfig.RepoName = r.FormValue("t") 304 buildConfig.SuppressOutput = boolValue(r, "q") 305 buildConfig.NoCache = boolValue(r, "nocache") 306 buildConfig.ForceRemove = boolValue(r, "forcerm") 307 buildConfig.AuthConfigs = authConfigs 308 buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") 309 buildConfig.Memory = int64ValueOrZero(r, "memory") 310 buildConfig.CPUShares = int64ValueOrZero(r, "cpushares") 311 buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod") 312 buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota") 313 buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") 314 buildConfig.CPUSetMems = r.FormValue("cpusetmems") 315 buildConfig.CgroupParent = r.FormValue("cgroupparent") 316 317 var buildUlimits = []*ulimit.Ulimit{} 318 ulimitsJSON := r.FormValue("ulimits") 319 if ulimitsJSON != "" { 320 if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { 321 return err 322 } 323 buildConfig.Ulimits = buildUlimits 324 } 325 326 var buildArgs = map[string]string{} 327 buildArgsJSON := r.FormValue("buildargs") 328 if buildArgsJSON != "" { 329 if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil { 330 return err 331 } 332 } 333 buildConfig.BuildArgs = buildArgs 334 335 // Job cancellation. Note: not all job types support this. 336 if closeNotifier, ok := w.(http.CloseNotifier); ok { 337 finished := make(chan struct{}) 338 defer close(finished) 339 go func() { 340 select { 341 case <-finished: 342 case <-closeNotifier.CloseNotify(): 343 logrus.Infof("Client disconnected, cancelling job: build") 344 buildConfig.Cancel() 345 } 346 }() 347 } 348 349 if err := builder.Build(s.daemon, buildConfig); err != nil { 350 // Do not write the error in the http output if it's still empty. 351 // This prevents from writing a 200(OK) when there is an interal error. 352 if !output.Flushed() { 353 return err 354 } 355 sf := streamformatter.NewJSONStreamFormatter() 356 w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err)))) 357 } 358 return nil 359 } 360 361 func (s *Server) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 362 if err := parseForm(r); err != nil { 363 return err 364 } 365 366 // FIXME: The filter parameter could just be a match filter 367 images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all")) 368 if err != nil { 369 return err 370 } 371 372 return writeJSON(w, http.StatusOK, images) 373 } 374 375 func (s *Server) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 376 if vars == nil { 377 return fmt.Errorf("Missing parameter") 378 } 379 380 name := vars["name"] 381 history, err := s.daemon.Repositories().History(name) 382 if err != nil { 383 return err 384 } 385 386 return writeJSON(w, http.StatusOK, history) 387 } 388 389 func (s *Server) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 390 if err := parseForm(r); err != nil { 391 return err 392 } 393 if vars == nil { 394 return fmt.Errorf("Missing parameter") 395 } 396 397 repo := r.Form.Get("repo") 398 tag := r.Form.Get("tag") 399 force := boolValue(r, "force") 400 name := vars["name"] 401 if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil { 402 return err 403 } 404 s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "") 405 w.WriteHeader(http.StatusCreated) 406 return nil 407 } 408 409 func (s *Server) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 410 if err := parseForm(r); err != nil { 411 return err 412 } 413 var ( 414 config *cliconfig.AuthConfig 415 authEncoded = r.Header.Get("X-Registry-Auth") 416 headers = map[string][]string{} 417 ) 418 419 if authEncoded != "" { 420 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 421 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 422 // for a search it is not an error if no auth was given 423 // to increase compatibility with the existing api it is defaulting to be empty 424 config = &cliconfig.AuthConfig{} 425 } 426 } 427 for k, v := range r.Header { 428 if strings.HasPrefix(k, "X-Meta-") { 429 headers[k] = v 430 } 431 } 432 query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers) 433 if err != nil { 434 return err 435 } 436 return writeJSON(w, http.StatusOK, query.Results) 437 }