github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/domain/infra/abi/images.go (about)

     1  // +build ABISupport
     2  
     3  package abi
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/containers/common/pkg/config"
    13  	"github.com/containers/image/v5/docker"
    14  	dockerarchive "github.com/containers/image/v5/docker/archive"
    15  	"github.com/containers/image/v5/docker/reference"
    16  	"github.com/containers/image/v5/manifest"
    17  	"github.com/containers/image/v5/transports/alltransports"
    18  	"github.com/containers/image/v5/types"
    19  	"github.com/containers/libpod/libpod/define"
    20  	"github.com/containers/libpod/libpod/image"
    21  	libpodImage "github.com/containers/libpod/libpod/image"
    22  	"github.com/containers/libpod/pkg/domain/entities"
    23  	domainUtils "github.com/containers/libpod/pkg/domain/utils"
    24  	"github.com/containers/libpod/pkg/util"
    25  	"github.com/containers/storage"
    26  	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
    27  	"github.com/pkg/errors"
    28  	"github.com/sirupsen/logrus"
    29  )
    30  
    31  func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
    32  	_, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
    33  	if err != nil && errors.Cause(err) != define.ErrNoSuchImage {
    34  		return nil, err
    35  	}
    36  	return &entities.BoolReport{Value: err == nil}, nil
    37  }
    38  
    39  func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
    40  	report := entities.ImageDeleteReport{}
    41  
    42  	if opts.All {
    43  		var previousTargets []*libpodImage.Image
    44  	repeatRun:
    45  		targets, err := ir.Libpod.ImageRuntime().GetRWImages()
    46  		if err != nil {
    47  			return &report, errors.Wrapf(err, "unable to query local images")
    48  		}
    49  		if len(targets) == 0 {
    50  			return &report, nil
    51  		}
    52  		if len(targets) > 0 && len(targets) == len(previousTargets) {
    53  			return &report, errors.New("unable to delete all images; re-run the rmi command again.")
    54  		}
    55  		previousTargets = targets
    56  
    57  		for _, img := range targets {
    58  			isParent, err := img.IsParent(ctx)
    59  			if err != nil {
    60  				return &report, err
    61  			}
    62  			if isParent {
    63  				continue
    64  			}
    65  			err = ir.deleteImage(ctx, img, opts, report)
    66  			report.Errors = append(report.Errors, err)
    67  		}
    68  		if len(previousTargets) != 1 {
    69  			goto repeatRun
    70  		}
    71  		return &report, nil
    72  	}
    73  
    74  	for _, id := range nameOrId {
    75  		image, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  
    80  		err = ir.deleteImage(ctx, image, opts, report)
    81  		if err != nil {
    82  			return &report, err
    83  		}
    84  	}
    85  	return &report, nil
    86  }
    87  
    88  func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error {
    89  	results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
    90  	switch errors.Cause(err) {
    91  	case nil:
    92  		break
    93  	case storage.ErrImageUsedByContainer:
    94  		report.ImageInUse = errors.New(
    95  			fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
    96  		return nil
    97  	case libpodImage.ErrNoSuchImage:
    98  		report.ImageNotFound = err
    99  		return nil
   100  	default:
   101  		return err
   102  	}
   103  
   104  	report.Deleted = append(report.Deleted, results.Deleted)
   105  	report.Untagged = append(report.Untagged, results.Untagged...)
   106  	return nil
   107  }
   108  
   109  func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
   110  	results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	report := entities.ImagePruneReport{
   116  		Report: entities.Report{
   117  			Id:  results,
   118  			Err: nil,
   119  		},
   120  		Size: 0,
   121  	}
   122  	return &report, nil
   123  }
   124  
   125  func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) {
   126  	image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	results, err := image.History(ctx)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	history := entities.ImageHistoryReport{
   136  		Layers: make([]entities.ImageHistoryLayer, len(results)),
   137  	}
   138  
   139  	for i, layer := range results {
   140  		history.Layers[i] = ToDomainHistoryLayer(layer)
   141  	}
   142  	return &history, nil
   143  }
   144  
   145  func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer {
   146  	l := entities.ImageHistoryLayer{}
   147  	l.ID = layer.ID
   148  	l.Created = *layer.Created
   149  	l.CreatedBy = layer.CreatedBy
   150  	copy(l.Tags, layer.Tags)
   151  	l.Size = layer.Size
   152  	l.Comment = layer.Comment
   153  	return l
   154  }
   155  
   156  func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) {
   157  	var writer io.Writer
   158  	if !options.Quiet {
   159  		writer = os.Stderr
   160  	}
   161  
   162  	dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
   163  	imageRef, err := alltransports.ParseImageName(rawImage)
   164  	if err != nil {
   165  		imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage))
   166  		if err != nil {
   167  			return nil, errors.Errorf("invalid image reference %q", rawImage)
   168  		}
   169  	}
   170  
   171  	// Special-case for docker-archive which allows multiple tags.
   172  	if imageRef.Transport().Name() == dockerarchive.Transport.Name() {
   173  		newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer)
   174  		if err != nil {
   175  			return nil, errors.Wrapf(err, "error pulling image %q", rawImage)
   176  		}
   177  		return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil
   178  	}
   179  
   180  	var registryCreds *types.DockerAuthConfig
   181  	if options.Credentials != "" {
   182  		creds, err := util.ParseRegistryCreds(options.Credentials)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		registryCreds = creds
   187  	}
   188  	dockerRegistryOptions := image.DockerRegistryOptions{
   189  		DockerRegistryCreds:         registryCreds,
   190  		DockerCertPath:              options.CertDir,
   191  		OSChoice:                    options.OverrideOS,
   192  		ArchitectureChoice:          options.OverrideArch,
   193  		DockerInsecureSkipTLSVerify: options.TLSVerify,
   194  	}
   195  
   196  	if !options.AllTags {
   197  		newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
   198  		if err != nil {
   199  			return nil, errors.Wrapf(err, "error pulling image %q", rawImage)
   200  		}
   201  		return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil
   202  	}
   203  
   204  	// --all-tags requires the docker transport
   205  	if imageRef.Transport().Name() != docker.Transport.Name() {
   206  		return nil, errors.New("--all-tags requires docker transport")
   207  	}
   208  
   209  	// Trim the docker-transport prefix.
   210  	rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name())
   211  
   212  	// all-tags doesn't work with a tagged reference, so let's check early
   213  	namedRef, err := reference.Parse(rawImage)
   214  	if err != nil {
   215  		return nil, errors.Wrapf(err, "error parsing %q", rawImage)
   216  	}
   217  	if _, isTagged := namedRef.(reference.Tagged); isTagged {
   218  		return nil, errors.New("--all-tags requires a reference without a tag")
   219  
   220  	}
   221  
   222  	systemContext := image.GetSystemContext("", options.Authfile, false)
   223  	tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef)
   224  	if err != nil {
   225  		return nil, errors.Wrapf(err, "error getting repository tags")
   226  	}
   227  
   228  	var foundIDs []string
   229  	for _, tag := range tags {
   230  		name := rawImage + ":" + tag
   231  		newImage, err := ir.Libpod.ImageRuntime().New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
   232  		if err != nil {
   233  			logrus.Errorf("error pulling image %q", name)
   234  			continue
   235  		}
   236  		foundIDs = append(foundIDs, newImage.ID())
   237  	}
   238  
   239  	if len(tags) != len(foundIDs) {
   240  		return nil, errors.Errorf("error pulling image %q", rawImage)
   241  	}
   242  	return &entities.ImagePullReport{Images: foundIDs}, nil
   243  }
   244  
   245  func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) {
   246  	report := entities.ImageInspectReport{
   247  		Errors: make(map[string]error),
   248  	}
   249  
   250  	for _, id := range names {
   251  		img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
   252  		if err != nil {
   253  			report.Errors[id] = err
   254  			continue
   255  		}
   256  
   257  		results, err := img.Inspect(ctx)
   258  		if err != nil {
   259  			report.Errors[id] = err
   260  			continue
   261  		}
   262  
   263  		cookedResults := entities.ImageData{}
   264  		_ = domainUtils.DeepCopy(&cookedResults, results)
   265  		report.Images = append(report.Images, &cookedResults)
   266  	}
   267  	return &report, nil
   268  }
   269  
   270  func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error {
   271  	var writer io.Writer
   272  	if !options.Quiet {
   273  		writer = os.Stderr
   274  	}
   275  
   276  	var manifestType string
   277  	switch options.Format {
   278  	case "":
   279  		// Default
   280  	case "oci":
   281  		manifestType = imgspecv1.MediaTypeImageManifest
   282  	case "v2s1":
   283  		manifestType = manifest.DockerV2Schema1SignedMediaType
   284  	case "v2s2", "docker":
   285  		manifestType = manifest.DockerV2Schema2MediaType
   286  	default:
   287  		return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format)
   288  	}
   289  
   290  	var registryCreds *types.DockerAuthConfig
   291  	if options.Credentials != "" {
   292  		creds, err := util.ParseRegistryCreds(options.Credentials)
   293  		if err != nil {
   294  			return err
   295  		}
   296  		registryCreds = creds
   297  	}
   298  	dockerRegistryOptions := image.DockerRegistryOptions{
   299  		DockerRegistryCreds:         registryCreds,
   300  		DockerCertPath:              options.CertDir,
   301  		DockerInsecureSkipTLSVerify: options.TLSVerify,
   302  	}
   303  
   304  	signOptions := image.SigningOptions{
   305  		RemoveSignatures: options.RemoveSignatures,
   306  		SignBy:           options.SignBy,
   307  	}
   308  
   309  	newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	return newImage.PushImageToHeuristicDestination(
   315  		ctx,
   316  		destination,
   317  		manifestType,
   318  		options.Authfile,
   319  		options.DigestFile,
   320  		options.SignaturePolicy,
   321  		writer,
   322  		options.Compress,
   323  		signOptions,
   324  		&dockerRegistryOptions,
   325  		nil)
   326  }
   327  
   328  // func (r *imageRuntime) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
   329  // 	image, err := r.libpod.ImageEngine().NewFromLocal(nameOrId)
   330  // 	if err != nil {
   331  // 		return nil, err
   332  // 	}
   333  //
   334  // 	results, err := r.libpod.RemoveImage(ctx, image, opts.Force)
   335  // 	if err != nil {
   336  // 		return nil, err
   337  // 	}
   338  //
   339  // 	report := entities.ImageDeleteReport{}
   340  // 	if err := domainUtils.DeepCopy(&report, results); err != nil {
   341  // 		return nil, err
   342  // 	}
   343  // 	return &report, nil
   344  // }
   345  //
   346  // func (r *imageRuntime) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
   347  // 	// TODO: map FilterOptions
   348  // 	id, err := r.libpod.ImageEngine().PruneImages(ctx, opts.All, []string{})
   349  // 	if err != nil {
   350  // 		return nil, err
   351  // 	}
   352  //
   353  // 	// TODO: Determine Size
   354  // 	report := entities.ImagePruneReport{}
   355  // 	copy(report.Report.Id, id)
   356  // 	return &report, nil
   357  // }
   358  
   359  func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string, options entities.ImageTagOptions) error {
   360  	newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
   361  	if err != nil {
   362  		return err
   363  	}
   364  	for _, tag := range tags {
   365  		if err := newImage.TagImage(tag); err != nil {
   366  			return err
   367  		}
   368  	}
   369  	return nil
   370  }
   371  
   372  func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error {
   373  	newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
   374  	if err != nil {
   375  		return err
   376  	}
   377  	for _, tag := range tags {
   378  		if err := newImage.UntagImage(tag); err != nil {
   379  			return err
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) {
   386  	var (
   387  		writer io.Writer
   388  	)
   389  	if !opts.Quiet {
   390  		writer = os.Stderr
   391  	}
   392  	name, err := ir.Libpod.LoadImage(ctx, opts.Name, opts.Input, writer, opts.SignaturePolicy)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name)
   397  	if err != nil {
   398  		return nil, errors.Wrap(err, "image loaded but no additional tags were created")
   399  	}
   400  	if len(opts.Name) > 0 {
   401  		if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil {
   402  			return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName)
   403  		}
   404  	}
   405  	return &entities.ImageLoadReport{Name: name}, nil
   406  }
   407  
   408  func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) {
   409  	id, err := ir.Libpod.Import(ctx, opts.Source, opts.Reference, opts.Changes, opts.Message, opts.Quiet)
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  	return &entities.ImageImportReport{Id: id}, nil
   414  }
   415  
   416  func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string, options entities.ImageSaveOptions) error {
   417  	newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
   418  	if err != nil {
   419  		return err
   420  	}
   421  	return newImage.Save(ctx, nameOrId, options.Format, options.Output, tags, options.Quiet, options.Compress)
   422  }
   423  
   424  func (ir *ImageEngine) Diff(_ context.Context, nameOrId string, _ entities.DiffOptions) (*entities.DiffReport, error) {
   425  	changes, err := ir.Libpod.GetDiff("", nameOrId)
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  	return &entities.DiffReport{Changes: changes}, nil
   430  }
   431  
   432  func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
   433  	filter, err := image.ParseSearchFilter(opts.Filters)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	searchOpts := image.SearchOptions{
   439  		Authfile:              opts.Authfile,
   440  		Filter:                *filter,
   441  		Limit:                 opts.Limit,
   442  		NoTrunc:               opts.NoTrunc,
   443  		InsecureSkipTLSVerify: opts.TLSVerify,
   444  	}
   445  
   446  	searchResults, err := image.SearchImages(term, searchOpts)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  
   451  	// Convert from image.SearchResults to entities.ImageSearchReport. We don't
   452  	// want to leak any low-level packages into the remote client, which
   453  	// requires converting.
   454  	reports := make([]entities.ImageSearchReport, len(searchResults))
   455  	for i := range searchResults {
   456  		reports[i].Index = searchResults[i].Index
   457  		reports[i].Name = searchResults[i].Name
   458  		reports[i].Description = searchResults[i].Index
   459  		reports[i].Stars = searchResults[i].Stars
   460  		reports[i].Official = searchResults[i].Official
   461  		reports[i].Automated = searchResults[i].Automated
   462  	}
   463  
   464  	return reports, nil
   465  }
   466  
   467  // GetConfig returns a copy of the configuration used by the runtime
   468  func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
   469  	return ir.Libpod.GetConfig()
   470  }