github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/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/image/v5/manifest" 15 "github.com/containers/image/v5/types" 16 "github.com/containers/podman/v2/libpod" 17 "github.com/containers/podman/v2/libpod/define" 18 "github.com/containers/podman/v2/libpod/image" 19 image2 "github.com/containers/podman/v2/libpod/image" 20 "github.com/containers/podman/v2/pkg/api/handlers" 21 "github.com/containers/podman/v2/pkg/api/handlers/utils" 22 "github.com/containers/podman/v2/pkg/auth" 23 "github.com/containers/podman/v2/pkg/domain/entities" 24 "github.com/containers/podman/v2/pkg/domain/infra/abi" 25 "github.com/containers/podman/v2/pkg/errorhandling" 26 utils2 "github.com/containers/podman/v2/utils" 27 "github.com/gorilla/schema" 28 "github.com/pkg/errors" 29 "github.com/sirupsen/logrus" 30 ) 31 32 // Commit 33 // author string 34 // "container" 35 // repo string 36 // tag string 37 // message 38 // pause bool 39 // changes []string 40 41 // create 42 43 func ImageExists(w http.ResponseWriter, r *http.Request) { 44 runtime := r.Context().Value("runtime").(*libpod.Runtime) 45 name := utils.GetName(r) 46 47 ir := abi.ImageEngine{Libpod: runtime} 48 report, err := ir.Exists(r.Context(), name) 49 if err != nil { 50 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) 51 return 52 } 53 if !report.Value { 54 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(nil, "failed to find image %s", name)) 55 return 56 } 57 utils.WriteResponse(w, http.StatusNoContent, "") 58 } 59 60 func ImageTree(w http.ResponseWriter, r *http.Request) { 61 runtime := r.Context().Value("runtime").(*libpod.Runtime) 62 name := utils.GetName(r) 63 decoder := r.Context().Value("decoder").(*schema.Decoder) 64 query := struct { 65 WhatRequires bool `schema:"whatrequires"` 66 }{ 67 WhatRequires: false, 68 } 69 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 70 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 71 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) == define.ErrNoSuchImage { 79 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) 80 return 81 } 82 utils.Error(w, "Server error", 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, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) 93 return 94 } 95 inspect, err := newImage.Inspect(r.Context()) 96 if err != nil { 97 utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) 98 return 99 } 100 utils.WriteResponse(w, http.StatusOK, inspect) 101 } 102 103 func GetImages(w http.ResponseWriter, r *http.Request) { 104 images, err := utils.GetImages(w, r) 105 if err != nil { 106 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) 107 return 108 } 109 var summaries = make([]*entities.ImageSummary, len(images)) 110 for j, img := range images { 111 is, err := handlers.ImageToImageSummary(img) 112 if err != nil { 113 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) 114 return 115 } 116 // libpod has additional fields that we need to populate. 117 is.ReadOnly = img.IsReadOnly() 118 summaries[j] = is 119 } 120 utils.WriteResponse(w, http.StatusOK, summaries) 121 } 122 123 func PruneImages(w http.ResponseWriter, r *http.Request) { 124 var ( 125 err error 126 ) 127 runtime := r.Context().Value("runtime").(*libpod.Runtime) 128 decoder := r.Context().Value("decoder").(*schema.Decoder) 129 query := struct { 130 All bool `schema:"all"` 131 Filters map[string][]string `schema:"filters"` 132 }{ 133 // override any golang type defaults 134 } 135 136 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 137 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 138 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 139 return 140 } 141 142 var libpodFilters = []string{} 143 if _, found := r.URL.Query()["filters"]; found { 144 dangling := query.Filters["all"] 145 if len(dangling) > 0 { 146 query.All, err = strconv.ParseBool(query.Filters["all"][0]) 147 if err != nil { 148 utils.InternalServerError(w, err) 149 return 150 } 151 } 152 // dangling is special and not implemented in the libpod side of things 153 delete(query.Filters, "dangling") 154 for k, v := range query.Filters { 155 libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) 156 } 157 } 158 159 cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) 160 if err != nil { 161 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) 162 return 163 } 164 utils.WriteResponse(w, http.StatusOK, cids) 165 } 166 167 func ExportImage(w http.ResponseWriter, r *http.Request) { 168 var ( 169 output string 170 ) 171 runtime := r.Context().Value("runtime").(*libpod.Runtime) 172 decoder := r.Context().Value("decoder").(*schema.Decoder) 173 query := struct { 174 Compress bool `schema:"compress"` 175 Format string `schema:"format"` 176 }{ 177 Format: define.OCIArchive, 178 } 179 180 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 181 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 182 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 183 return 184 } 185 name := utils.GetName(r) 186 newImage, err := runtime.ImageRuntime().NewFromLocal(name) 187 if err != nil { 188 utils.ImageNotFound(w, name, err) 189 return 190 } 191 switch query.Format { 192 case define.OCIArchive, define.V2s2Archive: 193 tmpfile, err := ioutil.TempFile("", "api.tar") 194 if err != nil { 195 utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 196 return 197 } 198 output = tmpfile.Name() 199 if err := tmpfile.Close(); err != nil { 200 utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 201 return 202 } 203 case define.OCIManifestDir, define.V2s2ManifestDir: 204 tmpdir, err := ioutil.TempDir("", "save") 205 if err != nil { 206 utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) 207 return 208 } 209 output = tmpdir 210 default: 211 utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) 212 return 213 } 214 if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress, true); err != nil { 215 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 216 return 217 } 218 defer os.RemoveAll(output) 219 // if dir format, we need to tar it 220 if query.Format == "oci-dir" || query.Format == "docker-dir" { 221 rdr, err := utils2.Tar(output) 222 if err != nil { 223 utils.InternalServerError(w, err) 224 return 225 } 226 defer rdr.Close() 227 utils.WriteResponse(w, http.StatusOK, rdr) 228 return 229 } 230 rdr, err := os.Open(output) 231 if err != nil { 232 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 233 return 234 } 235 defer rdr.Close() 236 utils.WriteResponse(w, http.StatusOK, rdr) 237 } 238 239 func ExportImages(w http.ResponseWriter, r *http.Request) { 240 var ( 241 output string 242 ) 243 runtime := r.Context().Value("runtime").(*libpod.Runtime) 244 decoder := r.Context().Value("decoder").(*schema.Decoder) 245 query := struct { 246 Compress bool `schema:"compress"` 247 Format string `schema:"format"` 248 References []string `schema:"references"` 249 }{ 250 Format: define.OCIArchive, 251 } 252 253 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 254 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 255 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.StatusText(http.StatusBadRequest), http.StatusBadRequest, 262 errors.New("No references")) 263 return 264 } 265 266 // Format is mandatory! Currently, we only support multi-image docker 267 // archives. 268 switch query.Format { 269 case define.V2s2Archive: 270 tmpfile, err := ioutil.TempFile("", "api.tar") 271 if err != nil { 272 utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 273 return 274 } 275 output = tmpfile.Name() 276 if err := tmpfile.Close(); err != nil { 277 utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 278 return 279 } 280 default: 281 utils.Error(w, "unsupported format", http.StatusInternalServerError, errors.Errorf("unsupported format %q", query.Format)) 282 return 283 } 284 defer os.RemoveAll(output) 285 286 // Use the ABI image engine to share as much code as possible. 287 opts := entities.ImageSaveOptions{ 288 Compress: query.Compress, 289 Format: query.Format, 290 MultiImageArchive: true, 291 Output: output, 292 RemoveSignatures: true, 293 } 294 295 imageEngine := abi.ImageEngine{Libpod: runtime} 296 if err := imageEngine.Save(r.Context(), query.References[0], query.References[1:], opts); err != nil { 297 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 298 return 299 } 300 301 rdr, err := os.Open(output) 302 if err != nil { 303 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 304 return 305 } 306 defer rdr.Close() 307 utils.WriteResponse(w, http.StatusOK, rdr) 308 } 309 310 func ImagesLoad(w http.ResponseWriter, r *http.Request) { 311 runtime := r.Context().Value("runtime").(*libpod.Runtime) 312 decoder := r.Context().Value("decoder").(*schema.Decoder) 313 query := struct { 314 Reference string `schema:"reference"` 315 }{ 316 // Add defaults here once needed. 317 } 318 319 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 320 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 321 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 322 return 323 } 324 325 tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar") 326 if err != nil { 327 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 328 return 329 } 330 defer os.Remove(tmpfile.Name()) 331 defer tmpfile.Close() 332 333 if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { 334 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) 335 return 336 } 337 338 tmpfile.Close() 339 loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "") 340 if err != nil { 341 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) 342 return 343 } 344 split := strings.Split(loadedImage, ",") 345 newImage, err := runtime.ImageRuntime().NewFromLocal(split[0]) 346 if err != nil { 347 utils.InternalServerError(w, err) 348 return 349 } 350 // TODO this should go into libpod proper at some point. 351 if len(query.Reference) > 0 { 352 if err := newImage.TagImage(query.Reference); err != nil { 353 utils.InternalServerError(w, err) 354 return 355 } 356 } 357 utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: split}) 358 } 359 360 func ImagesImport(w http.ResponseWriter, r *http.Request) { 361 runtime := r.Context().Value("runtime").(*libpod.Runtime) 362 decoder := r.Context().Value("decoder").(*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 }{ 369 // Add defaults here once needed. 370 } 371 372 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 373 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 374 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 375 return 376 } 377 378 // Check if we need to load the image from a URL or from the request's body. 379 source := query.URL 380 if len(query.URL) == 0 { 381 tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar") 382 if err != nil { 383 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 384 return 385 } 386 defer os.Remove(tmpfile.Name()) 387 defer tmpfile.Close() 388 389 if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { 390 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) 391 return 392 } 393 394 tmpfile.Close() 395 source = tmpfile.Name() 396 } 397 importedImage, err := runtime.Import(context.Background(), source, query.Reference, "", query.Changes, query.Message, true) 398 if err != nil { 399 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image")) 400 return 401 } 402 403 utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage}) 404 } 405 406 // PushImage is the handler for the compat http endpoint for pushing images. 407 func PushImage(w http.ResponseWriter, r *http.Request) { 408 decoder := r.Context().Value("decoder").(*schema.Decoder) 409 runtime := r.Context().Value("runtime").(*libpod.Runtime) 410 411 query := struct { 412 Destination string `schema:"destination"` 413 TLSVerify bool `schema:"tlsVerify"` 414 }{ 415 // This is where you can override the golang default value for one of fields 416 } 417 418 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 419 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 420 return 421 } 422 423 source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path 424 if _, err := utils.ParseStorageReference(source); err != nil { 425 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 426 return 427 } 428 429 destination := query.Destination 430 if destination == "" { 431 destination = source 432 } 433 434 if _, err := utils.ParseDockerReference(destination); err != nil { 435 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 436 return 437 } 438 439 newImage, err := runtime.ImageRuntime().NewFromLocal(source) 440 if err != nil { 441 utils.ImageNotFound(w, source, errors.Wrapf(err, "failed to find image %s", source)) 442 return 443 } 444 445 authConf, authfile, key, err := auth.GetCredentials(r) 446 if err != nil { 447 utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) 448 return 449 } 450 defer auth.RemoveAuthfile(authfile) 451 logrus.Errorf("AuthConf: %v", authConf) 452 453 dockerRegistryOptions := &image.DockerRegistryOptions{ 454 DockerRegistryCreds: authConf, 455 } 456 if sys := runtime.SystemContext(); sys != nil { 457 dockerRegistryOptions.DockerCertPath = sys.DockerCertPath 458 dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath 459 } 460 if _, found := r.URL.Query()["tlsVerify"]; found { 461 dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 462 } 463 464 err = newImage.PushImageToHeuristicDestination( 465 context.Background(), 466 destination, 467 "", // manifest type 468 authfile, 469 "", // digest file 470 "", // signature policy 471 os.Stderr, 472 false, // force compression 473 image.SigningOptions{}, 474 dockerRegistryOptions, 475 nil, // additional tags 476 ) 477 if err != nil { 478 utils.Error(w, "Something went wrong.", 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("decoder").(*schema.Decoder) 491 runtime := r.Context().Value("runtime").(*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 Repo string `schema:"repo"` 501 Tag string `schema:"tag"` 502 }{ 503 Format: "oci", 504 } 505 506 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 507 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 508 return 509 } 510 rtc, err := runtime.GetConfig() 511 if err != nil { 512 utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) 513 return 514 } 515 sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) 516 tag := "latest" 517 options := libpod.ContainerCommitOptions{ 518 Pause: true, 519 } 520 switch query.Format { 521 case "oci": 522 mimeType = buildah.OCIv1ImageManifest 523 if len(query.Comment) > 0 { 524 utils.InternalServerError(w, errors.New("messages are only compatible with the docker image format (-f docker)")) 525 return 526 } 527 case "docker": 528 mimeType = manifest.DockerV2Schema2MediaType 529 default: 530 utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format)) 531 return 532 } 533 options.CommitOptions = buildah.CommitOptions{ 534 SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, 535 ReportWriter: os.Stderr, 536 SystemContext: sc, 537 PreferredManifestType: mimeType, 538 } 539 540 if len(query.Tag) > 0 { 541 tag = query.Tag 542 } 543 options.Message = query.Comment 544 options.Author = query.Author 545 options.Pause = query.Pause 546 options.Changes = query.Changes 547 ctr, err := runtime.LookupContainer(query.Container) 548 if err != nil { 549 utils.Error(w, "failed to lookup container", http.StatusNotFound, err) 550 return 551 } 552 553 if len(query.Repo) > 0 { 554 destImage = fmt.Sprintf("%s:%s", query.Repo, tag) 555 } 556 commitImage, err := ctr.Commit(r.Context(), destImage, options) 557 if err != nil && !strings.Contains(err.Error(), "is not running") { 558 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) 559 return 560 } 561 utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint 562 } 563 564 func UntagImage(w http.ResponseWriter, r *http.Request) { 565 runtime := r.Context().Value("runtime").(*libpod.Runtime) 566 567 tags := []string{} // Note: if empty, all tags will be removed from the image. 568 repo := r.Form.Get("repo") 569 tag := r.Form.Get("tag") 570 571 // Do the parameter dance. 572 switch { 573 // If tag is set, repo must be as well. 574 case len(repo) == 0 && len(tag) > 0: 575 utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) 576 return 577 578 case len(repo) == 0: 579 break 580 581 // If repo is specified, we need to add that to the tags. 582 default: 583 if len(tag) == 0 { 584 // Normalize tag to "latest" if empty. 585 tag = "latest" 586 } 587 tags = append(tags, fmt.Sprintf("%s:%s", repo, tag)) 588 } 589 590 // Now use the ABI implementation to prevent us from having duplicate 591 // code. 592 opts := entities.ImageUntagOptions{} 593 imageEngine := abi.ImageEngine{Libpod: runtime} 594 595 name := utils.GetName(r) 596 if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil { 597 if errors.Cause(err) == define.ErrNoSuchImage { 598 utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) 599 } else { 600 utils.Error(w, "failed to untag", http.StatusInternalServerError, err) 601 } 602 return 603 } 604 utils.WriteResponse(w, http.StatusCreated, "") 605 } 606 607 func SearchImages(w http.ResponseWriter, r *http.Request) { 608 decoder := r.Context().Value("decoder").(*schema.Decoder) 609 query := struct { 610 Term string `json:"term"` 611 Limit int `json:"limit"` 612 NoTrunc bool `json:"noTrunc"` 613 Filters []string `json:"filters"` 614 TLSVerify bool `json:"tlsVerify"` 615 ListTags bool `json:"listTags"` 616 }{ 617 // This is where you can override the golang default value for one of fields 618 } 619 620 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 621 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 622 return 623 } 624 625 options := image.SearchOptions{ 626 Limit: query.Limit, 627 NoTrunc: query.NoTrunc, 628 ListTags: query.ListTags, 629 } 630 if _, found := r.URL.Query()["tlsVerify"]; found { 631 options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 632 } 633 634 if _, found := r.URL.Query()["filters"]; found { 635 filter, err := image.ParseSearchFilter(query.Filters) 636 if err != nil { 637 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse filters parameter for %s", r.URL.String())) 638 return 639 } 640 options.Filter = *filter 641 } 642 643 _, authfile, key, err := auth.GetCredentials(r) 644 if err != nil { 645 utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) 646 return 647 } 648 defer auth.RemoveAuthfile(authfile) 649 options.Authfile = authfile 650 651 searchResults, err := image.SearchImages(query.Term, options) 652 if err != nil { 653 utils.BadRequest(w, "term", query.Term, err) 654 return 655 } 656 // Convert from image.SearchResults to entities.ImageSearchReport. We don't 657 // want to leak any low-level packages into the remote client, which 658 // requires converting. 659 reports := make([]entities.ImageSearchReport, len(searchResults)) 660 for i := range searchResults { 661 reports[i].Index = searchResults[i].Index 662 reports[i].Name = searchResults[i].Name 663 reports[i].Description = searchResults[i].Description 664 reports[i].Stars = searchResults[i].Stars 665 reports[i].Official = searchResults[i].Official 666 reports[i].Automated = searchResults[i].Automated 667 reports[i].Tag = searchResults[i].Tag 668 } 669 670 utils.WriteResponse(w, http.StatusOK, reports) 671 } 672 673 // ImagesBatchRemove is the endpoint for batch image removal. 674 func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { 675 runtime := r.Context().Value("runtime").(*libpod.Runtime) 676 decoder := r.Context().Value("decoder").(*schema.Decoder) 677 query := struct { 678 All bool `schema:"all"` 679 Force bool `schema:"force"` 680 Images []string `schema:"images"` 681 }{ 682 All: false, 683 Force: false, 684 } 685 686 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 687 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 688 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 689 return 690 } 691 692 opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force} 693 694 imageEngine := abi.ImageEngine{Libpod: runtime} 695 rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts) 696 697 strErrs := errorhandling.ErrorsToStrings(rmErrors) 698 report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs} 699 utils.WriteResponse(w, http.StatusOK, report) 700 } 701 702 // ImagesRemove is the endpoint for removing one image. 703 func ImagesRemove(w http.ResponseWriter, r *http.Request) { 704 runtime := r.Context().Value("runtime").(*libpod.Runtime) 705 decoder := r.Context().Value("decoder").(*schema.Decoder) 706 query := struct { 707 Force bool `schema:"force"` 708 }{ 709 Force: false, 710 } 711 712 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 713 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 714 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 715 return 716 } 717 718 opts := entities.ImageRemoveOptions{Force: query.Force} 719 imageEngine := abi.ImageEngine{Libpod: runtime} 720 rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts) 721 722 // In contrast to batch-removal, where we're only setting the exit 723 // code, we need to have another closer look at the errors here and set 724 // the appropriate http status code. 725 726 switch rmReport.ExitCode { 727 case 0: 728 report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}} 729 utils.WriteResponse(w, http.StatusOK, report) 730 case 1: 731 // 404 - no such image 732 utils.Error(w, "error removing image", http.StatusNotFound, errorhandling.JoinErrors(rmErrors)) 733 case 2: 734 // 409 - conflict error (in use by containers) 735 utils.Error(w, "error removing image", http.StatusConflict, errorhandling.JoinErrors(rmErrors)) 736 default: 737 // 500 - internal error 738 utils.Error(w, "failed to remove image", http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors)) 739 } 740 }