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  }