github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/domain/infra/abi/manifest.go (about)

     1  package abi
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/containers/common/libimage"
    11  	cp "github.com/containers/image/v5/copy"
    12  	"github.com/containers/image/v5/manifest"
    13  	"github.com/containers/image/v5/pkg/shortnames"
    14  	"github.com/containers/image/v5/transports"
    15  	"github.com/containers/image/v5/transports/alltransports"
    16  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    17  	"github.com/containers/storage"
    18  	"github.com/opencontainers/go-digest"
    19  	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus"
    22  )
    23  
    24  // ManifestCreate implements logic for creating manifest lists via ImageEngine
    25  func (ir *ImageEngine) ManifestCreate(ctx context.Context, name string, images []string, opts entities.ManifestCreateOptions) (string, error) {
    26  	if len(name) == 0 {
    27  		return "", errors.New("no name specified for creating a manifest list")
    28  	}
    29  
    30  	manifestList, err := ir.Libpod.LibimageRuntime().CreateManifestList(name)
    31  	if err != nil {
    32  		return "", err
    33  	}
    34  
    35  	addOptions := &libimage.ManifestListAddOptions{All: opts.All}
    36  	for _, image := range images {
    37  		if _, err := manifestList.Add(ctx, image, addOptions); err != nil {
    38  			return "", err
    39  		}
    40  	}
    41  
    42  	return manifestList.ID(), nil
    43  }
    44  
    45  // ManifestExists checks if a manifest list with the given name exists in local storage
    46  func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) {
    47  	_, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
    48  	if err != nil {
    49  		if errors.Cause(err) == storage.ErrImageUnknown {
    50  			return &entities.BoolReport{Value: false}, nil
    51  		}
    52  		return nil, err
    53  	}
    54  
    55  	return &entities.BoolReport{Value: true}, nil
    56  }
    57  
    58  // ManifestInspect returns the content of a manifest list or image
    59  func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
    60  	// NOTE: we have to do a bit of a limbo here as `podman manifest
    61  	// inspect foo` wants to do a remote-inspect of foo iff "foo" in the
    62  	// containers storage is an ordinary image but not a manifest list.
    63  
    64  	manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
    65  	if err != nil {
    66  		switch errors.Cause(err) {
    67  		// Do a remote inspect if there's no local image or if the
    68  		// local image is not a manifest list.
    69  		case storage.ErrImageUnknown, libimage.ErrNotAManifestList:
    70  			return ir.remoteManifestInspect(ctx, name)
    71  
    72  		default:
    73  			return nil, err
    74  		}
    75  	}
    76  
    77  	schema2List, err := manifestList.Inspect()
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	rawSchema2List, err := json.Marshal(schema2List)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	var b bytes.Buffer
    88  	if err := json.Indent(&b, rawSchema2List, "", "    "); err != nil {
    89  		return nil, errors.Wrapf(err, "error rendering manifest %s for display", name)
    90  	}
    91  	return b.Bytes(), nil
    92  }
    93  
    94  // inspect a remote manifest list.
    95  func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) {
    96  	sys := ir.Libpod.SystemContext()
    97  
    98  	resolved, err := shortnames.Resolve(sys, name)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	var (
   104  		latestErr error
   105  		result    []byte
   106  		manType   string
   107  		b         bytes.Buffer
   108  	)
   109  	appendErr := func(e error) {
   110  		if latestErr == nil {
   111  			latestErr = e
   112  		} else {
   113  			// FIXME should we use multierror package instead?
   114  
   115  			// we want the new line here so ignore the linter
   116  			//nolint:revive
   117  			latestErr = errors.Wrapf(latestErr, "tried %v\n", e)
   118  		}
   119  	}
   120  
   121  	for _, candidate := range resolved.PullCandidates {
   122  		ref, err := alltransports.ParseImageName("docker://" + candidate.Value.String())
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  		src, err := ref.NewImageSource(ctx, sys)
   127  		if err != nil {
   128  			appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref)))
   129  			continue
   130  		}
   131  		defer src.Close()
   132  
   133  		manifestBytes, manifestType, err := src.GetManifest(ctx, nil)
   134  		if err != nil {
   135  			appendErr(errors.Wrapf(err, "loading manifest %q", transports.ImageName(ref)))
   136  			continue
   137  		}
   138  
   139  		result = manifestBytes
   140  		manType = manifestType
   141  		break
   142  	}
   143  
   144  	if len(result) == 0 && latestErr != nil {
   145  		return nil, latestErr
   146  	}
   147  
   148  	switch manType {
   149  	case manifest.DockerV2Schema2MediaType:
   150  		logrus.Warnf("The manifest type %s is not a manifest list but a single image.", manType)
   151  		schema2Manifest, err := manifest.Schema2FromManifest(result)
   152  		if err != nil {
   153  			return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType)
   154  		}
   155  		if result, err = schema2Manifest.Serialize(); err != nil {
   156  			return nil, err
   157  		}
   158  	default:
   159  		listBlob, err := manifest.ListFromBlob(result, manType)
   160  		if err != nil {
   161  			return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType)
   162  		}
   163  		list, err := listBlob.ConvertToMIMEType(manifest.DockerV2ListMediaType)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  		if result, err = list.Serialize(); err != nil {
   168  			return nil, err
   169  		}
   170  	}
   171  
   172  	if err = json.Indent(&b, result, "", "    "); err != nil {
   173  		return nil, errors.Wrapf(err, "error rendering manifest %s for display", name)
   174  	}
   175  	return b.Bytes(), nil
   176  }
   177  
   178  // ManifestAdd adds images to the manifest list
   179  func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []string, opts entities.ManifestAddOptions) (string, error) {
   180  	if len(images) < 1 {
   181  		return "", errors.New("manifest add requires at least one image")
   182  	}
   183  
   184  	manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  
   189  	addOptions := &libimage.ManifestListAddOptions{
   190  		All:                   opts.All,
   191  		AuthFilePath:          opts.Authfile,
   192  		CertDirPath:           opts.CertDir,
   193  		InsecureSkipTLSVerify: opts.SkipTLSVerify,
   194  		Username:              opts.Username,
   195  		Password:              opts.Password,
   196  	}
   197  
   198  	for _, image := range images {
   199  		instanceDigest, err := manifestList.Add(ctx, image, addOptions)
   200  		if err != nil {
   201  			return "", err
   202  		}
   203  
   204  		annotateOptions := &libimage.ManifestListAnnotateOptions{
   205  			Architecture: opts.Arch,
   206  			Features:     opts.Features,
   207  			OS:           opts.OS,
   208  			OSVersion:    opts.OSVersion,
   209  			Variant:      opts.Variant,
   210  		}
   211  		if len(opts.Annotation) != 0 {
   212  			annotations := make(map[string]string)
   213  			for _, annotationSpec := range opts.Annotation {
   214  				spec := strings.SplitN(annotationSpec, "=", 2)
   215  				if len(spec) != 2 {
   216  					return "", errors.Errorf("no value given for annotation %q", spec[0])
   217  				}
   218  				annotations[spec[0]] = spec[1]
   219  			}
   220  			annotateOptions.Annotations = annotations
   221  		}
   222  
   223  		if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil {
   224  			return "", err
   225  		}
   226  	}
   227  	return manifestList.ID(), nil
   228  }
   229  
   230  // ManifestAnnotate updates an entry of the manifest list
   231  func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, opts entities.ManifestAnnotateOptions) (string, error) {
   232  	instanceDigest, err := digest.Parse(image)
   233  	if err != nil {
   234  		return "", errors.Errorf(`invalid image digest "%s": %v`, image, err)
   235  	}
   236  
   237  	manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
   238  	if err != nil {
   239  		return "", err
   240  	}
   241  
   242  	annotateOptions := &libimage.ManifestListAnnotateOptions{
   243  		Architecture: opts.Arch,
   244  		Features:     opts.Features,
   245  		OS:           opts.OS,
   246  		OSVersion:    opts.OSVersion,
   247  		Variant:      opts.Variant,
   248  	}
   249  	if len(opts.Annotation) != 0 {
   250  		annotations := make(map[string]string)
   251  		for _, annotationSpec := range opts.Annotation {
   252  			spec := strings.SplitN(annotationSpec, "=", 2)
   253  			if len(spec) != 2 {
   254  				return "", errors.Errorf("no value given for annotation %q", spec[0])
   255  			}
   256  			annotations[spec[0]] = spec[1]
   257  		}
   258  		annotateOptions.Annotations = annotations
   259  	}
   260  
   261  	if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil {
   262  		return "", err
   263  	}
   264  
   265  	return manifestList.ID(), nil
   266  }
   267  
   268  // ManifestRemoveDigest removes specified digest from the specified manifest list
   269  func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name, image string) (string, error) {
   270  	instanceDigest, err := digest.Parse(image)
   271  	if err != nil {
   272  		return "", errors.Errorf(`invalid image digest "%s": %v`, image, err)
   273  	}
   274  
   275  	manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
   276  	if err != nil {
   277  		return "", err
   278  	}
   279  
   280  	if err := manifestList.RemoveInstance(instanceDigest); err != nil {
   281  		return "", err
   282  	}
   283  
   284  	return manifestList.ID(), nil
   285  }
   286  
   287  // ManifestRm removes the specified manifest list from storage
   288  func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (report *entities.ImageRemoveReport, rmErrors []error) {
   289  	return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true})
   290  }
   291  
   292  // ManifestPush pushes a manifest list or image index to the destination
   293  func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) {
   294  	manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
   295  	if err != nil {
   296  		return "", errors.Wrapf(err, "error retrieving local image from image name %s", name)
   297  	}
   298  
   299  	var manifestType string
   300  	if opts.Format != "" {
   301  		switch opts.Format {
   302  		case "oci":
   303  			manifestType = imgspecv1.MediaTypeImageManifest
   304  		case "v2s2", "docker":
   305  			manifestType = manifest.DockerV2Schema2MediaType
   306  		default:
   307  			return "", errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format)
   308  		}
   309  	}
   310  
   311  	pushOptions := &libimage.ManifestListPushOptions{}
   312  	pushOptions.AuthFilePath = opts.Authfile
   313  	pushOptions.CertDirPath = opts.CertDir
   314  	pushOptions.Username = opts.Username
   315  	pushOptions.Password = opts.Password
   316  	pushOptions.ImageListSelection = cp.CopySpecificImages
   317  	pushOptions.ManifestMIMEType = manifestType
   318  	pushOptions.RemoveSignatures = opts.RemoveSignatures
   319  	pushOptions.SignBy = opts.SignBy
   320  	pushOptions.InsecureSkipTLSVerify = opts.SkipTLSVerify
   321  
   322  	if opts.All {
   323  		pushOptions.ImageListSelection = cp.CopyAllImages
   324  	}
   325  	if !opts.Quiet {
   326  		pushOptions.Writer = os.Stderr
   327  	}
   328  
   329  	manDigest, err := manifestList.Push(ctx, destination, pushOptions)
   330  	if err != nil {
   331  		return "", err
   332  	}
   333  
   334  	if opts.Rm {
   335  		if _, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, []string{manifestList.ID()}, nil); len(rmErrors) > 0 {
   336  			return "", errors.Wrap(rmErrors[0], "error removing manifest after push")
   337  		}
   338  	}
   339  
   340  	return manDigest.String(), err
   341  }