summaryrefslogtreecommitdiff
path: root/libpod/image/pull.go
diff options
context:
space:
mode:
authorValentin Rothberg <rothberg@redhat.com>2021-04-22 08:01:12 +0200
committerValentin Rothberg <rothberg@redhat.com>2021-05-05 11:30:12 +0200
commit0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21 (patch)
tree192e52054de2abf0c92d83ecdbc71d498c2ec947 /libpod/image/pull.go
parent8eefca5a257121b177562742c972e39e1686140d (diff)
downloadpodman-0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21.tar.gz
podman-0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21.tar.bz2
podman-0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21.zip
migrate Podman to containers/common/libimage
Migrate the Podman code base over to `common/libimage` which replaces `libpod/image` and a lot of glue code entirely. Note that I tried to leave bread crumbs for changed tests. Miscellaneous changes: * Some errors yield different messages which required to alter some tests. * I fixed some pre-existing issues in the code. Others were marked as `//TODO`s to prevent the PR from exploding. * The `NamesHistory` of an image is returned as is from the storage. Previously, we did some filtering which I think is undesirable. Instead we should return the data as stored in the storage. * Touched handlers use the ABI interfaces where possible. * Local image resolution: previously Podman would match "foo" on "myfoo". This behaviour has been changed and Podman will now only match on repository boundaries such that "foo" would match "my/foo" but not "myfoo". I consider the old behaviour to be a bug, at the very least an exotic corner case. * Futhermore, "foo:none" does *not* resolve to a local image "foo" without tag anymore. It's a hill I am (almost) willing to die on. * `image prune` prints the IDs of pruned images. Previously, in some cases, the names were printed instead. The API clearly states ID, so we should stick to it. * Compat endpoint image removal with _force_ deletes the entire not only the specified tag. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
Diffstat (limited to 'libpod/image/pull.go')
-rw-r--r--libpod/image/pull.go437
1 files changed, 0 insertions, 437 deletions
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
deleted file mode 100644
index 6517fbd07..000000000
--- a/libpod/image/pull.go
+++ /dev/null
@@ -1,437 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/containers/common/pkg/retry"
- cp "github.com/containers/image/v5/copy"
- "github.com/containers/image/v5/directory"
- "github.com/containers/image/v5/docker"
- dockerarchive "github.com/containers/image/v5/docker/archive"
- ociarchive "github.com/containers/image/v5/oci/archive"
- oci "github.com/containers/image/v5/oci/layout"
- "github.com/containers/image/v5/pkg/shortnames"
- is "github.com/containers/image/v5/storage"
- "github.com/containers/image/v5/transports"
- "github.com/containers/image/v5/transports/alltransports"
- "github.com/containers/image/v5/types"
- "github.com/containers/podman/v3/libpod/events"
- "github.com/containers/podman/v3/pkg/errorhandling"
- "github.com/containers/podman/v3/pkg/registries"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-var (
- // DockerArchive is the transport we prepend to an image name
- // when saving to docker-archive
- DockerArchive = dockerarchive.Transport.Name()
- // OCIArchive is the transport we prepend to an image name
- // when saving to oci-archive
- OCIArchive = ociarchive.Transport.Name()
- // DirTransport is the transport for pushing and pulling
- // images to and from a directory
- DirTransport = directory.Transport.Name()
- // DockerTransport is the transport for docker registries
- DockerTransport = docker.Transport.Name()
- // OCIDirTransport is the transport for pushing and pulling
- // images to and from a directory containing an OCI image
- OCIDirTransport = oci.Transport.Name()
- // AtomicTransport is the transport for atomic registries
- AtomicTransport = "atomic"
- // DefaultTransport is a prefix that we apply to an image name
- // NOTE: This is a string prefix, not actually a transport name usable for transports.Get();
- // and because syntaxes of image names are transport-dependent, the prefix is not really interchangeable;
- // each user implicitly assumes the appended string is a Docker-like reference.
- DefaultTransport = DockerTransport + "://"
- // DefaultLocalRegistry is the default local registry for local image operations
- // Remote pulls will still use defined registries
- DefaultLocalRegistry = "localhost"
-)
-
-// pullRefPair records a pair of prepared image references to pull.
-type pullRefPair struct {
- image string
- srcRef types.ImageReference
- dstRef types.ImageReference
- resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull
-}
-
-// cleanUpFunc is a function prototype for clean-up functions.
-type cleanUpFunc func() error
-
-// pullGoal represents the prepared image references and decided behavior to be executed by imagePull
-type pullGoal struct {
- refPairs []pullRefPair
- pullAllPairs bool // Pull all refPairs instead of stopping on first success.
- cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader)
- shortName string // Set when pulling a short name
- resolved *shortnames.Resolved // Set when pulling a short name
-}
-
-// cleanUp invokes all cleanUpFuncs. Certain resources may not be available
-// anymore. Errors are logged.
-func (p *pullGoal) cleanUp() {
- for _, f := range p.cleanUpFuncs {
- if err := f(); err != nil {
- logrus.Error(err.Error())
- }
- }
-}
-
-// singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair.
-func singlePullRefPairGoal(rp pullRefPair) *pullGoal {
- return &pullGoal{
- refPairs: []pullRefPair{rp},
- pullAllPairs: false, // Does not really make a difference.
- }
-}
-
-func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) {
- decomposedDest, err := decompose(destName)
- if err == nil && !decomposedDest.hasRegistry {
- // If the image doesn't have a registry, set it as the default repo
- ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry)
- if err != nil {
- return pullRefPair{}, err
- }
- destName = ref.String()
- }
-
- reference := destName
- if srcRef.DockerReference() != nil {
- reference = srcRef.DockerReference().String()
- }
- destRef, err := is.Transport.ParseStoreReference(ir.store, reference)
- if err != nil {
- return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
- }
- return pullRefPair{
- image: destName,
- srcRef: srcRef,
- dstRef: destRef,
- }, nil
-}
-
-// getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value.
-func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) {
- rp, err := ir.getPullRefPair(srcRef, destName)
- if err != nil {
- return nil, err
- }
- return singlePullRefPairGoal(rp), nil
-}
-
-// getPullRefPairsFromDockerArchiveReference returns a slice of pullRefPairs
-// for the specified docker reference and the corresponding archive.Reader.
-func (ir *Runtime) getPullRefPairsFromDockerArchiveReference(ctx context.Context, reader *dockerarchive.Reader, ref types.ImageReference, sc *types.SystemContext) ([]pullRefPair, error) {
- destNames, err := reader.ManifestTagsForReference(ref)
- if err != nil {
- return nil, err
- }
-
- if len(destNames) == 0 {
- destName, err := getImageDigest(ctx, ref, sc)
- if err != nil {
- return nil, err
- }
- destNames = append(destNames, destName)
- } else {
- for i := range destNames {
- ref, err := NormalizedTag(destNames[i])
- if err != nil {
- return nil, err
- }
- destNames[i] = ref.String()
- }
- }
-
- refPairs := []pullRefPair{}
- for _, destName := range destNames {
- destRef, err := is.Transport.ParseStoreReference(ir.store, destName)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
- }
- pair := pullRefPair{
- image: destName,
- srcRef: ref,
- dstRef: destRef,
- }
- refPairs = append(refPairs, pair)
- }
-
- return refPairs, nil
-}
-
-// pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport.
-// Note that callers are responsible for invoking (*pullGoal).cleanUp() to clean up possibly open resources.
-func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) {
- // supports pulling from docker-archive, oci, and registries
- switch srcRef.Transport().Name() {
- case DockerArchive:
- reader, readerRef, err := dockerarchive.NewReaderForReference(sc, srcRef)
- if err != nil {
- return nil, err
- }
-
- pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, readerRef, sc)
- if err != nil {
- // No need to defer for a single error path.
- if err := reader.Close(); err != nil {
- logrus.Error(err.Error())
- }
- return nil, err
- }
-
- return &pullGoal{
- pullAllPairs: true,
- refPairs: pairs,
- cleanUpFuncs: []cleanUpFunc{reader.Close},
- }, nil
-
- case OCIArchive:
- // retrieve the manifest from index.json to access the image name
- manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
- if err != nil {
- return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef)
- }
- var dest string
- if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
- // If the input image has no image.ref.name, we need to feed it a dest anyways
- // use the hex of the digest
- dest, err = getImageDigest(ctx, srcRef, sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting image digest; image reference not found")
- }
- } else {
- dest = manifest.Annotations["org.opencontainers.image.ref.name"]
- }
- return ir.getSinglePullRefPairGoal(srcRef, dest)
-
- case DirTransport:
- image := toLocalImageName(srcRef.StringWithinTransport())
- return ir.getSinglePullRefPairGoal(srcRef, image)
-
- case OCIDirTransport:
- split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2)
- image := toLocalImageName(split[0])
- return ir.getSinglePullRefPairGoal(srcRef, image)
-
- default:
- return ir.getSinglePullRefPairGoal(srcRef, imgName)
- }
-}
-
-// toLocalImageName converts an image name into a 'localhost/' prefixed one
-func toLocalImageName(imageName string) string {
- return fmt.Sprintf(
- "%s/%s",
- DefaultLocalRegistry,
- strings.TrimLeft(imageName, "/"),
- )
-}
-
-// pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries.
-// Use pullImageFromReference if the source is known precisely.
-func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string, progress chan types.ProgressProperties) ([]string, error) {
- var goal *pullGoal
- sc := GetSystemContext(signaturePolicyPath, authfile, false)
- if dockerOptions != nil {
- sc.OSChoice = dockerOptions.OSChoice
- sc.ArchitectureChoice = dockerOptions.ArchitectureChoice
- sc.VariantChoice = dockerOptions.VariantChoice
- sc.SystemRegistriesConfPath = dockerOptions.RegistriesConfPath
- }
- if signaturePolicyPath == "" {
- sc.SignaturePolicyPath = ir.SignaturePolicyPath
- }
- sc.BlobInfoCacheDir = filepath.Join(ir.store.GraphRoot(), "cache")
- srcRef, err := alltransports.ParseImageName(inputName)
- if err != nil {
- // We might be pulling with an unqualified image reference in which case
- // we need to make sure that we're not using any other transport.
- srcTransport := alltransports.TransportFromImageName(inputName)
- if srcTransport != nil && srcTransport.Name() != DockerTransport {
- return nil, err
- }
- goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName)
- if err != nil {
- return nil, errors.Wrap(err, "error getting default registries to try")
- }
- } else {
- goal, err = ir.pullGoalFromImageReference(ctx, srcRef, inputName, sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName)
- }
- }
- defer goal.cleanUp()
- return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label, progress)
-}
-
-// pullImageFromReference pulls an image from a types.imageReference.
-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) {
- sc := GetSystemContext(signaturePolicyPath, authfile, false)
- if dockerOptions != nil {
- sc.OSChoice = dockerOptions.OSChoice
- sc.ArchitectureChoice = dockerOptions.ArchitectureChoice
- sc.VariantChoice = dockerOptions.VariantChoice
- }
- goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef))
- }
- defer goal.cleanUp()
- return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil, nil)
-}
-
-func cleanErrorMessage(err error) string {
- errMessage := strings.TrimPrefix(errors.Cause(err).Error(), "errors:\n")
- errMessage = strings.Split(errMessage, "\n")[0]
- return fmt.Sprintf(" %s\n", errMessage)
-}
-
-// doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead.
-func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string, progress chan types.ProgressProperties) ([]string, error) {
- policyContext, err := getPolicyContext(sc)
- if err != nil {
- return nil, err
- }
- defer func() {
- if err := policyContext.Destroy(); err != nil {
- logrus.Errorf("failed to destroy policy context: %q", err)
- }
- }()
-
- var systemRegistriesConfPath string
- if dockerOptions != nil && dockerOptions.RegistriesConfPath != "" {
- systemRegistriesConfPath = dockerOptions.RegistriesConfPath
- } else {
- systemRegistriesConfPath = registries.SystemRegistriesConfPath()
- }
-
- var (
- images []string
- pullErrors []error
- )
-
- for _, imageInfo := range goal.refPairs {
- copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil)
- 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.
- if progress != nil {
- copyOptions.Progress = progress
- copyOptions.ProgressInterval = time.Second
- }
- // Print the following statement only when pulling from a docker or atomic registry
- if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) {
- if _, err := io.WriteString(writer, fmt.Sprintf("Trying to pull %s...\n", imageInfo.image)); err != nil {
- return nil, err
- }
- }
- // If the label is not nil, check if the label exists and if not, return err
- if label != nil {
- if err := checkRemoteImageForLabel(ctx, *label, imageInfo, sc); err != nil {
- return nil, err
- }
- }
- imageInfo := imageInfo
- if err = retry.RetryIfNecessary(ctx, func() error {
- _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions)
- return err
- }, retryOptions); err != nil {
- pullErrors = append(pullErrors, err)
- logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err)
- if writer != nil {
- _, _ = io.WriteString(writer, cleanErrorMessage(err))
- }
- } else {
- if imageInfo.resolvedShortname != nil {
- if err := imageInfo.resolvedShortname.Record(); err != nil {
- logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err)
- }
- }
- if !goal.pullAllPairs {
- ir.newImageEvent(events.Pull, "")
- return []string{imageInfo.image}, nil
- }
- images = append(images, imageInfo.image)
- }
- }
- // If no image was found, we should handle. Lets be nicer to the user
- // and see if we can figure out why.
- if len(images) == 0 {
- if goal.resolved != nil {
- return nil, goal.resolved.FormatPullErrors(pullErrors)
- }
- return nil, errorhandling.JoinErrors(pullErrors)
- }
-
- ir.newImageEvent(events.Pull, images[0])
- return images, nil
-}
-
-// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible
-// image references to try pulling in combination with the registries.conf file as well
-func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) {
- if sys == nil {
- sys = &types.SystemContext{}
- }
-
- resolved, err := shortnames.Resolve(sys, inputName)
- if err != nil {
- return nil, err
- }
-
- if desc := resolved.Description(); len(desc) > 0 {
- logrus.Debug(desc)
- if writer != nil {
- if _, err := writer.Write([]byte(desc + "\n")); err != nil {
- return nil, err
- }
- }
- }
-
- refPairs := []pullRefPair{}
- for i, candidate := range resolved.PullCandidates {
- srcRef, err := docker.NewReference(candidate.Value)
- if err != nil {
- return nil, err
- }
- ps, err := ir.getPullRefPair(srcRef, candidate.Value.String())
- if err != nil {
- return nil, err
- }
- ps.resolvedShortname = &resolved.PullCandidates[i]
- refPairs = append(refPairs, ps)
- }
- return &pullGoal{
- refPairs: refPairs,
- pullAllPairs: false,
- shortName: inputName,
- resolved: resolved,
- }, nil
-}
-
-// checkRemoteImageForLabel checks if the remote image has a specific label. if the label exists, we
-// return nil, else we return an error
-func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullRefPair, sc *types.SystemContext) error {
- labelImage, err := imageInfo.srcRef.NewImage(ctx, sc)
- if err != nil {
- return err
- }
- remoteInspect, err := labelImage.Inspect(ctx)
- if err != nil {
- return err
- }
- // Labels are case insensitive; so we iterate instead of simple lookup
- for k := range remoteInspect.Labels {
- if strings.EqualFold(label, k) {
- return nil
- }
- }
- return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels)
-}