github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/domain/infra/abi/images.go (about) 1 package abi 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "github.com/containers/image/v5/types" 8 "io/ioutil" 9 "net/url" 10 "os" 11 "os/exec" 12 "os/user" 13 "path" 14 "path/filepath" 15 "strconv" 16 "strings" 17 "syscall" 18 19 "github.com/containers/common/libimage" 20 "github.com/containers/common/pkg/config" 21 "github.com/containers/image/v5/docker" 22 "github.com/containers/image/v5/docker/reference" 23 "github.com/containers/image/v5/manifest" 24 "github.com/containers/image/v5/pkg/compression" 25 "github.com/containers/image/v5/signature" 26 "github.com/containers/image/v5/transports" 27 "github.com/containers/image/v5/transports/alltransports" 28 "github.com/containers/storage" 29 dockerRef "github.com/docker/distribution/reference" 30 "github.com/hanks177/podman/v4/libpod/define" 31 "github.com/hanks177/podman/v4/pkg/domain/entities" 32 "github.com/hanks177/podman/v4/pkg/domain/entities/reports" 33 domainUtils "github.com/hanks177/podman/v4/pkg/domain/utils" 34 "github.com/hanks177/podman/v4/pkg/errorhandling" 35 "github.com/hanks177/podman/v4/pkg/rootless" 36 "github.com/hanks177/podman/v4/utils" 37 "github.com/opencontainers/go-digest" 38 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" 39 "github.com/pkg/errors" 40 "github.com/sirupsen/logrus" 41 ) 42 43 func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) { 44 exists, err := ir.Libpod.LibimageRuntime().Exists(nameOrID) 45 if err != nil { 46 return nil, err 47 } 48 return &entities.BoolReport{Value: exists}, nil 49 } 50 51 func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) { 52 pruneOptions := &libimage.RemoveImagesOptions{ 53 RemoveContainerFunc: ir.Libpod.RemoveContainersForImageCallback(ctx), 54 IsExternalContainerFunc: ir.Libpod.IsExternalContainerCallback(ctx), 55 ExternalContainers: opts.External, 56 Filters: append(opts.Filter, "readonly=false"), 57 WithSize: true, 58 } 59 60 if !opts.All { 61 pruneOptions.Filters = append(pruneOptions.Filters, "dangling=true") 62 } 63 if opts.External { 64 pruneOptions.Filters = append(pruneOptions.Filters, "containers=external") 65 } else { 66 pruneOptions.Filters = append(pruneOptions.Filters, "containers=false") 67 } 68 69 pruneReports := make([]*reports.PruneReport, 0) 70 71 // Now prune all images until we converge. 72 numPreviouslyRemovedImages := 1 73 for { 74 removedImages, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, pruneOptions) 75 if rmErrors != nil { 76 return nil, errorhandling.JoinErrors(rmErrors) 77 } 78 79 for _, rmReport := range removedImages { 80 r := *rmReport 81 pruneReports = append(pruneReports, &reports.PruneReport{ 82 Id: r.ID, 83 Size: uint64(r.Size), 84 }) 85 } 86 87 numRemovedImages := len(removedImages) 88 if numRemovedImages+numPreviouslyRemovedImages == 0 { 89 break 90 } 91 numPreviouslyRemovedImages = numRemovedImages 92 } 93 94 return pruneReports, nil 95 } 96 97 func toDomainHistoryLayer(layer *libimage.ImageHistory) entities.ImageHistoryLayer { 98 l := entities.ImageHistoryLayer{} 99 l.ID = layer.ID 100 if layer.Created != nil { 101 l.Created = *layer.Created 102 } 103 l.CreatedBy = layer.CreatedBy 104 copy(l.Tags, layer.Tags) 105 l.Size = layer.Size 106 l.Comment = layer.Comment 107 return l 108 } 109 110 func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { 111 image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) 112 if err != nil { 113 return nil, err 114 } 115 116 results, err := image.History(ctx) 117 if err != nil { 118 return nil, err 119 } 120 121 history := entities.ImageHistoryReport{ 122 Layers: make([]entities.ImageHistoryLayer, len(results)), 123 } 124 125 for i := range results { 126 history.Layers[i] = toDomainHistoryLayer(&results[i]) 127 } 128 return &history, nil 129 } 130 131 func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entities.ImageMountOptions) ([]*entities.ImageMountReport, error) { 132 if opts.All && len(nameOrIDs) > 0 { 133 return nil, errors.Errorf("cannot mix --all with images") 134 } 135 136 if os.Geteuid() != 0 { 137 if driver := ir.Libpod.StorageConfig().GraphDriverName; driver != "vfs" { 138 // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part 139 // of the mount command. 140 return nil, errors.Errorf("cannot mount using driver %s in rootless mode", driver) 141 } 142 143 became, ret, err := rootless.BecomeRootInUserNS("") 144 if err != nil { 145 return nil, err 146 } 147 if became { 148 os.Exit(ret) 149 } 150 } 151 152 listImagesOptions := &libimage.ListImagesOptions{} 153 if opts.All { 154 listImagesOptions.Filters = []string{"readonly=false"} 155 } 156 images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) 157 if err != nil { 158 return nil, err 159 } 160 161 mountReports := []*entities.ImageMountReport{} 162 listMountsOnly := !opts.All && len(nameOrIDs) == 0 163 for _, i := range images { 164 var mountPoint string 165 var err error 166 if listMountsOnly { 167 // We're only looking for mounted images. 168 mountPoint, err = i.Mountpoint() 169 if err != nil { 170 return nil, err 171 } 172 // Not mounted, so skip. 173 if mountPoint == "" { 174 continue 175 } 176 } else { 177 mountPoint, err = i.Mount(ctx, nil, "") 178 if err != nil { 179 return nil, err 180 } 181 } 182 183 tags, err := i.RepoTags() 184 if err != nil { 185 return nil, err 186 } 187 mountReports = append(mountReports, &entities.ImageMountReport{ 188 Id: i.ID(), 189 Name: string(i.Digest()), 190 Repositories: tags, 191 Path: mountPoint, 192 }) 193 } 194 return mountReports, nil 195 } 196 197 func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { 198 if options.All && len(nameOrIDs) > 0 { 199 return nil, errors.Errorf("cannot mix --all with images") 200 } 201 202 listImagesOptions := &libimage.ListImagesOptions{} 203 if options.All { 204 listImagesOptions.Filters = []string{"readonly=false"} 205 } 206 images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) 207 if err != nil { 208 return nil, err 209 } 210 211 unmountReports := []*entities.ImageUnmountReport{} 212 for _, image := range images { 213 r := &entities.ImageUnmountReport{Id: image.ID()} 214 mountPoint, err := image.Mountpoint() 215 if err != nil { 216 r.Err = err 217 unmountReports = append(unmountReports, r) 218 continue 219 } 220 if mountPoint == "" { 221 // Skip if the image wasn't mounted. 222 continue 223 } 224 r.Err = image.Unmount(options.Force) 225 unmountReports = append(unmountReports, r) 226 } 227 return unmountReports, nil 228 } 229 230 type pullResult struct { 231 images []*libimage.Image 232 err error 233 } 234 235 func (ir *ImageEngine) PullImage(ctx context.Context, rawImage string, pullOptions *libimage.PullOptions) error { 236 progress := make(chan types.ProgressProperties) 237 pullOptions.Progress = progress 238 239 pullResChan := make(chan pullResult) 240 go func() { 241 pulledImages, err := ir.Libpod.LibimageRuntime().Pull(ctx, rawImage, config.PullPolicyMissing, pullOptions) 242 pullResChan <- pullResult{images: pulledImages, err: err} 243 }() 244 245 enc := json.NewEncoder(os.Stdout) 246 for { 247 var report struct { 248 Stream string `json:"stream,omitempty"` 249 Status string `json:"status,omitempty"` 250 Progress struct { 251 Current uint64 `json:"current,omitempty"` 252 Total int64 `json:"total,omitempty"` 253 } `json:"progressDetail,omitempty"` 254 Error string `json:"error,omitempty"` 255 Id string `json:"id,omitempty"` // nolint 256 } 257 select { 258 case e := <-progress: 259 switch e.Event { 260 case types.ProgressEventNewArtifact: 261 report.Status = "Pulling fs layer" 262 case types.ProgressEventRead: 263 report.Status = "Downloading" 264 report.Progress.Current = e.Offset 265 report.Progress.Total = e.Artifact.Size 266 case types.ProgressEventSkipped: 267 report.Status = "Already exists" 268 case types.ProgressEventDone: 269 report.Status = "Download complete" 270 } 271 report.Id = e.Artifact.Digest.Encoded()[0:12] 272 if err := enc.Encode(report); err != nil { 273 return fmt.Errorf("failed to json encode error %v", err) 274 } 275 case pullRes := <-pullResChan: 276 err := pullRes.err 277 pulledImages := pullRes.images 278 if err != nil { 279 report.Error = err.Error() 280 } else { 281 if len(pulledImages) > 0 { 282 img := pulledImages[0].ID() 283 report.Status = "Pull complete" 284 report.Id = img[0:12] 285 } else { 286 report.Error = "internal error: no images pulled" 287 } 288 } 289 if err = enc.Encode(report); err != nil { 290 return fmt.Errorf("failed to json encode error %v", err) 291 } 292 return nil 293 } 294 } 295 } 296 297 func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { 298 pullOptions := &libimage.PullOptions{AllTags: options.AllTags} 299 pullOptions.AuthFilePath = options.Authfile 300 pullOptions.CertDirPath = options.CertDir 301 pullOptions.Username = options.Username 302 pullOptions.Password = options.Password 303 pullOptions.Architecture = options.Arch 304 pullOptions.OS = options.OS 305 pullOptions.Variant = options.Variant 306 pullOptions.SignaturePolicyPath = options.SignaturePolicy 307 pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify 308 309 if !options.Quiet { 310 pullOptions.Writer = os.Stderr 311 } 312 313 pulledImages, err := ir.Libpod.LibimageRuntime().Pull(ctx, rawImage, options.PullPolicy, pullOptions) 314 if err != nil { 315 return nil, err 316 } 317 318 pulledIDs := make([]string, len(pulledImages)) 319 for i := range pulledImages { 320 pulledIDs[i] = pulledImages[i].ID() 321 } 322 323 return &entities.ImagePullReport{Images: pulledIDs}, nil 324 } 325 326 func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { 327 reports := []*entities.ImageInspectReport{} 328 errs := []error{} 329 330 inspectOptions := &libimage.InspectOptions{WithParent: true, WithSize: true} 331 for _, i := range namesOrIDs { 332 img, _, err := ir.Libpod.LibimageRuntime().LookupImage(i, nil) 333 if err != nil { 334 // This is probably a no such image, treat as nonfatal. 335 errs = append(errs, err) 336 continue 337 } 338 result, err := img.Inspect(ctx, inspectOptions) 339 if err != nil { 340 // This is more likely to be fatal. 341 return nil, nil, err 342 } 343 report := entities.ImageInspectReport{} 344 if err := domainUtils.DeepCopy(&report, result); err != nil { 345 return nil, nil, err 346 } 347 reports = append(reports, &report) 348 } 349 return reports, errs, nil 350 } 351 352 func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { 353 var manifestType string 354 switch options.Format { 355 case "": 356 // Default 357 case "oci": 358 manifestType = imgspecv1.MediaTypeImageManifest 359 case "v2s1": 360 manifestType = manifest.DockerV2Schema1SignedMediaType 361 case "v2s2", "docker": 362 manifestType = manifest.DockerV2Schema2MediaType 363 default: 364 return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) 365 } 366 367 pushOptions := &libimage.PushOptions{} 368 pushOptions.AuthFilePath = options.Authfile 369 pushOptions.CertDirPath = options.CertDir 370 pushOptions.DirForceCompress = options.Compress 371 pushOptions.Username = options.Username 372 pushOptions.Password = options.Password 373 pushOptions.ManifestMIMEType = manifestType 374 pushOptions.RemoveSignatures = options.RemoveSignatures 375 pushOptions.SignBy = options.SignBy 376 pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify 377 378 compressionFormat := options.CompressionFormat 379 if compressionFormat == "" { 380 config, err := ir.Libpod.GetConfigNoCopy() 381 if err != nil { 382 return err 383 } 384 compressionFormat = config.Engine.CompressionFormat 385 } 386 if compressionFormat != "" { 387 algo, err := compression.AlgorithmByName(compressionFormat) 388 if err != nil { 389 return err 390 } 391 pushOptions.CompressionFormat = &algo 392 } 393 394 if !options.Quiet { 395 pushOptions.Writer = os.Stderr 396 } 397 398 pushedManifestBytes, pushError := ir.Libpod.LibimageRuntime().Push(ctx, source, destination, pushOptions) 399 if pushError == nil { 400 if options.DigestFile != "" { 401 manifestDigest, err := manifest.Digest(pushedManifestBytes) 402 if err != nil { 403 return err 404 } 405 406 if err := ioutil.WriteFile(options.DigestFile, []byte(manifestDigest.String()), 0644); err != nil { 407 return err 408 } 409 } 410 return nil 411 } 412 // If the image could not be found, we may be referring to a manifest 413 // list but could not find a matching image instance in the local 414 // containers storage. In that case, fall back and attempt to push the 415 // (entire) manifest. 416 if _, err := ir.Libpod.LibimageRuntime().LookupManifestList(source); err == nil { 417 _, err := ir.ManifestPush(ctx, source, destination, options) 418 return err 419 } 420 return pushError 421 } 422 423 // Transfer moves images between root and rootless storage so the user specified in the scp call can access and use the image modified by root 424 func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error { 425 if source.User == "" { 426 return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage") 427 } 428 podman, err := os.Executable() 429 if err != nil { 430 return err 431 } 432 if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo 433 return transferRootless(source, dest, podman, parentFlags) 434 } 435 return transferRootful(source, dest, podman, parentFlags) 436 } 437 438 func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error { 439 // Allow tagging manifest list instead of resolving instances from manifest 440 lookupOptions := &libimage.LookupImageOptions{ManifestList: true} 441 image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, lookupOptions) 442 if err != nil { 443 return err 444 } 445 for _, tag := range tags { 446 if err := image.Tag(tag); err != nil { 447 return err 448 } 449 } 450 return nil 451 } 452 453 func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string, options entities.ImageUntagOptions) error { 454 image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) 455 if err != nil { 456 return err 457 } 458 // If only one arg is provided, all names are to be untagged 459 if len(tags) == 0 { 460 tags = image.Names() 461 } 462 for _, tag := range tags { 463 if err := image.Untag(tag); err != nil { 464 return err 465 } 466 } 467 return nil 468 } 469 470 func (ir *ImageEngine) Load(ctx context.Context, options entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { 471 loadOptions := &libimage.LoadOptions{} 472 loadOptions.SignaturePolicyPath = options.SignaturePolicy 473 if !options.Quiet { 474 loadOptions.Writer = os.Stderr 475 } 476 477 loadedImages, err := ir.Libpod.LibimageRuntime().Load(ctx, options.Input, loadOptions) 478 if err != nil { 479 return nil, err 480 } 481 return &entities.ImageLoadReport{Names: loadedImages}, nil 482 } 483 484 func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { 485 saveOptions := &libimage.SaveOptions{} 486 saveOptions.DirForceCompress = options.Compress 487 saveOptions.OciAcceptUncompressedLayers = options.OciAcceptUncompressedLayers 488 489 // Force signature removal to preserve backwards compat. 490 // See https://github.com/containers/podman/pull/11669#issuecomment-925250264 491 saveOptions.RemoveSignatures = true 492 493 if !options.Quiet { 494 saveOptions.Writer = os.Stderr 495 } 496 497 names := []string{nameOrID} 498 if options.MultiImageArchive { 499 names = append(names, tags...) 500 } else { 501 saveOptions.AdditionalTags = tags 502 } 503 return ir.Libpod.LibimageRuntime().Save(ctx, names, options.Format, options.Output, saveOptions) 504 } 505 506 func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportOptions) (*entities.ImageImportReport, error) { 507 importOptions := &libimage.ImportOptions{} 508 importOptions.Changes = options.Changes 509 importOptions.CommitMessage = options.Message 510 importOptions.Tag = options.Reference 511 importOptions.SignaturePolicyPath = options.SignaturePolicy 512 importOptions.OS = options.OS 513 importOptions.Arch = options.Architecture 514 importOptions.Variant = options.Variant 515 516 if !options.Quiet { 517 importOptions.Writer = os.Stderr 518 } 519 520 imageID, err := ir.Libpod.LibimageRuntime().Import(ctx, options.Source, importOptions) 521 if err != nil { 522 return nil, err 523 } 524 525 return &entities.ImageImportReport{Id: imageID}, nil 526 } 527 528 // Search for images using term and filters 529 func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { 530 filter, err := libimage.ParseSearchFilter(opts.Filters) 531 if err != nil { 532 return nil, err 533 } 534 535 searchOptions := &libimage.SearchOptions{ 536 Authfile: opts.Authfile, 537 Filter: *filter, 538 Limit: opts.Limit, 539 NoTrunc: true, 540 InsecureSkipTLSVerify: opts.SkipTLSVerify, 541 ListTags: opts.ListTags, 542 } 543 544 searchResults, err := ir.Libpod.LibimageRuntime().Search(ctx, term, searchOptions) 545 if err != nil { 546 return nil, err 547 } 548 549 // Convert from image.SearchResults to entities.ImageSearchReport. We don't 550 // want to leak any low-level packages into the remote client, which 551 // requires converting. 552 reports := make([]entities.ImageSearchReport, len(searchResults)) 553 for i := range searchResults { 554 reports[i].Index = searchResults[i].Index 555 reports[i].Name = searchResults[i].Name 556 reports[i].Description = searchResults[i].Description 557 reports[i].Stars = searchResults[i].Stars 558 reports[i].Official = searchResults[i].Official 559 reports[i].Automated = searchResults[i].Automated 560 reports[i].Tag = searchResults[i].Tag 561 } 562 563 return reports, nil 564 } 565 566 // Config returns a copy of the configuration used by the runtime 567 func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { 568 return ir.Libpod.GetConfig() 569 } 570 571 func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) { 572 id, _, err := ir.Libpod.Build(ctx, opts.BuildOptions, containerFiles...) 573 if err != nil { 574 return nil, err 575 } 576 return &entities.BuildReport{ID: id}, nil 577 } 578 579 func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { 580 image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) 581 if err != nil { 582 return nil, err 583 } 584 tree, err := image.Tree(opts.WhatRequires) 585 if err != nil { 586 return nil, err 587 } 588 return &entities.ImageTreeReport{Tree: tree}, nil 589 } 590 591 // removeErrorsToExitCode returns an exit code for the specified slice of 592 // image-removal errors. The error codes are set according to the documented 593 // behaviour in the Podman man pages. 594 func removeErrorsToExitCode(rmErrors []error) int { 595 var ( 596 // noSuchImageErrors indicates that at least one image was not found. 597 noSuchImageErrors bool 598 // inUseErrors indicates that at least one image is being used by a 599 // container. 600 inUseErrors bool 601 // otherErrors indicates that at least one error other than the two 602 // above occurred. 603 otherErrors bool 604 ) 605 606 if len(rmErrors) == 0 { 607 return 0 608 } 609 610 for _, e := range rmErrors { 611 switch errors.Cause(e) { 612 case storage.ErrImageUnknown, storage.ErrLayerUnknown: 613 noSuchImageErrors = true 614 case storage.ErrImageUsedByContainer: 615 inUseErrors = true 616 default: 617 otherErrors = true 618 } 619 } 620 621 switch { 622 case inUseErrors: 623 // One of the specified images has child images or is 624 // being used by a container. 625 return 2 626 case noSuchImageErrors && !(otherErrors || inUseErrors): 627 // One of the specified images did not exist, and no other 628 // failures. 629 return 1 630 default: 631 return 125 632 } 633 } 634 635 // Remove removes one or more images from local storage. 636 func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) { 637 report = &entities.ImageRemoveReport{} 638 639 // Set the exit code at very end. 640 defer func() { 641 report.ExitCode = removeErrorsToExitCode(rmErrors) 642 }() 643 644 libimageOptions := &libimage.RemoveImagesOptions{} 645 libimageOptions.Filters = []string{"readonly=false"} 646 libimageOptions.Force = opts.Force 647 libimageOptions.Ignore = opts.Ignore 648 libimageOptions.LookupManifest = opts.LookupManifest 649 if !opts.All { 650 libimageOptions.Filters = append(libimageOptions.Filters, "intermediate=false") 651 } 652 libimageOptions.RemoveContainerFunc = ir.Libpod.RemoveContainersForImageCallback(ctx) 653 654 libimageReport, libimageErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, images, libimageOptions) 655 656 for _, r := range libimageReport { 657 if r.Removed { 658 report.Deleted = append(report.Deleted, r.ID) 659 } 660 report.Untagged = append(report.Untagged, r.Untagged...) 661 } 662 663 rmErrors = libimageErrors 664 665 return //nolint 666 } 667 668 // Shutdown Libpod engine 669 func (ir *ImageEngine) Shutdown(_ context.Context) { 670 shutdownSync.Do(func() { 671 _ = ir.Libpod.Shutdown(false) 672 }) 673 } 674 675 func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { 676 mech, err := signature.NewGPGSigningMechanism() 677 if err != nil { 678 return nil, errors.Wrap(err, "error initializing GPG") 679 } 680 defer mech.Close() 681 if err := mech.SupportsSigning(); err != nil { 682 return nil, errors.Wrap(err, "signing is not supported") 683 } 684 sc := ir.Libpod.SystemContext() 685 sc.DockerCertPath = options.CertDir 686 sc.AuthFilePath = options.Authfile 687 688 for _, signimage := range names { 689 err = func() error { 690 srcRef, err := alltransports.ParseImageName(signimage) 691 if err != nil { 692 return errors.Wrapf(err, "error parsing image name") 693 } 694 rawSource, err := srcRef.NewImageSource(ctx, sc) 695 if err != nil { 696 return errors.Wrapf(err, "error getting image source") 697 } 698 defer func() { 699 if err = rawSource.Close(); err != nil { 700 logrus.Errorf("Unable to close %s image source %q", srcRef.DockerReference().Name(), err) 701 } 702 }() 703 topManifestBlob, manifestType, err := rawSource.GetManifest(ctx, nil) 704 if err != nil { 705 return errors.Wrapf(err, "error getting manifest blob") 706 } 707 dockerReference := rawSource.Reference().DockerReference() 708 if dockerReference == nil { 709 return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) 710 } 711 var sigStoreDir string 712 if options.Directory != "" { 713 repo := reference.Path(dockerReference) 714 if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references 715 return errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) 716 } 717 sigStoreDir = filepath.Join(options.Directory, repo) 718 } else { 719 signatureURL, err := docker.SignatureStorageBaseURL(sc, rawSource.Reference(), true) 720 if err != nil { 721 return err 722 } 723 sigStoreDir, err = localPathFromURI(signatureURL) 724 if err != nil { 725 return err 726 } 727 } 728 manifestDigest, err := manifest.Digest(topManifestBlob) 729 if err != nil { 730 return err 731 } 732 733 if options.All { 734 if !manifest.MIMETypeIsMultiImage(manifestType) { 735 return errors.Errorf("%s is not a multi-architecture image (manifest type %s)", signimage, manifestType) 736 } 737 list, err := manifest.ListFromBlob(topManifestBlob, manifestType) 738 if err != nil { 739 return errors.Wrapf(err, "Error parsing manifest list %q", string(topManifestBlob)) 740 } 741 instanceDigests := list.Instances() 742 for _, instanceDigest := range instanceDigests { 743 digest := instanceDigest 744 man, _, err := rawSource.GetManifest(ctx, &digest) 745 if err != nil { 746 return err 747 } 748 if err = putSignature(man, mech, sigStoreDir, instanceDigest, dockerReference, options); err != nil { 749 return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), instanceDigest) 750 } 751 } 752 return nil 753 } 754 if err = putSignature(topManifestBlob, mech, sigStoreDir, manifestDigest, dockerReference, options); err != nil { 755 return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), manifestDigest) 756 } 757 return nil 758 }() 759 if err != nil { 760 return nil, err 761 } 762 } 763 return nil, nil 764 } 765 766 func getSigFilename(sigStoreDirPath string) (string, error) { 767 sigFileSuffix := 1 768 sigFiles, err := ioutil.ReadDir(sigStoreDirPath) 769 if err != nil { 770 return "", err 771 } 772 sigFilenames := make(map[string]bool) 773 for _, file := range sigFiles { 774 sigFilenames[file.Name()] = true 775 } 776 for { 777 sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) 778 if _, exists := sigFilenames[sigFilename]; !exists { 779 return sigFilename, nil 780 } 781 sigFileSuffix++ 782 } 783 } 784 785 func localPathFromURI(url *url.URL) (string, error) { 786 if url.Scheme != "file" { 787 return "", errors.Errorf("writing to %s is not supported. Use a supported scheme", url.String()) 788 } 789 return url.Path, nil 790 } 791 792 // putSignature creates signature and saves it to the signstore file 793 func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error { 794 newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy) 795 if err != nil { 796 return err 797 } 798 signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex()) 799 if err := os.MkdirAll(signatureDir, 0751); err != nil { 800 // The directory is allowed to exist 801 if !os.IsExist(err) { 802 return err 803 } 804 } 805 sigFilename, err := getSigFilename(signatureDir) 806 if err != nil { 807 return err 808 } 809 if err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644); err != nil { 810 return err 811 } 812 return nil 813 } 814 815 // TransferRootless creates new podman processes using exec.Command and sudo, transferring images between the given source and destination users 816 func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error { 817 var cmdSave *exec.Cmd 818 saveCommand, loadCommand := parentFlags, parentFlags 819 saveCommand = append(saveCommand, []string{"save"}...) 820 loadCommand = append(loadCommand, []string{"load"}...) 821 if source.Quiet { 822 saveCommand = append(saveCommand, "-q") 823 loadCommand = append(loadCommand, "-q") 824 } 825 826 saveCommand = append(saveCommand, []string{"--output", source.File, source.Image}...) 827 828 loadCommand = append(loadCommand, []string{"--input", dest.File}...) 829 830 if source.User == "root" { 831 cmdSave = exec.Command("sudo", podman) 832 } else { 833 cmdSave = exec.Command(podman) 834 } 835 cmdSave = utils.CreateSCPCommand(cmdSave, saveCommand) 836 logrus.Debugf("Executing save command: %q", cmdSave) 837 err := cmdSave.Run() 838 if err != nil { 839 return err 840 } 841 842 var cmdLoad *exec.Cmd 843 if source.User != "root" { 844 cmdLoad = exec.Command("sudo", podman) 845 } else { 846 cmdLoad = exec.Command(podman) 847 } 848 cmdLoad = utils.CreateSCPCommand(cmdLoad, loadCommand) 849 logrus.Debugf("Executing load command: %q", cmdLoad) 850 return cmdLoad.Run() 851 } 852 853 // transferRootful creates new podman processes using exec.Command and a new uid/gid alongside a cleared environment 854 func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error { 855 basicCommand := make([]string, 0, len(parentFlags)+1) 856 basicCommand = append(basicCommand, podman) 857 basicCommand = append(basicCommand, parentFlags...) 858 859 saveCommand := make([]string, 0, len(basicCommand)+4) 860 saveCommand = append(saveCommand, basicCommand...) 861 saveCommand = append(saveCommand, "save") 862 863 loadCommand := make([]string, 0, len(basicCommand)+3) 864 loadCommand = append(loadCommand, basicCommand...) 865 loadCommand = append(loadCommand, "load") 866 if source.Quiet { 867 saveCommand = append(saveCommand, "-q") 868 loadCommand = append(loadCommand, "-q") 869 } 870 saveCommand = append(saveCommand, []string{"--output", source.File, source.Image}...) 871 loadCommand = append(loadCommand, []string{"--input", dest.File}...) 872 873 // if executing using sudo or transferring between two users, the TransferRootless approach will not work, the new process needs to be set up 874 // with the proper uid and gid as well as environmental variables. 875 var uSave *user.User 876 var uLoad *user.User 877 var err error 878 source.User = strings.Split(source.User, ":")[0] // split in case provided with uid:gid 879 dest.User = strings.Split(dest.User, ":")[0] 880 uSave, err = lookupUser(source.User) 881 if err != nil { 882 return err 883 } 884 switch { 885 case dest.User != "": // if we are given a destination user, check that first 886 uLoad, err = lookupUser(dest.User) 887 if err != nil { 888 return err 889 } 890 case uSave.Name != "root": // else if we have no destination user, and source is not root that means we should be root 891 uLoad, err = user.LookupId("0") 892 if err != nil { 893 return err 894 } 895 default: // else if we have no dest user, and source user IS root, we want to be the default user. 896 uString := os.Getenv("SUDO_USER") 897 if uString == "" { 898 return errors.New("$SUDO_USER must be defined to find the default rootless user") 899 } 900 uLoad, err = user.Lookup(uString) 901 if err != nil { 902 return err 903 } 904 } 905 err = execPodman(uSave, saveCommand) 906 if err != nil { 907 return err 908 } 909 return execPodman(uLoad, loadCommand) 910 } 911 912 func lookupUser(u string) (*user.User, error) { 913 if u, err := user.LookupId(u); err == nil { 914 return u, nil 915 } 916 return user.Lookup(u) 917 } 918 919 func execPodman(execUser *user.User, command []string) error { 920 cmdLogin, err := utils.LoginUser(execUser.Username) 921 if err != nil { 922 return err 923 } 924 925 defer func() { 926 _ = cmdLogin.Process.Kill() 927 _ = cmdLogin.Wait() 928 }() 929 930 cmd := exec.Command(command[0], command[1:]...) 931 cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "TERM=" + os.Getenv("TERM")} 932 cmd.Stderr = os.Stderr 933 cmd.Stdout = os.Stdout 934 uid, err := strconv.ParseInt(execUser.Uid, 10, 32) 935 if err != nil { 936 return err 937 } 938 gid, err := strconv.ParseInt(execUser.Gid, 10, 32) 939 if err != nil { 940 return err 941 } 942 cmd.SysProcAttr = &syscall.SysProcAttr{ 943 Credential: &syscall.Credential{ 944 Uid: uint32(uid), 945 Gid: uint32(gid), 946 Groups: nil, 947 NoSetGroups: false, 948 }, 949 } 950 return cmd.Run() 951 }