github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/domain/infra/abi/manifest.go (about)

     1  // +build !remote
     2  
     3  package abi
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"strings"
    13  
    14  	"github.com/containers/buildah/manifests"
    15  	buildahManifests "github.com/containers/buildah/pkg/manifests"
    16  	"github.com/containers/buildah/util"
    17  	buildahUtil "github.com/containers/buildah/util"
    18  	cp "github.com/containers/image/v5/copy"
    19  	"github.com/containers/image/v5/docker"
    20  	"github.com/containers/image/v5/manifest"
    21  	"github.com/containers/image/v5/transports"
    22  	"github.com/containers/image/v5/transports/alltransports"
    23  	"github.com/containers/image/v5/types"
    24  	libpodImage "github.com/containers/podman/v2/libpod/image"
    25  	"github.com/containers/podman/v2/pkg/domain/entities"
    26  	"github.com/opencontainers/go-digest"
    27  	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
    28  	"github.com/sirupsen/logrus"
    29  
    30  	"github.com/pkg/errors"
    31  )
    32  
    33  // ManifestCreate implements logic for creating manifest lists via ImageEngine
    34  func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) {
    35  	fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore())
    36  	if err != nil {
    37  		return "", errors.Wrapf(err, "error encountered while expanding image name %q", names)
    38  	}
    39  	imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All)
    40  	if err != nil {
    41  		return imageID, err
    42  	}
    43  	return imageID, err
    44  }
    45  
    46  // ManifestInspect returns the content of a manifest list or image
    47  func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
    48  	if newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil {
    49  		// return the manifest in local storage
    50  		if list, err := newImage.InspectManifest(); err == nil {
    51  			buf, err := json.MarshalIndent(list, "", "    ")
    52  			if err != nil {
    53  				return buf, errors.Wrapf(err, "error rendering manifest %s for display", name)
    54  			}
    55  			return buf, nil
    56  			// no return if local image is not a list of images type
    57  			// continue on getting valid manifest through remote serice
    58  		} else if errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported {
    59  			return nil, errors.Wrapf(err, "loading manifest %q", name)
    60  		}
    61  	}
    62  	sc := ir.Libpod.SystemContext()
    63  	refs, err := util.ResolveNameToReferences(ir.Libpod.GetStore(), sc, name)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	var (
    68  		latestErr error
    69  		result    []byte
    70  		manType   string
    71  		b         bytes.Buffer
    72  	)
    73  	appendErr := func(e error) {
    74  		if latestErr == nil {
    75  			latestErr = e
    76  		} else {
    77  			latestErr = errors.Wrapf(latestErr, "tried %v\n", e)
    78  		}
    79  	}
    80  	for _, ref := range refs {
    81  		src, err := ref.NewImageSource(ctx, sc)
    82  		if err != nil {
    83  			appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref)))
    84  			continue
    85  		}
    86  		defer src.Close()
    87  
    88  		manifestBytes, manifestType, err := src.GetManifest(ctx, nil)
    89  		if err != nil {
    90  			appendErr(errors.Wrapf(err, "loading manifest %q", transports.ImageName(ref)))
    91  			continue
    92  		}
    93  
    94  		result = manifestBytes
    95  		manType = manifestType
    96  		break
    97  	}
    98  	if len(result) == 0 && latestErr != nil {
    99  		return nil, latestErr
   100  	}
   101  
   102  	switch manType {
   103  	case manifest.DockerV2Schema2MediaType:
   104  		logrus.Warnf("Warning! The manifest type %s is not a manifest list but a single image.", manType)
   105  		schema2Manifest, err := manifest.Schema2FromManifest(result)
   106  		if err != nil {
   107  			return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType)
   108  		}
   109  		if result, err = schema2Manifest.Serialize(); err != nil {
   110  			return nil, err
   111  		}
   112  	default:
   113  		listBlob, err := manifest.ListFromBlob(result, manType)
   114  		if err != nil {
   115  			return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType)
   116  		}
   117  		list, err := listBlob.ConvertToMIMEType(manifest.DockerV2ListMediaType)
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  		if result, err = list.Serialize(); err != nil {
   122  			return nil, err
   123  		}
   124  	}
   125  
   126  	if err = json.Indent(&b, result, "", "    "); err != nil {
   127  		return nil, errors.Wrapf(err, "error rendering manifest %s for display", name)
   128  	}
   129  	return b.Bytes(), nil
   130  }
   131  
   132  // ManifestAdd adds images to the manifest list
   133  func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
   134  	imageSpec := opts.Images[0]
   135  	listImageSpec := opts.Images[1]
   136  	dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
   137  	_, err := alltransports.ParseImageName(imageSpec)
   138  	if err != nil {
   139  		_, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec))
   140  		if err != nil {
   141  			return "", errors.Errorf("invalid image reference %q", imageSpec)
   142  		}
   143  	}
   144  	listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec)
   145  	if err != nil {
   146  		return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec)
   147  	}
   148  
   149  	manifestAddOpts := libpodImage.ManifestAddOpts{
   150  		All:       opts.All,
   151  		Arch:      opts.Arch,
   152  		Features:  opts.Features,
   153  		Images:    opts.Images,
   154  		OS:        opts.OS,
   155  		OSVersion: opts.OSVersion,
   156  		Variant:   opts.Variant,
   157  	}
   158  	if len(opts.Annotation) != 0 {
   159  		annotations := make(map[string]string)
   160  		for _, annotationSpec := range opts.Annotation {
   161  			spec := strings.SplitN(annotationSpec, "=", 2)
   162  			if len(spec) != 2 {
   163  				return "", errors.Errorf("no value given for annotation %q", spec[0])
   164  			}
   165  			annotations[spec[0]] = spec[1]
   166  		}
   167  		manifestAddOpts.Annotation = annotations
   168  	}
   169  
   170  	// Set the system context.
   171  	sys := ir.Libpod.SystemContext()
   172  	if sys != nil {
   173  		sys = &types.SystemContext{}
   174  	}
   175  	sys.AuthFilePath = opts.Authfile
   176  	sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
   177  	sys.DockerCertPath = opts.CertDir
   178  
   179  	if opts.Username != "" && opts.Password != "" {
   180  		sys.DockerAuthConfig = &types.DockerAuthConfig{
   181  			Username: opts.Username,
   182  			Password: opts.Password,
   183  		}
   184  	}
   185  
   186  	listID, err := listImage.AddManifest(*sys, manifestAddOpts)
   187  	if err != nil {
   188  		return listID, err
   189  	}
   190  	return listID, nil
   191  }
   192  
   193  // ManifestAnnotate updates an entry of the manifest list
   194  func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
   195  	listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
   196  	if err != nil {
   197  		return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0])
   198  	}
   199  	digest, err := digest.Parse(names[1])
   200  	if err != nil {
   201  		return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
   202  	}
   203  	manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{
   204  		Arch:       opts.Arch,
   205  		Features:   opts.Features,
   206  		OS:         opts.OS,
   207  		OSFeatures: opts.OSFeatures,
   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  		manifestAnnotateOpts.Annotation = annotations
   221  	}
   222  	updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts)
   223  	if err == nil {
   224  		return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil
   225  	}
   226  	return "", err
   227  }
   228  
   229  // ManifestRemove removes specified digest from the specified manifest list
   230  func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
   231  	instanceDigest, err := digest.Parse(names[1])
   232  	if err != nil {
   233  		return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
   234  	}
   235  	listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
   236  	if err != nil {
   237  		return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0])
   238  	}
   239  	updatedListID, err := listImage.RemoveManifest(instanceDigest)
   240  	if err == nil {
   241  		return fmt.Sprintf("%s :%s\n", updatedListID, instanceDigest.String()), nil
   242  	}
   243  	return "", err
   244  }
   245  
   246  // ManifestPush pushes a manifest list or image index to the destination
   247  func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts entities.ManifestPushOptions) error {
   248  	listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
   249  	if err != nil {
   250  		return errors.Wrapf(err, "error retrieving local image from image name %s", names[0])
   251  	}
   252  	dest, err := alltransports.ParseImageName(names[1])
   253  	if err != nil {
   254  		return err
   255  	}
   256  	var manifestType string
   257  	if opts.Format != "" {
   258  		switch opts.Format {
   259  		case "oci":
   260  			manifestType = imgspecv1.MediaTypeImageManifest
   261  		case "v2s2", "docker":
   262  			manifestType = manifest.DockerV2Schema2MediaType
   263  		default:
   264  			return errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format)
   265  		}
   266  	}
   267  
   268  	// Set the system context.
   269  	sys := ir.Libpod.SystemContext()
   270  	if sys != nil {
   271  		sys = &types.SystemContext{}
   272  	}
   273  	sys.AuthFilePath = opts.Authfile
   274  	sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
   275  	sys.DockerCertPath = opts.CertDir
   276  
   277  	if opts.Username != "" && opts.Password != "" {
   278  		sys.DockerAuthConfig = &types.DockerAuthConfig{
   279  			Username: opts.Username,
   280  			Password: opts.Password,
   281  		}
   282  	}
   283  
   284  	options := manifests.PushOptions{
   285  		Store:              ir.Libpod.GetStore(),
   286  		SystemContext:      sys,
   287  		ImageListSelection: cp.CopySpecificImages,
   288  		Instances:          nil,
   289  		RemoveSignatures:   opts.RemoveSignatures,
   290  		SignBy:             opts.SignBy,
   291  		ManifestType:       manifestType,
   292  	}
   293  	if opts.All {
   294  		options.ImageListSelection = cp.CopyAllImages
   295  	}
   296  	if !opts.Quiet {
   297  		options.ReportWriter = os.Stderr
   298  	}
   299  	digest, err := listImage.PushManifest(dest, options)
   300  	if err == nil && opts.Purge {
   301  		_, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true)
   302  	}
   303  	if opts.DigestFile != "" {
   304  		if err = ioutil.WriteFile(opts.DigestFile, []byte(digest.String()), 0644); err != nil {
   305  			return buildahUtil.GetFailureCause(err, errors.Wrapf(err, "failed to write digest to file %q", opts.DigestFile))
   306  		}
   307  	}
   308  	return err
   309  }