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