github.com/goern/docker@v1.9.0-rc1/api/server/router/local/image.go (about) 1 package local 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/server/httputils" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/builder" 16 "github.com/docker/docker/builder/dockerfile" 17 "github.com/docker/docker/cliconfig" 18 "github.com/docker/docker/daemon/daemonbuilder" 19 "github.com/docker/docker/graph" 20 "github.com/docker/docker/graph/tags" 21 "github.com/docker/docker/pkg/archive" 22 "github.com/docker/docker/pkg/chrootarchive" 23 "github.com/docker/docker/pkg/ioutils" 24 "github.com/docker/docker/pkg/parsers" 25 "github.com/docker/docker/pkg/progressreader" 26 "github.com/docker/docker/pkg/streamformatter" 27 "github.com/docker/docker/pkg/ulimit" 28 "github.com/docker/docker/registry" 29 "github.com/docker/docker/runconfig" 30 "github.com/docker/docker/utils" 31 "golang.org/x/net/context" 32 ) 33 34 func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 35 if err := httputils.ParseForm(r); err != nil { 36 return err 37 } 38 39 if err := httputils.CheckForJSON(r); err != nil { 40 return err 41 } 42 43 cname := r.Form.Get("container") 44 45 pause := httputils.BoolValue(r, "pause") 46 version := httputils.VersionFromContext(ctx) 47 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 48 pause = true 49 } 50 51 c, _, err := runconfig.DecodeContainerConfig(r.Body) 52 if err != nil && err != io.EOF { //Do not fail if body is empty. 53 return err 54 } 55 56 commitCfg := &dockerfile.CommitConfig{ 57 Pause: pause, 58 Repo: r.Form.Get("repo"), 59 Tag: r.Form.Get("tag"), 60 Author: r.Form.Get("author"), 61 Comment: r.Form.Get("comment"), 62 Changes: r.Form["changes"], 63 Config: c, 64 } 65 66 container, err := s.daemon.Get(cname) 67 if err != nil { 68 return err 69 } 70 71 imgID, err := dockerfile.Commit(container, s.daemon, commitCfg) 72 if err != nil { 73 return err 74 } 75 76 return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ 77 ID: string(imgID), 78 }) 79 } 80 81 // Creates an image from Pull or from Import 82 func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 83 if err := httputils.ParseForm(r); err != nil { 84 return err 85 } 86 87 var ( 88 image = r.Form.Get("fromImage") 89 repo = r.Form.Get("repo") 90 tag = r.Form.Get("tag") 91 message = r.Form.Get("message") 92 ) 93 authEncoded := r.Header.Get("X-Registry-Auth") 94 authConfig := &cliconfig.AuthConfig{} 95 if authEncoded != "" { 96 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 97 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 98 // for a pull it is not an error if no auth was given 99 // to increase compatibility with the existing api it is defaulting to be empty 100 authConfig = &cliconfig.AuthConfig{} 101 } 102 } 103 104 var ( 105 err error 106 output = ioutils.NewWriteFlusher(w) 107 ) 108 109 w.Header().Set("Content-Type", "application/json") 110 111 if image != "" { //pull 112 if tag == "" { 113 image, tag = parsers.ParseRepositoryTag(image) 114 } 115 metaHeaders := map[string][]string{} 116 for k, v := range r.Header { 117 if strings.HasPrefix(k, "X-Meta-") { 118 metaHeaders[k] = v 119 } 120 } 121 122 imagePullConfig := &graph.ImagePullConfig{ 123 MetaHeaders: metaHeaders, 124 AuthConfig: authConfig, 125 OutStream: output, 126 } 127 128 err = s.daemon.PullImage(image, tag, imagePullConfig) 129 } else { //import 130 if tag == "" { 131 repo, tag = parsers.ParseRepositoryTag(repo) 132 } 133 134 src := r.Form.Get("fromSrc") 135 136 // 'err' MUST NOT be defined within this block, we need any error 137 // generated from the download to be available to the output 138 // stream processing below 139 var newConfig *runconfig.Config 140 newConfig, err = dockerfile.BuildFromConfig(&runconfig.Config{}, r.Form["changes"]) 141 if err != nil { 142 return err 143 } 144 145 err = s.daemon.ImportImage(src, repo, tag, message, r.Body, output, newConfig) 146 } 147 if err != nil { 148 if !output.Flushed() { 149 return err 150 } 151 sf := streamformatter.NewJSONStreamFormatter() 152 output.Write(sf.FormatError(err)) 153 } 154 155 return nil 156 } 157 158 func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 159 if vars == nil { 160 return fmt.Errorf("Missing parameter") 161 } 162 163 metaHeaders := map[string][]string{} 164 for k, v := range r.Header { 165 if strings.HasPrefix(k, "X-Meta-") { 166 metaHeaders[k] = v 167 } 168 } 169 if err := httputils.ParseForm(r); err != nil { 170 return err 171 } 172 authConfig := &cliconfig.AuthConfig{} 173 174 authEncoded := r.Header.Get("X-Registry-Auth") 175 if authEncoded != "" { 176 // the new format is to handle the authConfig as a header 177 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 178 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 179 // to increase compatibility to existing api it is defaulting to be empty 180 authConfig = &cliconfig.AuthConfig{} 181 } 182 } else { 183 // the old format is supported for compatibility if there was no authConfig header 184 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 185 return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) 186 } 187 } 188 189 name := vars["name"] 190 output := ioutils.NewWriteFlusher(w) 191 imagePushConfig := &graph.ImagePushConfig{ 192 MetaHeaders: metaHeaders, 193 AuthConfig: authConfig, 194 Tag: r.Form.Get("tag"), 195 OutStream: output, 196 } 197 198 w.Header().Set("Content-Type", "application/json") 199 200 if err := s.daemon.PushImage(name, imagePushConfig); err != nil { 201 if !output.Flushed() { 202 return err 203 } 204 sf := streamformatter.NewJSONStreamFormatter() 205 output.Write(sf.FormatError(err)) 206 } 207 return nil 208 } 209 210 func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 211 if vars == nil { 212 return fmt.Errorf("Missing parameter") 213 } 214 if err := httputils.ParseForm(r); err != nil { 215 return err 216 } 217 218 w.Header().Set("Content-Type", "application/x-tar") 219 220 output := ioutils.NewWriteFlusher(w) 221 var names []string 222 if name, ok := vars["name"]; ok { 223 names = []string{name} 224 } else { 225 names = r.Form["names"] 226 } 227 228 if err := s.daemon.ExportImage(names, output); err != nil { 229 if !output.Flushed() { 230 return err 231 } 232 sf := streamformatter.NewJSONStreamFormatter() 233 output.Write(sf.FormatError(err)) 234 } 235 return nil 236 } 237 238 func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 239 return s.daemon.LoadImage(r.Body, w) 240 } 241 242 func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 243 if err := httputils.ParseForm(r); err != nil { 244 return err 245 } 246 if vars == nil { 247 return fmt.Errorf("Missing parameter") 248 } 249 250 name := vars["name"] 251 252 if name == "" { 253 return fmt.Errorf("image name cannot be blank") 254 } 255 256 force := httputils.BoolValue(r, "force") 257 prune := !httputils.BoolValue(r, "noprune") 258 259 list, err := s.daemon.ImageDelete(name, force, prune) 260 if err != nil { 261 return err 262 } 263 264 return httputils.WriteJSON(w, http.StatusOK, list) 265 } 266 267 func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 268 if vars == nil { 269 return fmt.Errorf("Missing parameter") 270 } 271 272 imageInspect, err := s.daemon.LookupImage(vars["name"]) 273 if err != nil { 274 return err 275 } 276 277 return httputils.WriteJSON(w, http.StatusOK, imageInspect) 278 } 279 280 func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 281 var ( 282 authConfigs = map[string]cliconfig.AuthConfig{} 283 authConfigsEncoded = r.Header.Get("X-Registry-Config") 284 buildConfig = &dockerfile.Config{} 285 ) 286 287 if authConfigsEncoded != "" { 288 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 289 if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { 290 // for a pull it is not an error if no auth was given 291 // to increase compatibility with the existing api it is defaulting 292 // to be empty. 293 } 294 } 295 296 w.Header().Set("Content-Type", "application/json") 297 298 version := httputils.VersionFromContext(ctx) 299 output := ioutils.NewWriteFlusher(w) 300 sf := streamformatter.NewJSONStreamFormatter() 301 errf := func(err error) error { 302 // Do not write the error in the http output if it's still empty. 303 // This prevents from writing a 200(OK) when there is an interal error. 304 if !output.Flushed() { 305 return err 306 } 307 _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err)))) 308 if err != nil { 309 logrus.Warnf("could not write error response: %v", err) 310 } 311 return nil 312 } 313 314 if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { 315 buildConfig.Remove = true 316 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 317 buildConfig.Remove = true 318 } else { 319 buildConfig.Remove = httputils.BoolValue(r, "rm") 320 } 321 if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { 322 buildConfig.Pull = true 323 } 324 325 repoName, tag := parsers.ParseRepositoryTag(r.FormValue("t")) 326 if repoName != "" { 327 if err := registry.ValidateRepositoryName(repoName); err != nil { 328 return errf(err) 329 } 330 if len(tag) > 0 { 331 if err := tags.ValidateTagName(tag); err != nil { 332 return errf(err) 333 } 334 } 335 } 336 337 buildConfig.DockerfileName = r.FormValue("dockerfile") 338 buildConfig.Verbose = !httputils.BoolValue(r, "q") 339 buildConfig.UseCache = !httputils.BoolValue(r, "nocache") 340 buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm") 341 buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") 342 buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory") 343 buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") 344 buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") 345 buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") 346 buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") 347 buildConfig.CPUSetMems = r.FormValue("cpusetmems") 348 buildConfig.CgroupParent = r.FormValue("cgroupparent") 349 350 var buildUlimits = []*ulimit.Ulimit{} 351 ulimitsJSON := r.FormValue("ulimits") 352 if ulimitsJSON != "" { 353 if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { 354 return errf(err) 355 } 356 buildConfig.Ulimits = buildUlimits 357 } 358 359 var buildArgs = map[string]string{} 360 buildArgsJSON := r.FormValue("buildargs") 361 if buildArgsJSON != "" { 362 if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil { 363 return errf(err) 364 } 365 buildConfig.BuildArgs = buildArgs 366 } 367 368 remoteURL := r.FormValue("remote") 369 370 // Currently, only used if context is from a remote url. 371 // The field `In` is set by DetectContextFromRemoteURL. 372 // Look at code in DetectContextFromRemoteURL for more information. 373 pReader := &progressreader.Config{ 374 // TODO: make progressreader streamformatter-agnostic 375 Out: output, 376 Formatter: sf, 377 Size: r.ContentLength, 378 NewLines: true, 379 ID: "Downloading context", 380 Action: remoteURL, 381 } 382 383 var ( 384 context builder.ModifiableContext 385 dockerfileName string 386 err error 387 ) 388 context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader) 389 if err != nil { 390 return errf(err) 391 } 392 defer func() { 393 if err := context.Close(); err != nil { 394 logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) 395 } 396 }() 397 398 uidMaps, gidMaps := s.daemon.GetUIDGIDMaps() 399 defaultArchiver := &archive.Archiver{ 400 Untar: chrootarchive.Untar, 401 UIDMaps: uidMaps, 402 GIDMaps: gidMaps, 403 } 404 docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver} 405 406 b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil) 407 if err != nil { 408 return errf(err) 409 } 410 b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} 411 b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf} 412 413 if closeNotifier, ok := w.(http.CloseNotifier); ok { 414 finished := make(chan struct{}) 415 defer close(finished) 416 go func() { 417 select { 418 case <-finished: 419 case <-closeNotifier.CloseNotify(): 420 logrus.Infof("Client disconnected, cancelling job: build") 421 b.Cancel() 422 } 423 }() 424 } 425 426 if len(dockerfileName) > 0 { 427 b.DockerfileName = dockerfileName 428 } 429 430 imgID, err := b.Build() 431 if err != nil { 432 return errf(err) 433 } 434 435 if repoName != "" { 436 if err := s.daemon.TagImage(repoName, tag, string(imgID), true); err != nil { 437 return errf(err) 438 } 439 } 440 441 return nil 442 } 443 444 func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 445 if err := httputils.ParseForm(r); err != nil { 446 return err 447 } 448 449 // FIXME: The filter parameter could just be a match filter 450 images, err := s.daemon.ListImages(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all")) 451 if err != nil { 452 return err 453 } 454 455 return httputils.WriteJSON(w, http.StatusOK, images) 456 } 457 458 func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 459 if vars == nil { 460 return fmt.Errorf("Missing parameter") 461 } 462 463 name := vars["name"] 464 history, err := s.daemon.ImageHistory(name) 465 if err != nil { 466 return err 467 } 468 469 return httputils.WriteJSON(w, http.StatusOK, history) 470 } 471 472 func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 473 if err := httputils.ParseForm(r); err != nil { 474 return err 475 } 476 if vars == nil { 477 return fmt.Errorf("Missing parameter") 478 } 479 480 repo := r.Form.Get("repo") 481 tag := r.Form.Get("tag") 482 name := vars["name"] 483 force := httputils.BoolValue(r, "force") 484 if err := s.daemon.TagImage(repo, tag, name, force); err != nil { 485 return err 486 } 487 s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "") 488 w.WriteHeader(http.StatusCreated) 489 return nil 490 } 491 492 func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 493 if err := httputils.ParseForm(r); err != nil { 494 return err 495 } 496 var ( 497 config *cliconfig.AuthConfig 498 authEncoded = r.Header.Get("X-Registry-Auth") 499 headers = map[string][]string{} 500 ) 501 502 if authEncoded != "" { 503 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 504 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 505 // for a search it is not an error if no auth was given 506 // to increase compatibility with the existing api it is defaulting to be empty 507 config = &cliconfig.AuthConfig{} 508 } 509 } 510 for k, v := range r.Header { 511 if strings.HasPrefix(k, "X-Meta-") { 512 headers[k] = v 513 } 514 } 515 query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers) 516 if err != nil { 517 return err 518 } 519 return httputils.WriteJSON(w, http.StatusOK, query.Results) 520 }