github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/libpod/images.go (about) 1 package libpod 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "strconv" 11 "strings" 12 13 "github.com/containers/buildah" 14 "github.com/containers/common/libimage" 15 "github.com/containers/image/v5/manifest" 16 "github.com/containers/image/v5/types" 17 "github.com/hanks177/podman/v4/libpod" 18 "github.com/hanks177/podman/v4/libpod/define" 19 "github.com/hanks177/podman/v4/pkg/api/handlers" 20 "github.com/hanks177/podman/v4/pkg/api/handlers/utils" 21 api "github.com/hanks177/podman/v4/pkg/api/types" 22 "github.com/hanks177/podman/v4/pkg/auth" 23 "github.com/hanks177/podman/v4/pkg/domain/entities" 24 "github.com/hanks177/podman/v4/pkg/domain/infra/abi" 25 "github.com/hanks177/podman/v4/pkg/errorhandling" 26 "github.com/hanks177/podman/v4/pkg/util" 27 utils2 "github.com/hanks177/podman/v4/utils" 28 "github.com/containers/storage" 29 "github.com/gorilla/schema" 30 "github.com/pkg/errors" 31 ) 32 33 // Commit 34 // author string 35 // "container" 36 // repo string 37 // tag string 38 // message 39 // pause bool 40 // changes []string 41 42 // create 43 44 func ImageExists(w http.ResponseWriter, r *http.Request) { 45 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 46 name := utils.GetName(r) 47 48 ir := abi.ImageEngine{Libpod: runtime} 49 report, err := ir.Exists(r.Context(), name) 50 if err != nil { 51 utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) 52 return 53 } 54 if !report.Value { 55 utils.Error(w, http.StatusNotFound, errors.Errorf("failed to find image %s", name)) 56 return 57 } 58 utils.WriteResponse(w, http.StatusNoContent, "") 59 } 60 61 func ImageTree(w http.ResponseWriter, r *http.Request) { 62 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 63 name := utils.GetName(r) 64 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 65 query := struct { 66 WhatRequires bool `schema:"whatrequires"` 67 }{ 68 WhatRequires: false, 69 } 70 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 71 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 72 return 73 } 74 ir := abi.ImageEngine{Libpod: runtime} 75 options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires} 76 report, err := ir.Tree(r.Context(), name, options) 77 if err != nil { 78 if errors.Cause(err) == storage.ErrImageUnknown { 79 utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) 80 return 81 } 82 utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name)) 83 return 84 } 85 utils.WriteResponse(w, http.StatusOK, report) 86 } 87 88 func GetImage(w http.ResponseWriter, r *http.Request) { 89 name := utils.GetName(r) 90 newImage, err := utils.GetImage(r, name) 91 if err != nil { 92 utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) 93 return 94 } 95 options := &libimage.InspectOptions{WithParent: true, WithSize: true} 96 inspect, err := newImage.Inspect(r.Context(), options) 97 if err != nil { 98 utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) 99 return 100 } 101 utils.WriteResponse(w, http.StatusOK, inspect) 102 } 103 104 func PruneImages(w http.ResponseWriter, r *http.Request) { 105 var err error 106 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 107 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 108 query := struct { 109 All bool `schema:"all"` 110 External bool `schema:"external"` 111 }{ 112 // override any golang type defaults 113 } 114 115 filterMap, err := util.PrepareFilters(r) 116 if err != nil { 117 utils.Error(w, http.StatusInternalServerError, 118 errors. 119 Wrapf(err, "failed to decode filter parameters for %s", r.URL.String())) 120 return 121 } 122 123 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 124 utils.Error(w, http.StatusInternalServerError, 125 errors. 126 Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 127 return 128 } 129 130 libpodFilters := []string{} 131 if _, found := r.URL.Query()["filters"]; found { 132 dangling := (*filterMap)["all"] 133 if len(dangling) > 0 { 134 query.All, err = strconv.ParseBool((*filterMap)["all"][0]) 135 if err != nil { 136 utils.InternalServerError(w, err) 137 return 138 } 139 } 140 // dangling is special and not implemented in the libpod side of things 141 delete(*filterMap, "dangling") 142 for k, v := range *filterMap { 143 libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) 144 } 145 } 146 147 imageEngine := abi.ImageEngine{Libpod: runtime} 148 149 pruneOptions := entities.ImagePruneOptions{ 150 All: query.All, 151 External: query.External, 152 Filter: libpodFilters, 153 } 154 imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions) 155 if err != nil { 156 utils.Error(w, http.StatusInternalServerError, err) 157 return 158 } 159 utils.WriteResponse(w, http.StatusOK, imagePruneReports) 160 } 161 162 func ExportImage(w http.ResponseWriter, r *http.Request) { 163 var output string 164 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 165 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 166 query := struct { 167 Compress bool `schema:"compress"` 168 Format string `schema:"format"` 169 }{ 170 Format: define.OCIArchive, 171 } 172 173 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 174 utils.Error(w, http.StatusBadRequest, 175 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 176 return 177 } 178 name := utils.GetName(r) 179 180 if _, _, err := runtime.LibimageRuntime().LookupImage(name, nil); err != nil { 181 utils.ImageNotFound(w, name, err) 182 return 183 } 184 185 switch query.Format { 186 case define.OCIArchive, define.V2s2Archive: 187 tmpfile, err := ioutil.TempFile("", "api.tar") 188 if err != nil { 189 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 190 return 191 } 192 output = tmpfile.Name() 193 if err := tmpfile.Close(); err != nil { 194 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 195 return 196 } 197 case define.OCIManifestDir, define.V2s2ManifestDir: 198 tmpdir, err := ioutil.TempDir("", "save") 199 if err != nil { 200 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) 201 return 202 } 203 output = tmpdir 204 default: 205 utils.Error(w, http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) 206 return 207 } 208 209 imageEngine := abi.ImageEngine{Libpod: runtime} 210 211 saveOptions := entities.ImageSaveOptions{ 212 Compress: query.Compress, 213 Format: query.Format, 214 Output: output, 215 } 216 if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { 217 utils.Error(w, http.StatusBadRequest, err) 218 return 219 } 220 defer os.RemoveAll(output) 221 // if dir format, we need to tar it 222 if query.Format == "oci-dir" || query.Format == "docker-dir" { 223 rdr, err := utils2.Tar(output) 224 if err != nil { 225 utils.InternalServerError(w, err) 226 return 227 } 228 defer rdr.Close() 229 utils.WriteResponse(w, http.StatusOK, rdr) 230 return 231 } 232 rdr, err := os.Open(output) 233 if err != nil { 234 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 235 return 236 } 237 defer rdr.Close() 238 utils.WriteResponse(w, http.StatusOK, rdr) 239 } 240 241 func ExportImages(w http.ResponseWriter, r *http.Request) { 242 var output string 243 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 244 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 245 query := struct { 246 Compress bool `schema:"compress"` 247 Format string `schema:"format"` 248 OciAcceptUncompressedLayers bool `schema:"ociAcceptUncompressedLayers"` 249 References []string `schema:"references"` 250 }{ 251 Format: define.OCIArchive, 252 } 253 254 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 255 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 256 return 257 } 258 259 // References are mandatory! 260 if len(query.References) == 0 { 261 utils.Error(w, http.StatusBadRequest, errors.New("No references")) 262 return 263 } 264 265 // Format is mandatory! Currently, we only support multi-image docker 266 // archives. 267 if len(query.References) > 1 && query.Format != define.V2s2Archive { 268 utils.Error(w, http.StatusInternalServerError, errors.Errorf("multi-image archives must use format of %s", define.V2s2Archive)) 269 return 270 } 271 272 // if format is dir, server will save to an archive 273 // the client will unArchive after receive the archive file 274 // so must convert is at here 275 switch query.Format { 276 case define.OCIManifestDir: 277 query.Format = define.OCIArchive 278 case define.V2s2ManifestDir: 279 query.Format = define.V2s2Archive 280 } 281 282 switch query.Format { 283 case define.V2s2Archive, define.OCIArchive: 284 tmpfile, err := ioutil.TempFile("", "api.tar") 285 if err != nil { 286 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 287 return 288 } 289 output = tmpfile.Name() 290 if err := tmpfile.Close(); err != nil { 291 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 292 return 293 } 294 case define.OCIManifestDir, define.V2s2ManifestDir: 295 tmpdir, err := ioutil.TempDir("", "save") 296 if err != nil { 297 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tmpdir")) 298 return 299 } 300 output = tmpdir 301 default: 302 utils.Error(w, http.StatusInternalServerError, errors.Errorf("unsupported format %q", query.Format)) 303 return 304 } 305 defer os.RemoveAll(output) 306 307 // Use the ABI image engine to share as much code as possible. 308 opts := entities.ImageSaveOptions{ 309 Compress: query.Compress, 310 Format: query.Format, 311 MultiImageArchive: len(query.References) > 1, 312 OciAcceptUncompressedLayers: query.OciAcceptUncompressedLayers, 313 Output: output, 314 } 315 316 imageEngine := abi.ImageEngine{Libpod: runtime} 317 if err := imageEngine.Save(r.Context(), query.References[0], query.References[1:], opts); err != nil { 318 utils.Error(w, http.StatusBadRequest, err) 319 return 320 } 321 322 rdr, err := os.Open(output) 323 if err != nil { 324 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 325 return 326 } 327 defer rdr.Close() 328 utils.WriteResponse(w, http.StatusOK, rdr) 329 } 330 331 func ImagesLoad(w http.ResponseWriter, r *http.Request) { 332 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 333 334 tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar") 335 if err != nil { 336 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 337 return 338 } 339 defer os.Remove(tmpfile.Name()) 340 341 _, err = io.Copy(tmpfile, r.Body) 342 tmpfile.Close() 343 344 if err != nil && err != io.EOF { 345 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) 346 return 347 } 348 349 imageEngine := abi.ImageEngine{Libpod: runtime} 350 351 loadOptions := entities.ImageLoadOptions{Input: tmpfile.Name()} 352 loadReport, err := imageEngine.Load(r.Context(), loadOptions) 353 if err != nil { 354 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) 355 return 356 } 357 utils.WriteResponse(w, http.StatusOK, loadReport) 358 } 359 360 func ImagesImport(w http.ResponseWriter, r *http.Request) { 361 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 362 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 363 query := struct { 364 Changes []string `schema:"changes"` 365 Message string `schema:"message"` 366 Reference string `schema:"reference"` 367 URL string `schema:"URL"` 368 OS string `schema:"OS"` 369 Architecture string `schema:"Architecture"` 370 Variant string `schema:"Variant"` 371 }{ 372 // Add defaults here once needed. 373 } 374 375 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 376 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 377 return 378 } 379 380 // Check if we need to load the image from a URL or from the request's body. 381 source := query.URL 382 if len(query.URL) == 0 { 383 tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar") 384 if err != nil { 385 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 386 return 387 } 388 defer os.Remove(tmpfile.Name()) 389 defer tmpfile.Close() 390 391 if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { 392 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) 393 return 394 } 395 396 tmpfile.Close() 397 source = tmpfile.Name() 398 } 399 400 imageEngine := abi.ImageEngine{Libpod: runtime} 401 importOptions := entities.ImageImportOptions{ 402 Changes: query.Changes, 403 Message: query.Message, 404 Reference: query.Reference, 405 OS: query.OS, 406 Architecture: query.Architecture, 407 Variant: query.Variant, 408 Source: source, 409 } 410 report, err := imageEngine.Import(r.Context(), importOptions) 411 if err != nil { 412 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) 413 return 414 } 415 416 utils.WriteResponse(w, http.StatusOK, report) 417 } 418 419 // PushImage is the handler for the compat http endpoint for pushing images. 420 func PushImage(w http.ResponseWriter, r *http.Request) { 421 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 422 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 423 424 query := struct { 425 Destination string `schema:"destination"` 426 TLSVerify bool `schema:"tlsVerify"` 427 Format string `schema:"format"` 428 All bool `schema:"all"` 429 }{ 430 // This is where you can override the golang default value for one of fields 431 } 432 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 433 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 434 return 435 } 436 437 source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path 438 if _, err := utils.ParseStorageReference(source); err != nil { 439 utils.Error(w, http.StatusBadRequest, err) 440 return 441 } 442 443 destination := query.Destination 444 if destination == "" { 445 destination = source 446 } 447 448 if err := utils.IsRegistryReference(destination); err != nil { 449 utils.Error(w, http.StatusBadRequest, err) 450 return 451 } 452 453 authconf, authfile, err := auth.GetCredentials(r) 454 if err != nil { 455 utils.Error(w, http.StatusBadRequest, err) 456 return 457 } 458 defer auth.RemoveAuthfile(authfile) 459 var username, password string 460 if authconf != nil { 461 username = authconf.Username 462 password = authconf.Password 463 } 464 options := entities.ImagePushOptions{ 465 Authfile: authfile, 466 Username: username, 467 Password: password, 468 Format: query.Format, 469 All: query.All, 470 Quiet: true, 471 } 472 if _, found := r.URL.Query()["tlsVerify"]; found { 473 options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 474 } 475 476 imageEngine := abi.ImageEngine{Libpod: runtime} 477 if err := imageEngine.Push(context.Background(), source, destination, options); err != nil { 478 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination)) 479 return 480 } 481 482 utils.WriteResponse(w, http.StatusOK, "") 483 } 484 485 func CommitContainer(w http.ResponseWriter, r *http.Request) { 486 var ( 487 destImage string 488 mimeType string 489 ) 490 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 491 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 492 493 query := struct { 494 Author string `schema:"author"` 495 Changes []string `schema:"changes"` 496 Comment string `schema:"comment"` 497 Container string `schema:"container"` 498 Format string `schema:"format"` 499 Pause bool `schema:"pause"` 500 Squash bool `schema:"squash"` 501 Repo string `schema:"repo"` 502 Tag string `schema:"tag"` 503 }{ 504 Format: "oci", 505 } 506 507 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 508 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 509 return 510 } 511 rtc, err := runtime.GetConfig() 512 if err != nil { 513 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) 514 return 515 } 516 sc := runtime.SystemContext() 517 tag := "latest" 518 options := libpod.ContainerCommitOptions{ 519 Pause: true, 520 } 521 switch query.Format { 522 case "oci": 523 mimeType = buildah.OCIv1ImageManifest 524 if len(query.Comment) > 0 { 525 utils.InternalServerError(w, errors.New("messages are only compatible with the docker image format (-f docker)")) 526 return 527 } 528 case "docker": 529 mimeType = manifest.DockerV2Schema2MediaType 530 default: 531 utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format)) 532 return 533 } 534 options.CommitOptions = buildah.CommitOptions{ 535 SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, 536 ReportWriter: os.Stderr, 537 SystemContext: sc, 538 PreferredManifestType: mimeType, 539 } 540 541 if len(query.Tag) > 0 { 542 tag = query.Tag 543 } 544 options.Message = query.Comment 545 options.Author = query.Author 546 options.Pause = query.Pause 547 options.Squash = query.Squash 548 options.Changes = query.Changes 549 ctr, err := runtime.LookupContainer(query.Container) 550 if err != nil { 551 utils.Error(w, http.StatusNotFound, err) 552 return 553 } 554 555 if len(query.Repo) > 0 { 556 destImage = fmt.Sprintf("%s:%s", query.Repo, tag) 557 } 558 commitImage, err := ctr.Commit(r.Context(), destImage, options) 559 if err != nil && !strings.Contains(err.Error(), "is not running") { 560 utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) 561 return 562 } 563 utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: commitImage.ID()}) // nolint 564 } 565 566 func UntagImage(w http.ResponseWriter, r *http.Request) { 567 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 568 569 tags := []string{} // Note: if empty, all tags will be removed from the image. 570 repo := r.Form.Get("repo") 571 tag := r.Form.Get("tag") 572 573 // Do the parameter dance. 574 switch { 575 // If tag is set, repo must be as well. 576 case len(repo) == 0 && len(tag) > 0: 577 utils.Error(w, http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) 578 return 579 580 case len(repo) == 0: 581 break 582 583 // If repo is specified, we need to add that to the tags. 584 default: 585 if len(tag) == 0 { 586 // Normalize tag to "latest" if empty. 587 tag = "latest" 588 } 589 tags = append(tags, fmt.Sprintf("%s:%s", repo, tag)) 590 } 591 592 // Now use the ABI implementation to prevent us from having duplicate 593 // code. 594 opts := entities.ImageUntagOptions{} 595 imageEngine := abi.ImageEngine{Libpod: runtime} 596 597 name := utils.GetName(r) 598 if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil { 599 if errors.Cause(err) == storage.ErrImageUnknown { 600 utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) 601 } else { 602 utils.Error(w, http.StatusInternalServerError, err) 603 } 604 return 605 } 606 utils.WriteResponse(w, http.StatusCreated, "") 607 } 608 609 // ImagesBatchRemove is the endpoint for batch image removal. 610 func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { 611 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 612 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 613 query := struct { 614 All bool `schema:"all"` 615 Force bool `schema:"force"` 616 Ignore bool `schema:"ignore"` 617 Images []string `schema:"images"` 618 }{} 619 620 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 621 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 622 return 623 } 624 625 opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore} 626 imageEngine := abi.ImageEngine{Libpod: runtime} 627 rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts) 628 strErrs := errorhandling.ErrorsToStrings(rmErrors) 629 report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs} 630 utils.WriteResponse(w, http.StatusOK, report) 631 } 632 633 // ImagesRemove is the endpoint for removing one image. 634 func ImagesRemove(w http.ResponseWriter, r *http.Request) { 635 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 636 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 637 query := struct { 638 Force bool `schema:"force"` 639 }{ 640 Force: false, 641 } 642 643 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 644 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 645 return 646 } 647 648 opts := entities.ImageRemoveOptions{Force: query.Force} 649 imageEngine := abi.ImageEngine{Libpod: runtime} 650 rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts) 651 652 // In contrast to batch-removal, where we're only setting the exit 653 // code, we need to have another closer look at the errors here and set 654 // the appropriate http status code. 655 656 switch rmReport.ExitCode { 657 case 0: 658 report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}} 659 utils.WriteResponse(w, http.StatusOK, report) 660 case 1: 661 // 404 - no such image 662 utils.Error(w, http.StatusNotFound, errorhandling.JoinErrors(rmErrors)) 663 case 2: 664 // 409 - conflict error (in use by containers) 665 utils.Error(w, http.StatusConflict, errorhandling.JoinErrors(rmErrors)) 666 default: 667 // 500 - internal error 668 utils.Error(w, http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors)) 669 } 670 }