github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/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/docker" 15 "github.com/containers/image/v5/docker/reference" 16 "github.com/containers/image/v5/manifest" 17 "github.com/containers/image/v5/types" 18 "github.com/containers/libpod/libpod" 19 "github.com/containers/libpod/libpod/define" 20 "github.com/containers/libpod/libpod/image" 21 image2 "github.com/containers/libpod/libpod/image" 22 "github.com/containers/libpod/pkg/api/handlers" 23 "github.com/containers/libpod/pkg/api/handlers/utils" 24 "github.com/containers/libpod/pkg/domain/entities" 25 "github.com/containers/libpod/pkg/util" 26 utils2 "github.com/containers/libpod/utils" 27 "github.com/gorilla/schema" 28 "github.com/pkg/errors" 29 ) 30 31 // Commit 32 // author string 33 // "container" 34 // repo string 35 // tag string 36 // message 37 // pause bool 38 // changes []string 39 40 // create 41 42 func ImageExists(w http.ResponseWriter, r *http.Request) { 43 runtime := r.Context().Value("runtime").(*libpod.Runtime) 44 name := utils.GetName(r) 45 46 _, err := runtime.ImageRuntime().NewFromLocal(name) 47 if err != nil { 48 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) 49 return 50 } 51 utils.WriteResponse(w, http.StatusNoContent, "") 52 } 53 54 func ImageTree(w http.ResponseWriter, r *http.Request) { 55 runtime := r.Context().Value("runtime").(*libpod.Runtime) 56 name := utils.GetName(r) 57 58 img, err := runtime.ImageRuntime().NewFromLocal(name) 59 if err != nil { 60 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) 61 return 62 } 63 64 decoder := r.Context().Value("decoder").(*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.StatusText(http.StatusBadRequest), http.StatusBadRequest, 72 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 73 return 74 } 75 76 tree, err := img.GenerateTree(query.WhatRequires) 77 if err != nil { 78 utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name)) 79 return 80 } 81 82 utils.WriteResponse(w, http.StatusOK, tree) 83 } 84 85 func GetImage(w http.ResponseWriter, r *http.Request) { 86 name := utils.GetName(r) 87 newImage, err := utils.GetImage(r, name) 88 if err != nil { 89 utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) 90 return 91 } 92 inspect, err := newImage.Inspect(r.Context()) 93 if err != nil { 94 utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) 95 return 96 } 97 utils.WriteResponse(w, http.StatusOK, inspect) 98 } 99 100 func GetImages(w http.ResponseWriter, r *http.Request) { 101 images, err := utils.GetImages(w, r) 102 if err != nil { 103 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) 104 return 105 } 106 var summaries = make([]*entities.ImageSummary, len(images)) 107 for j, img := range images { 108 is, err := handlers.ImageToImageSummary(img) 109 if err != nil { 110 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) 111 return 112 } 113 // libpod has additional fields that we need to populate. 114 is.Created = img.Created().Unix() 115 is.ReadOnly = img.IsReadOnly() 116 summaries[j] = is 117 } 118 utils.WriteResponse(w, http.StatusOK, summaries) 119 } 120 121 func PruneImages(w http.ResponseWriter, r *http.Request) { 122 var ( 123 err error 124 ) 125 runtime := r.Context().Value("runtime").(*libpod.Runtime) 126 decoder := r.Context().Value("decoder").(*schema.Decoder) 127 query := struct { 128 All bool `schema:"all"` 129 Filters map[string][]string `schema:"filters"` 130 }{ 131 // override any golang type defaults 132 } 133 134 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 135 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 136 errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 137 return 138 } 139 140 var libpodFilters = []string{} 141 if _, found := r.URL.Query()["filters"]; found { 142 dangling := query.Filters["all"] 143 if len(dangling) > 0 { 144 query.All, err = strconv.ParseBool(query.Filters["all"][0]) 145 if err != nil { 146 utils.InternalServerError(w, err) 147 return 148 } 149 } 150 // dangling is special and not implemented in the libpod side of things 151 delete(query.Filters, "dangling") 152 for k, v := range query.Filters { 153 libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) 154 } 155 } 156 157 cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) 158 if err != nil { 159 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) 160 return 161 } 162 utils.WriteResponse(w, http.StatusOK, cids) 163 } 164 165 func ExportImage(w http.ResponseWriter, r *http.Request) { 166 var ( 167 output string 168 ) 169 runtime := r.Context().Value("runtime").(*libpod.Runtime) 170 decoder := r.Context().Value("decoder").(*schema.Decoder) 171 query := struct { 172 Compress bool `schema:"compress"` 173 Format string `schema:"format"` 174 }{ 175 Format: define.OCIArchive, 176 } 177 178 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 179 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 180 errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 181 return 182 } 183 name := utils.GetName(r) 184 newImage, err := runtime.ImageRuntime().NewFromLocal(name) 185 if err != nil { 186 utils.ImageNotFound(w, name, err) 187 return 188 } 189 switch query.Format { 190 case define.OCIArchive, define.V2s2Archive: 191 tmpfile, err := ioutil.TempFile("", "api.tar") 192 if err != nil { 193 utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 194 return 195 } 196 output = tmpfile.Name() 197 if err := tmpfile.Close(); err != nil { 198 utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) 199 return 200 } 201 case define.OCIManifestDir, define.V2s2ManifestDir: 202 tmpdir, err := ioutil.TempDir("", "save") 203 if err != nil { 204 utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) 205 return 206 } 207 output = tmpdir 208 default: 209 utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) 210 return 211 } 212 if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress); err != nil { 213 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 214 return 215 } 216 defer os.RemoveAll(output) 217 // if dir format, we need to tar it 218 if query.Format == "oci-dir" || query.Format == "docker-dir" { 219 rdr, err := utils2.Tar(output) 220 if err != nil { 221 utils.InternalServerError(w, err) 222 return 223 } 224 defer rdr.Close() 225 utils.WriteResponse(w, http.StatusOK, rdr) 226 return 227 } 228 rdr, err := os.Open(output) 229 if err != nil { 230 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) 231 return 232 } 233 defer rdr.Close() 234 utils.WriteResponse(w, http.StatusOK, rdr) 235 } 236 237 func ImagesLoad(w http.ResponseWriter, r *http.Request) { 238 runtime := r.Context().Value("runtime").(*libpod.Runtime) 239 decoder := r.Context().Value("decoder").(*schema.Decoder) 240 query := struct { 241 Reference string `schema:"reference"` 242 }{ 243 // Add defaults here once needed. 244 } 245 246 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 247 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 248 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 249 return 250 } 251 252 tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar") 253 if err != nil { 254 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 255 return 256 } 257 defer os.Remove(tmpfile.Name()) 258 defer tmpfile.Close() 259 260 if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { 261 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) 262 return 263 } 264 265 tmpfile.Close() 266 loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "") 267 if err != nil { 268 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) 269 return 270 } 271 split := strings.Split(loadedImage, ",") 272 newImage, err := runtime.ImageRuntime().NewFromLocal(split[0]) 273 if err != nil { 274 utils.InternalServerError(w, err) 275 return 276 } 277 // TODO this should go into libpod proper at some point. 278 if len(query.Reference) > 0 { 279 if err := newImage.TagImage(query.Reference); err != nil { 280 utils.InternalServerError(w, err) 281 return 282 } 283 } 284 utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Name: loadedImage}) 285 } 286 287 func ImagesImport(w http.ResponseWriter, r *http.Request) { 288 runtime := r.Context().Value("runtime").(*libpod.Runtime) 289 decoder := r.Context().Value("decoder").(*schema.Decoder) 290 query := struct { 291 Changes []string `schema:"changes"` 292 Message string `schema:"message"` 293 Reference string `schema:"reference"` 294 URL string `schema:"URL"` 295 }{ 296 // Add defaults here once needed. 297 } 298 299 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 300 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 301 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 302 return 303 } 304 305 // Check if we need to load the image from a URL or from the request's body. 306 source := query.URL 307 if len(query.URL) == 0 { 308 tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar") 309 if err != nil { 310 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) 311 return 312 } 313 defer os.Remove(tmpfile.Name()) 314 defer tmpfile.Close() 315 316 if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { 317 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) 318 return 319 } 320 321 tmpfile.Close() 322 source = tmpfile.Name() 323 } 324 importedImage, err := runtime.Import(context.Background(), source, query.Reference, query.Changes, query.Message, true) 325 if err != nil { 326 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image")) 327 return 328 } 329 330 utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage}) 331 } 332 333 // ImagesPull is the v2 libpod endpoint for pulling images. Note that the 334 // mandatory `reference` must be a reference to a registry (i.e., of docker 335 // transport or be normalized to one). Other transports are rejected as they 336 // do not make sense in a remote context. 337 func ImagesPull(w http.ResponseWriter, r *http.Request) { 338 runtime := r.Context().Value("runtime").(*libpod.Runtime) 339 decoder := r.Context().Value("decoder").(*schema.Decoder) 340 query := struct { 341 Reference string `schema:"reference"` 342 Credentials string `schema:"credentials"` 343 OverrideOS string `schema:"overrideOS"` 344 OverrideArch string `schema:"overrideArch"` 345 TLSVerify bool `schema:"tlsVerify"` 346 AllTags bool `schema:"allTags"` 347 }{ 348 TLSVerify: true, 349 } 350 351 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 352 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 353 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 354 return 355 } 356 357 if len(query.Reference) == 0 { 358 utils.InternalServerError(w, errors.New("reference parameter cannot be empty")) 359 return 360 } 361 362 imageRef, err := utils.ParseDockerReference(query.Reference) 363 if err != nil { 364 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 365 errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference)) 366 return 367 } 368 369 // Trim the docker-transport prefix. 370 rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name())) 371 372 // all-tags doesn't work with a tagged reference, so let's check early 373 namedRef, err := reference.Parse(rawImage) 374 if err != nil { 375 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 376 errors.Wrapf(err, "error parsing reference %q", rawImage)) 377 return 378 } 379 if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags { 380 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 381 errors.Errorf("reference %q must not have a tag for all-tags", rawImage)) 382 return 383 } 384 385 var registryCreds *types.DockerAuthConfig 386 if len(query.Credentials) != 0 { 387 creds, err := util.ParseRegistryCreds(query.Credentials) 388 if err != nil { 389 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 390 errors.Wrapf(err, "error parsing credentials %q", query.Credentials)) 391 return 392 } 393 registryCreds = creds 394 } 395 396 // Setup the registry options 397 dockerRegistryOptions := image.DockerRegistryOptions{ 398 DockerRegistryCreds: registryCreds, 399 OSChoice: query.OverrideOS, 400 ArchitectureChoice: query.OverrideArch, 401 } 402 if _, found := r.URL.Query()["tlsVerify"]; found { 403 dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 404 } 405 406 // Prepare the images we want to pull 407 imagesToPull := []string{} 408 res := []handlers.LibpodImagesPullReport{} 409 imageName := namedRef.String() 410 411 if !query.AllTags { 412 imagesToPull = append(imagesToPull, imageName) 413 } else { 414 systemContext := image.GetSystemContext("", "", false) 415 tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef) 416 if err != nil { 417 utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags")) 418 return 419 } 420 for _, tag := range tags { 421 imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag)) 422 } 423 } 424 425 authfile := "" 426 if sys := runtime.SystemContext(); sys != nil { 427 dockerRegistryOptions.DockerCertPath = sys.DockerCertPath 428 authfile = sys.AuthFilePath 429 } 430 431 // Finally pull the images 432 for _, img := range imagesToPull { 433 newImage, err := runtime.ImageRuntime().New( 434 context.Background(), 435 img, 436 "", 437 authfile, 438 os.Stderr, 439 &dockerRegistryOptions, 440 image.SigningOptions{}, 441 nil, 442 util.PullImageAlways) 443 if err != nil { 444 utils.InternalServerError(w, errors.Wrapf(err, "error pulling image %q", query.Reference)) 445 return 446 } 447 res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()}) 448 } 449 450 utils.WriteResponse(w, http.StatusOK, res) 451 } 452 453 // PushImage is the handler for the compat http endpoint for pushing images. 454 func PushImage(w http.ResponseWriter, r *http.Request) { 455 decoder := r.Context().Value("decoder").(*schema.Decoder) 456 runtime := r.Context().Value("runtime").(*libpod.Runtime) 457 458 query := struct { 459 Credentials string `schema:"credentials"` 460 Destination string `schema:"destination"` 461 TLSVerify bool `schema:"tlsVerify"` 462 }{ 463 // This is where you can override the golang default value for one of fields 464 } 465 466 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 467 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 468 return 469 } 470 471 source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path 472 if _, err := utils.ParseStorageReference(source); err != nil { 473 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 474 errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", source)) 475 return 476 } 477 478 destination := query.Destination 479 if destination == "" { 480 destination = source 481 } 482 483 if _, err := utils.ParseDockerReference(destination); err != nil { 484 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 485 errors.Wrapf(err, "image destination %q is not a docker-transport reference", destination)) 486 return 487 } 488 489 newImage, err := runtime.ImageRuntime().NewFromLocal(source) 490 if err != nil { 491 utils.ImageNotFound(w, source, errors.Wrapf(err, "Failed to find image %s", source)) 492 return 493 } 494 495 var registryCreds *types.DockerAuthConfig 496 if len(query.Credentials) != 0 { 497 creds, err := util.ParseRegistryCreds(query.Credentials) 498 if err != nil { 499 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 500 errors.Wrapf(err, "error parsing credentials %q", query.Credentials)) 501 return 502 } 503 registryCreds = creds 504 } 505 506 // TODO: the X-Registry-Auth header is not checked yet here nor in any other 507 // endpoint. Pushing does NOT work with authentication at the moment. 508 dockerRegistryOptions := &image.DockerRegistryOptions{ 509 DockerRegistryCreds: registryCreds, 510 } 511 authfile := "" 512 if sys := runtime.SystemContext(); sys != nil { 513 dockerRegistryOptions.DockerCertPath = sys.DockerCertPath 514 authfile = sys.AuthFilePath 515 } 516 if _, found := r.URL.Query()["tlsVerify"]; found { 517 dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 518 } 519 520 err = newImage.PushImageToHeuristicDestination( 521 context.Background(), 522 destination, 523 "", // manifest type 524 authfile, 525 "", // digest file 526 "", // signature policy 527 os.Stderr, 528 false, // force compression 529 image.SigningOptions{}, 530 dockerRegistryOptions, 531 nil, // additional tags 532 ) 533 if err != nil { 534 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Error pushing image %q", destination)) 535 return 536 } 537 538 utils.WriteResponse(w, http.StatusOK, "") 539 } 540 541 func CommitContainer(w http.ResponseWriter, r *http.Request) { 542 var ( 543 destImage string 544 mimeType string 545 ) 546 decoder := r.Context().Value("decoder").(*schema.Decoder) 547 runtime := r.Context().Value("runtime").(*libpod.Runtime) 548 549 query := struct { 550 Author string `schema:"author"` 551 Changes []string `schema:"changes"` 552 Comment string `schema:"comment"` 553 Container string `schema:"container"` 554 Format string `schema:"format"` 555 Pause bool `schema:"pause"` 556 Repo string `schema:"repo"` 557 Tag string `schema:"tag"` 558 }{ 559 Format: "oci", 560 } 561 562 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 563 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 564 return 565 } 566 rtc, err := runtime.GetConfig() 567 if err != nil { 568 utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) 569 return 570 } 571 sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) 572 tag := "latest" 573 options := libpod.ContainerCommitOptions{ 574 Pause: true, 575 } 576 switch query.Format { 577 case "oci": 578 mimeType = buildah.OCIv1ImageManifest 579 if len(query.Comment) > 0 { 580 utils.InternalServerError(w, errors.New("messages are only compatible with the docker image format (-f docker)")) 581 return 582 } 583 case "docker": 584 mimeType = manifest.DockerV2Schema2MediaType 585 default: 586 utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format)) 587 return 588 } 589 options.CommitOptions = buildah.CommitOptions{ 590 SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, 591 ReportWriter: os.Stderr, 592 SystemContext: sc, 593 PreferredManifestType: mimeType, 594 } 595 596 if len(query.Tag) > 0 { 597 tag = query.Tag 598 } 599 options.Message = query.Comment 600 options.Author = query.Author 601 options.Pause = query.Pause 602 options.Changes = query.Changes 603 ctr, err := runtime.LookupContainer(query.Container) 604 if err != nil { 605 utils.Error(w, "failed to lookup container", http.StatusNotFound, err) 606 return 607 } 608 609 // I know mitr hates this ... but doing for now 610 if len(query.Repo) > 1 { 611 destImage = fmt.Sprintf("%s:%s", query.Repo, tag) 612 } 613 614 commitImage, err := ctr.Commit(r.Context(), destImage, options) 615 if err != nil && !strings.Contains(err.Error(), "is not running") { 616 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) 617 return 618 } 619 utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint 620 } 621 622 func UntagImage(w http.ResponseWriter, r *http.Request) { 623 runtime := r.Context().Value("runtime").(*libpod.Runtime) 624 625 name := utils.GetName(r) 626 newImage, err := runtime.ImageRuntime().NewFromLocal(name) 627 if err != nil { 628 utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) 629 return 630 } 631 tag := "latest" 632 if len(r.Form.Get("tag")) > 0 { 633 tag = r.Form.Get("tag") 634 } 635 if len(r.Form.Get("repo")) < 1 { 636 utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) 637 return 638 } 639 repo := r.Form.Get("repo") 640 tagName := fmt.Sprintf("%s:%s", repo, tag) 641 if err := newImage.UntagImage(tagName); err != nil { 642 utils.Error(w, "failed to untag", http.StatusInternalServerError, err) 643 return 644 } 645 utils.WriteResponse(w, http.StatusCreated, "") 646 } 647 648 func SearchImages(w http.ResponseWriter, r *http.Request) { 649 decoder := r.Context().Value("decoder").(*schema.Decoder) 650 query := struct { 651 Term string `json:"term"` 652 Limit int `json:"limit"` 653 Filters []string `json:"filters"` 654 TLSVerify bool `json:"tlsVerify"` 655 }{ 656 // This is where you can override the golang default value for one of fields 657 } 658 659 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 660 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 661 return 662 } 663 664 options := image.SearchOptions{ 665 Limit: query.Limit, 666 } 667 if _, found := r.URL.Query()["tlsVerify"]; found { 668 options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 669 } 670 671 if _, found := r.URL.Query()["filters"]; found { 672 filter, err := image.ParseSearchFilter(query.Filters) 673 if err != nil { 674 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse filters parameter for %s", r.URL.String())) 675 return 676 } 677 options.Filter = *filter 678 } 679 680 searchResults, err := image.SearchImages(query.Term, options) 681 if err != nil { 682 utils.BadRequest(w, "term", query.Term, err) 683 return 684 } 685 // Convert from image.SearchResults to entities.ImageSearchReport. We don't 686 // want to leak any low-level packages into the remote client, which 687 // requires converting. 688 reports := make([]entities.ImageSearchReport, len(searchResults)) 689 for i := range searchResults { 690 reports[i].Index = searchResults[i].Index 691 reports[i].Name = searchResults[i].Name 692 reports[i].Description = searchResults[i].Index 693 reports[i].Stars = searchResults[i].Stars 694 reports[i].Official = searchResults[i].Official 695 reports[i].Automated = searchResults[i].Automated 696 } 697 698 utils.WriteResponse(w, http.StatusOK, reports) 699 }