github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/image/pull.go (about)

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/containers/common/pkg/retry"
    12  	cp "github.com/containers/image/v5/copy"
    13  	"github.com/containers/image/v5/directory"
    14  	"github.com/containers/image/v5/docker"
    15  	"github.com/containers/image/v5/docker/archive"
    16  	dockerarchive "github.com/containers/image/v5/docker/archive"
    17  	ociarchive "github.com/containers/image/v5/oci/archive"
    18  	oci "github.com/containers/image/v5/oci/layout"
    19  	"github.com/containers/image/v5/pkg/shortnames"
    20  	is "github.com/containers/image/v5/storage"
    21  	"github.com/containers/image/v5/transports"
    22  	"github.com/containers/image/v5/transports/alltransports"
    23  	"github.com/containers/image/v5/types"
    24  	"github.com/containers/podman/v2/libpod/events"
    25  	"github.com/containers/podman/v2/pkg/errorhandling"
    26  	"github.com/containers/podman/v2/pkg/registries"
    27  	"github.com/opentracing/opentracing-go"
    28  	"github.com/pkg/errors"
    29  	"github.com/sirupsen/logrus"
    30  )
    31  
    32  var (
    33  	// DockerArchive is the transport we prepend to an image name
    34  	// when saving to docker-archive
    35  	DockerArchive = dockerarchive.Transport.Name()
    36  	// OCIArchive is the transport we prepend to an image name
    37  	// when saving to oci-archive
    38  	OCIArchive = ociarchive.Transport.Name()
    39  	// DirTransport is the transport for pushing and pulling
    40  	// images to and from a directory
    41  	DirTransport = directory.Transport.Name()
    42  	// DockerTransport is the transport for docker registries
    43  	DockerTransport = docker.Transport.Name()
    44  	// OCIDirTransport is the transport for pushing and pulling
    45  	// images to and from a directory containing an OCI image
    46  	OCIDirTransport = oci.Transport.Name()
    47  	// AtomicTransport is the transport for atomic registries
    48  	AtomicTransport = "atomic"
    49  	// DefaultTransport is a prefix that we apply to an image name
    50  	// NOTE: This is a string prefix, not actually a transport name usable for transports.Get();
    51  	// and because syntaxes of image names are transport-dependent, the prefix is not really interchangeable;
    52  	// each user implicitly assumes the appended string is a Docker-like reference.
    53  	DefaultTransport = DockerTransport + "://"
    54  	// DefaultLocalRegistry is the default local registry for local image operations
    55  	// Remote pulls will still use defined registries
    56  	DefaultLocalRegistry = "localhost"
    57  )
    58  
    59  // pullRefPair records a pair of prepared image references to pull.
    60  type pullRefPair struct {
    61  	image             string
    62  	srcRef            types.ImageReference
    63  	dstRef            types.ImageReference
    64  	resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull
    65  }
    66  
    67  // cleanUpFunc is a function prototype for clean-up functions.
    68  type cleanUpFunc func() error
    69  
    70  // pullGoal represents the prepared image references and decided behavior to be executed by imagePull
    71  type pullGoal struct {
    72  	refPairs     []pullRefPair
    73  	pullAllPairs bool                 // Pull all refPairs instead of stopping on first success.
    74  	cleanUpFuncs []cleanUpFunc        // Mainly used to close long-lived objects (e.g., an archive.Reader)
    75  	shortName    string               // Set when pulling a short name
    76  	resolved     *shortnames.Resolved // Set when pulling a short name
    77  }
    78  
    79  // cleanUp invokes all cleanUpFuncs.  Certain resources may not be available
    80  // anymore.  Errors are logged.
    81  func (p *pullGoal) cleanUp() {
    82  	for _, f := range p.cleanUpFuncs {
    83  		if err := f(); err != nil {
    84  			logrus.Error(err.Error())
    85  		}
    86  	}
    87  }
    88  
    89  // singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair.
    90  func singlePullRefPairGoal(rp pullRefPair) *pullGoal {
    91  	return &pullGoal{
    92  		refPairs:     []pullRefPair{rp},
    93  		pullAllPairs: false, // Does not really make a difference.
    94  	}
    95  }
    96  
    97  func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) {
    98  	decomposedDest, err := decompose(destName)
    99  	if err == nil && !decomposedDest.hasRegistry {
   100  		// If the image doesn't have a registry, set it as the default repo
   101  		ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry)
   102  		if err != nil {
   103  			return pullRefPair{}, err
   104  		}
   105  		destName = ref.String()
   106  	}
   107  
   108  	reference := destName
   109  	if srcRef.DockerReference() != nil {
   110  		reference = srcRef.DockerReference().String()
   111  	}
   112  	destRef, err := is.Transport.ParseStoreReference(ir.store, reference)
   113  	if err != nil {
   114  		return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
   115  	}
   116  	return pullRefPair{
   117  		image:  destName,
   118  		srcRef: srcRef,
   119  		dstRef: destRef,
   120  	}, nil
   121  }
   122  
   123  // getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value.
   124  func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) {
   125  	rp, err := ir.getPullRefPair(srcRef, destName)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	return singlePullRefPairGoal(rp), nil
   130  }
   131  
   132  // getPullRefPairsFromDockerArchiveReference returns a slice of pullRefPairs
   133  // for the specified docker reference and the corresponding archive.Reader.
   134  func (ir *Runtime) getPullRefPairsFromDockerArchiveReference(ctx context.Context, reader *archive.Reader, ref types.ImageReference, sc *types.SystemContext) ([]pullRefPair, error) {
   135  	destNames, err := reader.ManifestTagsForReference(ref)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	if len(destNames) == 0 {
   141  		destName, err := getImageDigest(ctx, ref, sc)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  		destNames = append(destNames, destName)
   146  	} else {
   147  		for i := range destNames {
   148  			ref, err := NormalizedTag(destNames[i])
   149  			if err != nil {
   150  				return nil, err
   151  			}
   152  			destNames[i] = ref.String()
   153  		}
   154  	}
   155  
   156  	refPairs := []pullRefPair{}
   157  	for _, destName := range destNames {
   158  		destRef, err := is.Transport.ParseStoreReference(ir.store, destName)
   159  		if err != nil {
   160  			return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
   161  		}
   162  		pair := pullRefPair{
   163  			image:  destName,
   164  			srcRef: ref,
   165  			dstRef: destRef,
   166  		}
   167  		refPairs = append(refPairs, pair)
   168  	}
   169  
   170  	return refPairs, nil
   171  }
   172  
   173  // pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport.
   174  // Note that callers are responsible for invoking (*pullGoal).cleanUp() to clean up possibly open resources.
   175  func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) {
   176  	span, _ := opentracing.StartSpanFromContext(ctx, "pullGoalFromImageReference")
   177  	defer span.Finish()
   178  
   179  	// supports pulling from docker-archive, oci, and registries
   180  	switch srcRef.Transport().Name() {
   181  	case DockerArchive:
   182  		reader, readerRef, err := archive.NewReaderForReference(sc, srcRef)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, readerRef, sc)
   188  		if err != nil {
   189  			// No need to defer for a single error path.
   190  			if err := reader.Close(); err != nil {
   191  				logrus.Error(err.Error())
   192  			}
   193  			return nil, err
   194  		}
   195  
   196  		return &pullGoal{
   197  			pullAllPairs: true,
   198  			refPairs:     pairs,
   199  			cleanUpFuncs: []cleanUpFunc{reader.Close},
   200  		}, nil
   201  
   202  	case OCIArchive:
   203  		// retrieve the manifest from index.json to access the image name
   204  		manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
   205  		if err != nil {
   206  			return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef)
   207  		}
   208  		var dest string
   209  		if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
   210  			// If the input image has no image.ref.name, we need to feed it a dest anyways
   211  			// use the hex of the digest
   212  			dest, err = getImageDigest(ctx, srcRef, sc)
   213  			if err != nil {
   214  				return nil, errors.Wrapf(err, "error getting image digest; image reference not found")
   215  			}
   216  		} else {
   217  			dest = manifest.Annotations["org.opencontainers.image.ref.name"]
   218  		}
   219  		return ir.getSinglePullRefPairGoal(srcRef, dest)
   220  
   221  	case DirTransport:
   222  		image := toLocalImageName(srcRef.StringWithinTransport())
   223  		return ir.getSinglePullRefPairGoal(srcRef, image)
   224  
   225  	case OCIDirTransport:
   226  		split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2)
   227  		image := toLocalImageName(split[0])
   228  		return ir.getSinglePullRefPairGoal(srcRef, image)
   229  
   230  	default:
   231  		return ir.getSinglePullRefPairGoal(srcRef, imgName)
   232  	}
   233  }
   234  
   235  // toLocalImageName converts an image name into a 'localhost/' prefixed one
   236  func toLocalImageName(imageName string) string {
   237  	return fmt.Sprintf(
   238  		"%s/%s",
   239  		DefaultLocalRegistry,
   240  		strings.TrimLeft(imageName, "/"),
   241  	)
   242  }
   243  
   244  // pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries.
   245  // Use pullImageFromReference if the source is known precisely.
   246  func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) {
   247  	span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromHeuristicSource")
   248  	defer span.Finish()
   249  
   250  	var goal *pullGoal
   251  	sc := GetSystemContext(signaturePolicyPath, authfile, false)
   252  	if dockerOptions != nil {
   253  		sc.OSChoice = dockerOptions.OSChoice
   254  		sc.ArchitectureChoice = dockerOptions.ArchitectureChoice
   255  		sc.VariantChoice = dockerOptions.VariantChoice
   256  	}
   257  	if signaturePolicyPath == "" {
   258  		sc.SignaturePolicyPath = ir.SignaturePolicyPath
   259  	}
   260  	sc.BlobInfoCacheDir = filepath.Join(ir.store.GraphRoot(), "cache")
   261  	srcRef, err := alltransports.ParseImageName(inputName)
   262  	if err != nil {
   263  		// We might be pulling with an unqualified image reference in which case
   264  		// we need to make sure that we're not using any other transport.
   265  		srcTransport := alltransports.TransportFromImageName(inputName)
   266  		if srcTransport != nil && srcTransport.Name() != DockerTransport {
   267  			return nil, err
   268  		}
   269  		goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName)
   270  		if err != nil {
   271  			return nil, errors.Wrap(err, "error getting default registries to try")
   272  		}
   273  	} else {
   274  		goal, err = ir.pullGoalFromImageReference(ctx, srcRef, inputName, sc)
   275  		if err != nil {
   276  			return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName)
   277  		}
   278  	}
   279  	defer goal.cleanUp()
   280  	return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label)
   281  }
   282  
   283  // pullImageFromReference pulls an image from a types.imageReference.
   284  func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions) ([]string, error) {
   285  	span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromReference")
   286  	defer span.Finish()
   287  
   288  	sc := GetSystemContext(signaturePolicyPath, authfile, false)
   289  	if dockerOptions != nil {
   290  		sc.OSChoice = dockerOptions.OSChoice
   291  		sc.ArchitectureChoice = dockerOptions.ArchitectureChoice
   292  		sc.VariantChoice = dockerOptions.VariantChoice
   293  	}
   294  	goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc)
   295  	if err != nil {
   296  		return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef))
   297  	}
   298  	defer goal.cleanUp()
   299  	return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil)
   300  }
   301  
   302  func cleanErrorMessage(err error) string {
   303  	errMessage := strings.TrimPrefix(errors.Cause(err).Error(), "errors:\n")
   304  	errMessage = strings.Split(errMessage, "\n")[0]
   305  	return fmt.Sprintf("  %s\n", errMessage)
   306  }
   307  
   308  // doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead.
   309  func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) {
   310  	span, _ := opentracing.StartSpanFromContext(ctx, "doPullImage")
   311  	defer span.Finish()
   312  
   313  	policyContext, err := getPolicyContext(sc)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	defer func() {
   318  		if err := policyContext.Destroy(); err != nil {
   319  			logrus.Errorf("failed to destroy policy context: %q", err)
   320  		}
   321  	}()
   322  
   323  	systemRegistriesConfPath := registries.SystemRegistriesConfPath()
   324  
   325  	var (
   326  		images     []string
   327  		pullErrors []error
   328  	)
   329  
   330  	for _, imageInfo := range goal.refPairs {
   331  		copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil)
   332  		copyOptions.SourceCtx.SystemRegistriesConfPath = systemRegistriesConfPath // FIXME: Set this more globally.  Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place.
   333  		// Print the following statement only when pulling from a docker or atomic registry
   334  		if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) {
   335  			if _, err := io.WriteString(writer, fmt.Sprintf("Trying to pull %s...\n", imageInfo.image)); err != nil {
   336  				return nil, err
   337  			}
   338  		}
   339  		// If the label is not nil, check if the label exists and if not, return err
   340  		if label != nil {
   341  			if err := checkRemoteImageForLabel(ctx, *label, imageInfo, sc); err != nil {
   342  				return nil, err
   343  			}
   344  		}
   345  		imageInfo := imageInfo
   346  		if err = retry.RetryIfNecessary(ctx, func() error {
   347  			_, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions)
   348  			return err
   349  		}, retryOptions); err != nil {
   350  			pullErrors = append(pullErrors, err)
   351  			logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err)
   352  			if writer != nil {
   353  				_, _ = io.WriteString(writer, cleanErrorMessage(err))
   354  			}
   355  		} else {
   356  			if imageInfo.resolvedShortname != nil {
   357  				if err := imageInfo.resolvedShortname.Record(); err != nil {
   358  					logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err)
   359  				}
   360  			}
   361  			if !goal.pullAllPairs {
   362  				ir.newImageEvent(events.Pull, "")
   363  				return []string{imageInfo.image}, nil
   364  			}
   365  			images = append(images, imageInfo.image)
   366  		}
   367  	}
   368  	// If no image was found, we should handle.  Lets be nicer to the user
   369  	// and see if we can figure out why.
   370  	if len(images) == 0 {
   371  		if goal.resolved != nil {
   372  			return nil, goal.resolved.FormatPullErrors(pullErrors)
   373  		}
   374  		return nil, errorhandling.JoinErrors(pullErrors)
   375  	}
   376  
   377  	ir.newImageEvent(events.Pull, images[0])
   378  	return images, nil
   379  }
   380  
   381  // getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment
   382  // variable.  If it's "on", return `nil` to use the defaults from
   383  // containers/image and the registries.conf files on the system.  If it's
   384  // "off", empty or unset, return types.ShortNameModeDisabled to turn off
   385  // short-name aliasing by default.
   386  //
   387  // TODO: remove this function once we want to default to short-name aliasing.
   388  func getShortNameMode() *types.ShortNameMode {
   389  	env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING")
   390  	if strings.ToLower(env) == "on" {
   391  		return nil // default to whatever registries.conf and c/image decide
   392  	}
   393  	mode := types.ShortNameModeDisabled
   394  	return &mode
   395  }
   396  
   397  // pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible
   398  // image references to try pulling in combination with the registries.conf file as well
   399  func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) {
   400  	if sys == nil {
   401  		sys = &types.SystemContext{}
   402  	}
   403  	sys.ShortNameMode = getShortNameMode()
   404  
   405  	resolved, err := shortnames.Resolve(sys, inputName)
   406  	if err != nil {
   407  		return nil, err
   408  	}
   409  
   410  	if desc := resolved.Description(); len(desc) > 0 {
   411  		logrus.Debug(desc)
   412  		if writer != nil {
   413  			if _, err := writer.Write([]byte(desc + "\n")); err != nil {
   414  				return nil, err
   415  			}
   416  		}
   417  	}
   418  
   419  	refPairs := []pullRefPair{}
   420  	for i, candidate := range resolved.PullCandidates {
   421  		srcRef, err := docker.NewReference(candidate.Value)
   422  		if err != nil {
   423  			return nil, err
   424  		}
   425  		ps, err := ir.getPullRefPair(srcRef, candidate.Value.String())
   426  		if err != nil {
   427  			return nil, err
   428  		}
   429  		ps.resolvedShortname = &resolved.PullCandidates[i]
   430  		refPairs = append(refPairs, ps)
   431  	}
   432  	return &pullGoal{
   433  		refPairs:     refPairs,
   434  		pullAllPairs: false,
   435  		shortName:    inputName,
   436  		resolved:     resolved,
   437  	}, nil
   438  }
   439  
   440  // checkRemoteImageForLabel checks if the remote image has a specific label. if the label exists, we
   441  // return nil, else we return an error
   442  func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullRefPair, sc *types.SystemContext) error {
   443  	labelImage, err := imageInfo.srcRef.NewImage(ctx, sc)
   444  	if err != nil {
   445  		return err
   446  	}
   447  	remoteInspect, err := labelImage.Inspect(ctx)
   448  	if err != nil {
   449  		return err
   450  	}
   451  	// Labels are case insensitive; so we iterate instead of simple lookup
   452  	for k := range remoteInspect.Labels {
   453  		if strings.ToLower(label) == strings.ToLower(k) {
   454  			return nil
   455  		}
   456  	}
   457  	return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels)
   458  }