github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/varlinkapi/images.go (about) 1 // +build varlink 2 3 package varlinkapi 4 5 import ( 6 "bytes" 7 "context" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strings" 15 "time" 16 17 "github.com/containers/buildah" 18 "github.com/containers/buildah/imagebuildah" 19 dockerarchive "github.com/containers/image/v5/docker/archive" 20 "github.com/containers/image/v5/manifest" 21 "github.com/containers/image/v5/transports/alltransports" 22 "github.com/containers/image/v5/types" 23 "github.com/containers/libpod/cmd/podman/shared" 24 "github.com/containers/libpod/libpod" 25 "github.com/containers/libpod/libpod/define" 26 "github.com/containers/libpod/libpod/image" 27 "github.com/containers/libpod/pkg/channelwriter" 28 "github.com/containers/libpod/pkg/util" 29 iopodman "github.com/containers/libpod/pkg/varlink" 30 "github.com/containers/libpod/utils" 31 "github.com/containers/storage/pkg/archive" 32 v1 "github.com/opencontainers/image-spec/specs-go/v1" 33 "github.com/pkg/errors" 34 "github.com/sirupsen/logrus" 35 ) 36 37 // ListImagesWithFilters returns a list of images that have been filtered 38 func (i *VarlinkAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []string) error { 39 images, err := i.Runtime.ImageRuntime().GetImagesWithFilters(filters) 40 if err != nil { 41 return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err)) 42 } 43 imageList, err := imagesToImageList(images) 44 if err != nil { 45 return call.ReplyErrorOccurred("unable to parse response " + err.Error()) 46 } 47 return call.ReplyListImagesWithFilters(imageList) 48 } 49 50 // imagesToImageList converts a slice of Images to an imagelist for varlink responses 51 func imagesToImageList(images []*image.Image) ([]iopodman.Image, error) { 52 var imageList []iopodman.Image 53 for _, img := range images { 54 labels, _ := img.Labels(getContext()) 55 containers, _ := img.Containers() 56 repoDigests, err := img.RepoDigests() 57 if err != nil { 58 return nil, err 59 } 60 61 size, _ := img.Size(getContext()) 62 isParent, err := img.IsParent(context.TODO()) 63 if err != nil { 64 return nil, err 65 } 66 67 i := iopodman.Image{ 68 Id: img.ID(), 69 Digest: string(img.Digest()), 70 ParentId: img.Parent, 71 RepoTags: img.Names(), 72 RepoDigests: repoDigests, 73 Created: img.Created().Format(time.RFC3339), 74 Size: int64(*size), 75 VirtualSize: img.VirtualSize, 76 Containers: int64(len(containers)), 77 Labels: labels, 78 IsParent: isParent, 79 ReadOnly: img.IsReadOnly(), 80 History: img.NamesHistory(), 81 } 82 imageList = append(imageList, i) 83 } 84 return imageList, nil 85 } 86 87 // ListImages lists all the images in the store 88 // It requires no inputs. 89 func (i *VarlinkAPI) ListImages(call iopodman.VarlinkCall) error { 90 images, err := i.Runtime.ImageRuntime().GetImages() 91 if err != nil { 92 return call.ReplyErrorOccurred("unable to get list of images " + err.Error()) 93 } 94 imageList, err := imagesToImageList(images) 95 if err != nil { 96 return call.ReplyErrorOccurred("unable to parse response " + err.Error()) 97 } 98 return call.ReplyListImages(imageList) 99 } 100 101 // GetImage returns a single image in the form of a Image 102 func (i *VarlinkAPI) GetImage(call iopodman.VarlinkCall, id string) error { 103 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(id) 104 if err != nil { 105 return call.ReplyImageNotFound(id, err.Error()) 106 } 107 labels, err := newImage.Labels(getContext()) 108 if err != nil { 109 return err 110 } 111 containers, err := newImage.Containers() 112 if err != nil { 113 return err 114 } 115 repoDigests, err := newImage.RepoDigests() 116 if err != nil { 117 return err 118 } 119 size, err := newImage.Size(getContext()) 120 if err != nil { 121 return err 122 } 123 124 il := iopodman.Image{ 125 Id: newImage.ID(), 126 ParentId: newImage.Parent, 127 RepoTags: newImage.Names(), 128 RepoDigests: repoDigests, 129 Created: newImage.Created().Format(time.RFC3339), 130 Size: int64(*size), 131 VirtualSize: newImage.VirtualSize, 132 Containers: int64(len(containers)), 133 Labels: labels, 134 TopLayer: newImage.TopLayer(), 135 ReadOnly: newImage.IsReadOnly(), 136 History: newImage.NamesHistory(), 137 } 138 return call.ReplyGetImage(il) 139 } 140 141 // BuildImage ... 142 func (i *VarlinkAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error { 143 var ( 144 namespace []buildah.NamespaceOption 145 imageID string 146 err error 147 ) 148 149 contextDir := config.ContextDir 150 151 newContextDir, err := ioutil.TempDir("", "buildTarball") 152 if err != nil { 153 call.ReplyErrorOccurred("unable to create tempdir") 154 } 155 logrus.Debugf("created new context dir at %s", newContextDir) 156 157 reader, err := os.Open(contextDir) 158 if err != nil { 159 logrus.Errorf("failed to open the context dir tar file %s", contextDir) 160 return call.ReplyErrorOccurred(fmt.Sprintf("unable to open context dir tar file %s", contextDir)) 161 } 162 defer reader.Close() 163 if err := archive.Untar(reader, newContextDir, &archive.TarOptions{}); err != nil { 164 logrus.Errorf("fail to untar the context dir tarball (%s) to the context dir (%s)", contextDir, newContextDir) 165 return call.ReplyErrorOccurred(fmt.Sprintf("unable to untar context dir %s", contextDir)) 166 } 167 logrus.Debugf("untar of %s successful", contextDir) 168 defer func() { 169 if err := os.Remove(contextDir); err != nil { 170 logrus.Errorf("unable to delete file '%s': %q", contextDir, err) 171 } 172 if err := os.RemoveAll(newContextDir); err != nil { 173 logrus.Errorf("unable to delete directory '%s': %q", newContextDir, err) 174 } 175 }() 176 177 systemContext := types.SystemContext{} 178 // All output (stdout, stderr) is captured in output as well 179 var output bytes.Buffer 180 181 commonOpts := &buildah.CommonBuildOptions{ 182 AddHost: config.BuildOptions.AddHosts, 183 CgroupParent: config.BuildOptions.CgroupParent, 184 CPUPeriod: uint64(config.BuildOptions.CpuPeriod), 185 CPUQuota: config.BuildOptions.CpuQuota, 186 CPUSetCPUs: config.BuildOptions.CpusetCpus, 187 CPUSetMems: config.BuildOptions.CpusetMems, 188 Memory: config.BuildOptions.Memory, 189 MemorySwap: config.BuildOptions.MemorySwap, 190 ShmSize: config.BuildOptions.ShmSize, 191 Ulimit: config.BuildOptions.Ulimit, 192 Volumes: config.BuildOptions.Volume, 193 } 194 195 options := imagebuildah.BuildOptions{ 196 AddCapabilities: config.AddCapabilities, 197 AdditionalTags: config.AdditionalTags, 198 Annotations: config.Annotations, 199 Architecture: config.Architecture, 200 Args: config.BuildArgs, 201 CNIConfigDir: config.CniConfigDir, 202 CNIPluginPath: config.CniPluginDir, 203 CommonBuildOpts: commonOpts, 204 Compression: stringCompressionToArchiveType(config.Compression), 205 ContextDirectory: newContextDir, 206 DefaultMountsFilePath: config.DefaultsMountFilePath, 207 Devices: config.Devices, 208 Err: &output, 209 ForceRmIntermediateCtrs: config.ForceRmIntermediateCtrs, 210 IIDFile: config.Iidfile, 211 Labels: config.Label, 212 Layers: config.Layers, 213 NamespaceOptions: namespace, 214 NoCache: config.Nocache, 215 OS: config.Os, 216 Out: &output, 217 Output: config.Output, 218 OutputFormat: config.OutputFormat, 219 PullPolicy: stringPullPolicyToType(config.PullPolicy), 220 Quiet: config.Quiet, 221 RemoveIntermediateCtrs: config.RemoteIntermediateCtrs, 222 ReportWriter: &output, 223 RuntimeArgs: config.RuntimeArgs, 224 SignBy: config.SignBy, 225 Squash: config.Squash, 226 SystemContext: &systemContext, 227 Target: config.Target, 228 TransientMounts: config.TransientMounts, 229 } 230 231 if call.WantsMore() { 232 call.Continues = true 233 } else { 234 return call.ReplyErrorOccurred("endpoint requires a more connection") 235 } 236 237 var newPathDockerFiles []string 238 239 for _, d := range config.Dockerfiles { 240 if strings.HasPrefix(d, "http://") || 241 strings.HasPrefix(d, "https://") || 242 strings.HasPrefix(d, "git://") || 243 strings.HasPrefix(d, "github.com/") { 244 newPathDockerFiles = append(newPathDockerFiles, d) 245 continue 246 } 247 base := filepath.Base(d) 248 newPathDockerFiles = append(newPathDockerFiles, filepath.Join(newContextDir, base)) 249 } 250 251 c := make(chan error) 252 go func() { 253 iid, _, err := i.Runtime.Build(getContext(), options, newPathDockerFiles...) 254 imageID = iid 255 c <- err 256 close(c) 257 }() 258 259 var log []string 260 done := false 261 for { 262 outputLine, err := output.ReadString('\n') 263 if err == nil { 264 log = append(log, outputLine) 265 if call.WantsMore() { 266 // we want to reply with what we have 267 br := iopodman.MoreResponse{ 268 Logs: log, 269 } 270 call.ReplyBuildImage(br) 271 log = []string{} 272 } 273 continue 274 } else if err == io.EOF { 275 select { 276 case err := <-c: 277 if err != nil { 278 return call.ReplyErrorOccurred(err.Error()) 279 } 280 done = true 281 default: 282 if call.WantsMore() { 283 time.Sleep(1 * time.Second) 284 break 285 } 286 } 287 } else { 288 return call.ReplyErrorOccurred(err.Error()) 289 } 290 if done { 291 break 292 } 293 } 294 call.Continues = false 295 296 br := iopodman.MoreResponse{ 297 Logs: log, 298 Id: imageID, 299 } 300 return call.ReplyBuildImage(br) 301 } 302 303 // InspectImage returns an image's inspect information as a string that can be serialized. 304 // Requires an image ID or name 305 func (i *VarlinkAPI) InspectImage(call iopodman.VarlinkCall, name string) error { 306 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 307 if err != nil { 308 return call.ReplyImageNotFound(name, err.Error()) 309 } 310 inspectInfo, err := newImage.Inspect(getContext()) 311 if err != nil { 312 return call.ReplyErrorOccurred(err.Error()) 313 } 314 b, err := json.Marshal(inspectInfo) 315 if err != nil { 316 return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize")) 317 } 318 return call.ReplyInspectImage(string(b)) 319 } 320 321 // HistoryImage returns the history of the image's layers 322 // Requires an image or name 323 func (i *VarlinkAPI) HistoryImage(call iopodman.VarlinkCall, name string) error { 324 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 325 if err != nil { 326 return call.ReplyImageNotFound(name, err.Error()) 327 } 328 history, err := newImage.History(getContext()) 329 if err != nil { 330 return call.ReplyErrorOccurred(err.Error()) 331 } 332 var histories []iopodman.ImageHistory 333 for _, hist := range history { 334 imageHistory := iopodman.ImageHistory{ 335 Id: hist.ID, 336 Created: hist.Created.Format(time.RFC3339), 337 CreatedBy: hist.CreatedBy, 338 Tags: newImage.Names(), 339 Size: hist.Size, 340 Comment: hist.Comment, 341 } 342 histories = append(histories, imageHistory) 343 } 344 return call.ReplyHistoryImage(histories) 345 } 346 347 // PushImage pushes an local image to registry 348 func (i *VarlinkAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compress bool, format string, removeSignatures bool, signBy string) error { 349 var ( 350 manifestType string 351 ) 352 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 353 if err != nil { 354 return call.ReplyImageNotFound(name, err.Error()) 355 } 356 destname := name 357 if tag != "" { 358 destname = tag 359 } 360 dockerRegistryOptions := image.DockerRegistryOptions{} 361 if format != "" { 362 switch format { 363 case "oci": // nolint 364 manifestType = v1.MediaTypeImageManifest 365 case "v2s1": 366 manifestType = manifest.DockerV2Schema1SignedMediaType 367 case "v2s2", "docker": 368 manifestType = manifest.DockerV2Schema2MediaType 369 default: 370 return call.ReplyErrorOccurred(fmt.Sprintf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", format)) 371 } 372 } 373 so := image.SigningOptions{ 374 RemoveSignatures: removeSignatures, 375 SignBy: signBy, 376 } 377 378 if call.WantsMore() { 379 call.Continues = true 380 } 381 382 output := bytes.NewBuffer([]byte{}) 383 c := make(chan error) 384 go func() { 385 writer := bytes.NewBuffer([]byte{}) 386 err := newImage.PushImageToHeuristicDestination(getContext(), destname, manifestType, "", "", "", writer, compress, so, &dockerRegistryOptions, nil) 387 if err != nil { 388 c <- err 389 } 390 _, err = io.CopyBuffer(output, writer, nil) 391 c <- err 392 close(c) 393 }() 394 395 // TODO When pull output gets fixed for the remote client, we need to look into how we can turn below 396 // into something re-usable. it is in build too 397 var log []string 398 done := false 399 for { 400 line, err := output.ReadString('\n') 401 if err == nil { 402 log = append(log, line) 403 continue 404 } else if err == io.EOF { 405 select { 406 case err := <-c: 407 if err != nil { 408 logrus.Errorf("reading of output during push failed for %s", newImage.ID()) 409 return call.ReplyErrorOccurred(err.Error()) 410 } 411 done = true 412 default: 413 if !call.WantsMore() { 414 break 415 } 416 br := iopodman.MoreResponse{ 417 Logs: log, 418 Id: newImage.ID(), 419 } 420 call.ReplyPushImage(br) 421 log = []string{} 422 } 423 } else { 424 return call.ReplyErrorOccurred(err.Error()) 425 } 426 if done { 427 break 428 } 429 } 430 call.Continues = false 431 432 br := iopodman.MoreResponse{ 433 Logs: log, 434 Id: newImage.ID(), 435 } 436 return call.ReplyPushImage(br) 437 } 438 439 // TagImage accepts an image name and tag as strings and tags an image in the local store. 440 func (i *VarlinkAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error { 441 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 442 if err != nil { 443 return call.ReplyImageNotFound(name, err.Error()) 444 } 445 if err := newImage.TagImage(tag); err != nil { 446 return call.ReplyErrorOccurred(err.Error()) 447 } 448 return call.ReplyTagImage(newImage.ID()) 449 } 450 451 // UntagImage accepts an image name and tag as strings and removes the tag from the local store. 452 func (i *VarlinkAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) error { 453 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 454 if err != nil { 455 return call.ReplyImageNotFound(name, err.Error()) 456 } 457 if err := newImage.UntagImage(tag); err != nil { 458 return call.ReplyErrorOccurred(err.Error()) 459 } 460 return call.ReplyUntagImage(newImage.ID()) 461 } 462 463 // RemoveImage accepts a image name or ID as a string and force bool to determine if it should 464 // remove the image even if being used by stopped containers 465 func (i *VarlinkAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bool) error { 466 ctx := getContext() 467 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 468 if err != nil { 469 return call.ReplyImageNotFound(name, err.Error()) 470 } 471 _, err = i.Runtime.RemoveImage(ctx, newImage, force) 472 if err != nil { 473 return call.ReplyErrorOccurred(err.Error()) 474 } 475 return call.ReplyRemoveImage(newImage.ID()) 476 } 477 478 // RemoveImageWithResponse accepts an image name and force bool. It returns details about what 479 // was done in removeimageresponse struct. 480 func (i *VarlinkAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name string, force bool) error { 481 ir := iopodman.RemoveImageResponse{} 482 ctx := getContext() 483 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 484 if err != nil { 485 return call.ReplyImageNotFound(name, err.Error()) 486 } 487 response, err := i.Runtime.RemoveImage(ctx, newImage, force) 488 if err != nil { 489 return call.ReplyErrorOccurred(err.Error()) 490 } 491 ir.Untagged = append(ir.Untagged, response.Untagged...) 492 ir.Deleted = response.Deleted 493 return call.ReplyRemoveImageWithResponse(ir) 494 } 495 496 // SearchImages searches all registries configured in /etc/containers/registries.conf for an image 497 // Requires an image name and a search limit as int 498 func (i *VarlinkAPI) SearchImages(call iopodman.VarlinkCall, query string, limit *int64, filter iopodman.ImageSearchFilter) error { 499 // Transform all arguments to proper types first 500 argLimit := 0 501 argIsOfficial := types.OptionalBoolUndefined 502 argIsAutomated := types.OptionalBoolUndefined 503 if limit != nil { 504 argLimit = int(*limit) 505 } 506 if filter.Is_official != nil { 507 argIsOfficial = types.NewOptionalBool(*filter.Is_official) 508 } 509 if filter.Is_automated != nil { 510 argIsAutomated = types.NewOptionalBool(*filter.Is_automated) 511 } 512 513 // Transform a SearchFilter the backend can deal with 514 sFilter := image.SearchFilter{ 515 IsOfficial: argIsOfficial, 516 IsAutomated: argIsAutomated, 517 Stars: int(filter.Star_count), 518 } 519 520 searchOptions := image.SearchOptions{ 521 Limit: argLimit, 522 Filter: sFilter, 523 } 524 results, err := image.SearchImages(query, searchOptions) 525 if err != nil { 526 return call.ReplyErrorOccurred(err.Error()) 527 } 528 529 var imageResults []iopodman.ImageSearchResult 530 for _, result := range results { 531 i := iopodman.ImageSearchResult{ 532 Registry: result.Index, 533 Description: result.Description, 534 Is_official: result.Official == "[OK]", 535 Is_automated: result.Automated == "[OK]", 536 Name: result.Name, 537 Star_count: int64(result.Stars), 538 } 539 imageResults = append(imageResults, i) 540 } 541 return call.ReplySearchImages(imageResults) 542 } 543 544 // DeleteUnusedImages deletes any images that do not have containers associated with it. 545 // TODO Filters are not implemented 546 func (i *VarlinkAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error { 547 images, err := i.Runtime.ImageRuntime().GetImages() 548 if err != nil { 549 return call.ReplyErrorOccurred(err.Error()) 550 } 551 var deletedImages []string 552 for _, img := range images { 553 containers, err := img.Containers() 554 if err != nil { 555 return call.ReplyErrorOccurred(err.Error()) 556 } 557 if len(containers) == 0 { 558 if err := img.Remove(context.TODO(), false); err != nil { 559 return call.ReplyErrorOccurred(err.Error()) 560 } 561 deletedImages = append(deletedImages, img.ID()) 562 } 563 } 564 return call.ReplyDeleteUnusedImages(deletedImages) 565 } 566 567 // Commit ... 568 func (i *VarlinkAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error { 569 var ( 570 newImage *image.Image 571 log []string 572 mimeType string 573 ) 574 output := channelwriter.NewChannelWriter() 575 channelClose := func() { 576 if err := output.Close(); err != nil { 577 logrus.Errorf("failed to close channel writer: %q", err) 578 } 579 } 580 defer channelClose() 581 582 ctr, err := i.Runtime.LookupContainer(name) 583 if err != nil { 584 return call.ReplyContainerNotFound(name, err.Error()) 585 } 586 rtc, err := i.Runtime.GetConfig() 587 if err != nil { 588 return call.ReplyErrorOccurred(err.Error()) 589 } 590 sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) 591 switch manifestType { 592 case "oci", "": // nolint 593 mimeType = buildah.OCIv1ImageManifest 594 case "docker": 595 mimeType = manifest.DockerV2Schema2MediaType 596 default: 597 return call.ReplyErrorOccurred(fmt.Sprintf("unrecognized image format %q", manifestType)) 598 } 599 coptions := buildah.CommitOptions{ 600 SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, 601 ReportWriter: output, 602 SystemContext: sc, 603 PreferredManifestType: mimeType, 604 } 605 options := libpod.ContainerCommitOptions{ 606 CommitOptions: coptions, 607 Pause: pause, 608 Message: message, 609 Changes: changes, 610 Author: author, 611 } 612 613 if call.WantsMore() { 614 call.Continues = true 615 } 616 617 c := make(chan error) 618 619 go func() { 620 newImage, err = ctr.Commit(getContext(), imageName, options) 621 if err != nil { 622 c <- err 623 } 624 c <- nil 625 close(c) 626 }() 627 628 // reply is the func being sent to the output forwarder. in this case it is replying 629 // with a more response struct 630 reply := func(br iopodman.MoreResponse) error { 631 return call.ReplyCommit(br) 632 } 633 log, err = forwardOutput(log, c, call.WantsMore(), output, reply) 634 if err != nil { 635 return call.ReplyErrorOccurred(err.Error()) 636 } 637 call.Continues = false 638 br := iopodman.MoreResponse{ 639 Logs: log, 640 Id: newImage.ID(), 641 } 642 return call.ReplyCommit(br) 643 } 644 645 // ImportImage imports an image from a tarball to the image store 646 func (i *VarlinkAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error { 647 configChanges, err := util.GetImageConfig(changes) 648 if err != nil { 649 return call.ReplyErrorOccurred(err.Error()) 650 } 651 history := []v1.History{ 652 {Comment: message}, 653 } 654 config := v1.Image{ 655 Config: configChanges.ImageConfig, 656 History: history, 657 } 658 newImage, err := i.Runtime.ImageRuntime().Import(getContext(), source, reference, nil, image.SigningOptions{}, config) 659 if err != nil { 660 return call.ReplyErrorOccurred(err.Error()) 661 } 662 if delete { 663 if err := os.Remove(source); err != nil { 664 return call.ReplyErrorOccurred(err.Error()) 665 } 666 } 667 668 return call.ReplyImportImage(newImage.ID()) 669 } 670 671 // ExportImage exports an image to the provided destination 672 // destination must have the transport type!! 673 func (i *VarlinkAPI) ExportImage(call iopodman.VarlinkCall, name, destination string, compress bool, tags []string) error { 674 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) 675 if err != nil { 676 return call.ReplyImageNotFound(name, err.Error()) 677 } 678 679 additionalTags, err := image.GetAdditionalTags(tags) 680 if err != nil { 681 return err 682 } 683 684 if err := newImage.PushImageToHeuristicDestination(getContext(), destination, "", "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, additionalTags); err != nil { 685 return call.ReplyErrorOccurred(err.Error()) 686 } 687 return call.ReplyExportImage(newImage.ID()) 688 } 689 690 // PullImage pulls an image from a registry to the image store. 691 func (i *VarlinkAPI) PullImage(call iopodman.VarlinkCall, name string, creds iopodman.AuthConfig) error { 692 var ( 693 imageID string 694 err error 695 ) 696 dockerRegistryOptions := image.DockerRegistryOptions{ 697 DockerRegistryCreds: &types.DockerAuthConfig{ 698 Username: creds.Username, 699 Password: creds.Password, 700 }, 701 } 702 703 so := image.SigningOptions{} 704 705 if call.WantsMore() { 706 call.Continues = true 707 } 708 output := channelwriter.NewChannelWriter() 709 channelClose := func() { 710 if err := output.Close(); err != nil { 711 logrus.Errorf("failed to close channel writer: %q", err) 712 } 713 } 714 defer channelClose() 715 c := make(chan error) 716 defer close(c) 717 718 go func() { 719 var foundError bool 720 if strings.HasPrefix(name, dockerarchive.Transport.Name()+":") { 721 srcRef, err := alltransports.ParseImageName(name) 722 if err != nil { 723 c <- errors.Wrapf(err, "error parsing %q", name) 724 } 725 newImage, err := i.Runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, "", output) 726 if err != nil { 727 foundError = true 728 c <- errors.Wrapf(err, "error pulling image from %q", name) 729 } else { 730 imageID = newImage[0].ID() 731 } 732 } else { 733 newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, "", "", output, &dockerRegistryOptions, so, nil, util.PullImageMissing) 734 if err != nil { 735 foundError = true 736 c <- errors.Wrapf(err, "unable to pull %s", name) 737 } else { 738 imageID = newImage.ID() 739 } 740 } 741 if !foundError { 742 c <- nil 743 } 744 }() 745 746 var log []string 747 reply := func(br iopodman.MoreResponse) error { 748 return call.ReplyPullImage(br) 749 } 750 log, err = forwardOutput(log, c, call.WantsMore(), output, reply) 751 if err != nil { 752 return call.ReplyErrorOccurred(err.Error()) 753 } 754 call.Continues = false 755 br := iopodman.MoreResponse{ 756 Logs: log, 757 Id: imageID, 758 } 759 return call.ReplyPullImage(br) 760 } 761 762 // ImageExists returns bool as to whether the input image exists in local storage 763 func (i *VarlinkAPI) ImageExists(call iopodman.VarlinkCall, name string) error { 764 _, err := i.Runtime.ImageRuntime().NewFromLocal(name) 765 if errors.Cause(err) == image.ErrNoSuchImage { 766 return call.ReplyImageExists(1) 767 } 768 if err != nil { 769 return call.ReplyErrorOccurred(err.Error()) 770 } 771 return call.ReplyImageExists(0) 772 } 773 774 // ContainerRunlabel ... 775 func (i *VarlinkAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { 776 ctx := getContext() 777 dockerRegistryOptions := image.DockerRegistryOptions{} 778 stdErr := os.Stderr 779 stdOut := os.Stdout 780 stdIn := os.Stdin 781 782 runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, "", dockerRegistryOptions, input.Authfile, "", nil) 783 if err != nil { 784 return call.ReplyErrorOccurred(err.Error()) 785 } 786 if runLabel == "" { 787 return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label)) 788 } 789 790 cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "") 791 if err != nil { 792 return call.ReplyErrorOccurred(err.Error()) 793 } 794 if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil { 795 return call.ReplyErrorOccurred(err.Error()) 796 } 797 return call.ReplyContainerRunlabel() 798 } 799 800 // ImagesPrune .... 801 func (i *VarlinkAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error { 802 prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all, []string{}) 803 if err != nil { 804 return call.ReplyErrorOccurred(err.Error()) 805 } 806 return call.ReplyImagesPrune(prunedImages) 807 } 808 809 // ImageSave .... 810 func (i *VarlinkAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error { 811 newImage, err := i.Runtime.ImageRuntime().NewFromLocal(options.Name) 812 if err != nil { 813 if errors.Cause(err) == define.ErrNoSuchImage { 814 return call.ReplyImageNotFound(options.Name, err.Error()) 815 } 816 return call.ReplyErrorOccurred(err.Error()) 817 } 818 819 // Determine if we are dealing with a tarball or dir 820 var output string 821 outputToDir := false 822 if options.Format == "oci-archive" || options.Format == "docker-archive" { 823 tempfile, err := ioutil.TempFile("", "varlink_send") 824 if err != nil { 825 return call.ReplyErrorOccurred(err.Error()) 826 } 827 output = tempfile.Name() 828 tempfile.Close() 829 } else { 830 var err error 831 outputToDir = true 832 output, err = ioutil.TempDir("", "varlink_send") 833 if err != nil { 834 return call.ReplyErrorOccurred(err.Error()) 835 } 836 } 837 if err != nil { 838 return call.ReplyErrorOccurred(err.Error()) 839 } 840 if call.WantsMore() { 841 call.Continues = true 842 } 843 844 saveOutput := bytes.NewBuffer([]byte{}) 845 c := make(chan error) 846 go func() { 847 err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress) 848 c <- err 849 close(c) 850 }() 851 var log []string 852 done := false 853 for { 854 line, err := saveOutput.ReadString('\n') 855 if err == nil { 856 log = append(log, line) 857 continue 858 } else if err == io.EOF { 859 select { 860 case err := <-c: 861 if err != nil { 862 logrus.Errorf("reading of output during save failed for %s", newImage.ID()) 863 return call.ReplyErrorOccurred(err.Error()) 864 } 865 done = true 866 default: 867 if !call.WantsMore() { 868 break 869 } 870 br := iopodman.MoreResponse{ 871 Logs: log, 872 } 873 call.ReplyImageSave(br) 874 log = []string{} 875 } 876 } else { 877 return call.ReplyErrorOccurred(err.Error()) 878 } 879 if done { 880 break 881 } 882 } 883 call.Continues = false 884 885 sendfile := output 886 // Image has been saved to `output` 887 if outputToDir { 888 // If the output is a directory, we need to tar up the directory to send it back 889 // Create a tempfile for the directory tarball 890 outputFile, err := ioutil.TempFile("", "varlink_save_dir") 891 if err != nil { 892 return err 893 } 894 defer outputFile.Close() 895 if err := utils.TarToFilesystem(output, outputFile); err != nil { 896 return call.ReplyErrorOccurred(err.Error()) 897 } 898 sendfile = outputFile.Name() 899 } 900 br := iopodman.MoreResponse{ 901 Logs: log, 902 Id: sendfile, 903 } 904 return call.ReplyPushImage(br) 905 } 906 907 // LoadImage ... 908 func (i *VarlinkAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, deleteInputFile, quiet bool) error { 909 var ( 910 names string 911 writer io.Writer 912 err error 913 ) 914 if !quiet { 915 writer = os.Stderr 916 } 917 918 if call.WantsMore() { 919 call.Continues = true 920 } 921 output := bytes.NewBuffer([]byte{}) 922 923 c := make(chan error) 924 go func() { 925 names, err = i.Runtime.LoadImage(getContext(), name, inputFile, writer, "") 926 c <- err 927 close(c) 928 }() 929 930 var log []string 931 done := false 932 for { 933 line, err := output.ReadString('\n') 934 if err == nil { 935 log = append(log, line) 936 continue 937 } else if err == io.EOF { 938 select { 939 case err := <-c: 940 if err != nil { 941 logrus.Error(err) 942 return call.ReplyErrorOccurred(err.Error()) 943 } 944 done = true 945 default: 946 if !call.WantsMore() { 947 break 948 } 949 br := iopodman.MoreResponse{ 950 Logs: log, 951 } 952 call.ReplyLoadImage(br) 953 log = []string{} 954 } 955 } else { 956 return call.ReplyErrorOccurred(err.Error()) 957 } 958 if done { 959 break 960 } 961 } 962 call.Continues = false 963 964 br := iopodman.MoreResponse{ 965 Logs: log, 966 Id: names, 967 } 968 if deleteInputFile { 969 if err := os.Remove(inputFile); err != nil { 970 logrus.Errorf("unable to delete input file %s", inputFile) 971 } 972 } 973 return call.ReplyLoadImage(br) 974 } 975 976 // Diff ... 977 func (i *VarlinkAPI) Diff(call iopodman.VarlinkCall, name string) error { 978 var response []iopodman.DiffInfo 979 changes, err := i.Runtime.GetDiff("", name) 980 if err != nil { 981 return call.ReplyErrorOccurred(err.Error()) 982 } 983 for _, change := range changes { 984 response = append(response, iopodman.DiffInfo{Path: change.Path, ChangeType: change.Kind.String()}) 985 } 986 return call.ReplyDiff(response) 987 } 988 989 // GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image. 990 func (i *VarlinkAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { 991 layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) 992 if err != nil { 993 return call.ReplyErrorOccurred(err.Error()) 994 } 995 b, err := json.Marshal(layerInfo) 996 if err != nil { 997 return call.ReplyErrorOccurred(err.Error()) 998 } 999 return call.ReplyGetLayersMapWithImageInfo(string(b)) 1000 } 1001 1002 // BuildImageHierarchyMap ... 1003 func (i *VarlinkAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { 1004 img, err := i.Runtime.ImageRuntime().NewFromLocal(name) 1005 if err != nil { 1006 return call.ReplyErrorOccurred(err.Error()) 1007 } 1008 imageInfo := &image.InfoImage{ 1009 ID: img.ID(), 1010 Tags: img.Names(), 1011 } 1012 layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) 1013 if err != nil { 1014 return call.ReplyErrorOccurred(err.Error()) 1015 } 1016 if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil { 1017 return call.ReplyErrorOccurred(err.Error()) 1018 } 1019 b, err := json.Marshal(imageInfo) 1020 if err != nil { 1021 return call.ReplyErrorOccurred(err.Error()) 1022 } 1023 return call.ReplyBuildImageHierarchyMap(string(b)) 1024 } 1025 1026 // ImageTree returns the image tree string for the provided image name or ID 1027 func (i *VarlinkAPI) ImageTree(call iopodman.VarlinkCall, nameOrID string, whatRequires bool) error { 1028 img, err := i.Runtime.ImageRuntime().NewFromLocal(nameOrID) 1029 if err != nil { 1030 return call.ReplyErrorOccurred(err.Error()) 1031 } 1032 1033 tree, err := img.GenerateTree(whatRequires) 1034 if err != nil { 1035 return call.ReplyErrorOccurred(err.Error()) 1036 } 1037 return call.ReplyImageTree(tree) 1038 }