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