package buildah import ( "context" "io" "strings" "github.com/containers/buildah/pkg/blobcache" "github.com/containers/buildah/util" 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" "github.com/containers/image/v5/docker/reference" tarfile "github.com/containers/image/v5/docker/tarfile" ociarchive "github.com/containers/image/v5/oci/archive" oci "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/signature" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/types" "github.com/containers/storage" multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // PullOptions can be used to alter how an image is copied in from somewhere. type PullOptions struct { // SignaturePolicyPath specifies an override location for the signature // policy which should be used for verifying the new image as it is // being written. Except in specific circumstances, no value should be // specified, indicating that the shared, system-wide default policy // should be used. SignaturePolicyPath string // ReportWriter is an io.Writer which will be used to log the writing // of the new image. ReportWriter io.Writer // Store is the local storage store which holds the source image. Store storage.Store // github.com/containers/image/types SystemContext to hold credentials // and other authentication/authorization information. SystemContext *types.SystemContext // BlobDirectory is the name of a directory in which we'll attempt to // store copies of layer blobs that we pull down, if any. It should // already exist. BlobDirectory string // AllTags is a boolean value that determines if all tagged images // will be downloaded from the repository. The default is false. AllTags bool } func localImageNameForReference(ctx context.Context, store storage.Store, srcRef types.ImageReference) (string, error) { if srcRef == nil { return "", errors.Errorf("reference to image is empty") } var name string switch srcRef.Transport().Name() { case dockerarchive.Transport.Name(): file := srcRef.StringWithinTransport() tarSource, err := tarfile.NewSourceFromFile(file) if err != nil { return "", errors.Wrapf(err, "error opening tarfile %q as a source image", file) } manifest, err := tarSource.LoadTarManifest() if err != nil { return "", errors.Errorf("error retrieving manifest.json from tarfile %q: %v", file, err) } // to pull the first image stored in the tar file if len(manifest) == 0 { // use the hex of the digest if no manifest is found name, err = getImageDigest(ctx, srcRef, nil) if err != nil { return "", err } } else { if len(manifest[0].RepoTags) > 0 { name = manifest[0].RepoTags[0] } else { // If the input image has no repotags, we need to feed it a dest anyways name, err = getImageDigest(ctx, srcRef, nil) if err != nil { return "", err } } } case ociarchive.Transport.Name(): // retrieve the manifest from index.json to access the image name manifest, err := ociarchive.LoadManifestDescriptor(srcRef) if err != nil { return "", errors.Wrapf(err, "error loading manifest for %q", transports.ImageName(srcRef)) } // if index.json has no reference name, compute the image digest instead if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { name, err = getImageDigest(ctx, srcRef, nil) if err != nil { return "", err } } else { name = manifest.Annotations["org.opencontainers.image.ref.name"] } case directory.Transport.Name(): // supports pull from a directory name = toLocalImageName(srcRef.StringWithinTransport()) case oci.Transport.Name(): // supports pull from a directory split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2) name = toLocalImageName(split[0]) default: ref := srcRef.DockerReference() if ref == nil { name = srcRef.StringWithinTransport() _, err := is.Transport.ParseStoreReference(store, name) if err == nil { return name, nil } logrus.Debugf("error parsing local storage reference %q: %v", name, err) if strings.LastIndex(name, "/") != -1 { name = name[strings.LastIndex(name, "/")+1:] _, err = is.Transport.ParseStoreReference(store, name) if err == nil { return name, errors.Wrapf(err, "error parsing local storage reference %q", name) } } return "", errors.Errorf("reference to image %q is not a named reference", transports.ImageName(srcRef)) } if named, ok := ref.(reference.Named); ok { name = named.Name() if namedTagged, ok := ref.(reference.NamedTagged); ok { name = name + ":" + namedTagged.Tag() } if canonical, ok := ref.(reference.Canonical); ok { name = name + "@" + canonical.Digest().String() } } } if _, err := is.Transport.ParseStoreReference(store, name); err != nil { return "", errors.Wrapf(err, "error parsing computed local image name %q", name) } return name, nil } // Pull copies the contents of the image from somewhere else to local storage. Returns the // ID of the local image or an error. func Pull(ctx context.Context, imageName string, options PullOptions) (imageID string, err error) { systemContext := getSystemContext(options.Store, options.SystemContext, options.SignaturePolicyPath) boptions := BuilderOptions{ FromImage: imageName, SignaturePolicyPath: options.SignaturePolicyPath, SystemContext: systemContext, BlobDirectory: options.BlobDirectory, ReportWriter: options.ReportWriter, } storageRef, transport, img, err := resolveImage(ctx, systemContext, options.Store, boptions) if err != nil { return "", err } var errs *multierror.Error if options.AllTags { if transport != util.DefaultTransport { return "", errors.New("Non-docker transport is not supported, for --all-tags pulling") } repo := reference.TrimNamed(storageRef.DockerReference()) dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) if err != nil { return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) } tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) if err != nil { return "", errors.Wrapf(err, "error getting repository tags") } for _, tag := range tags { tagged, err := reference.WithTag(repo, tag) if err != nil { errs = multierror.Append(errs, err) continue } taggedRef, err := docker.NewReference(tagged) if err != nil { return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) } if options.ReportWriter != nil { if _, err := options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")); err != nil { return "", errors.Wrapf(err, "error writing pull report") } } ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) if err != nil { errs = multierror.Append(errs, err) continue } taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) if err != nil { errs = multierror.Append(errs, err) continue } imageID = taggedImg.ID } } else { imageID = img.ID } return imageID, errs.ErrorOrNil() } func pullImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { blocked, err := isReferenceBlocked(srcRef, sc) if err != nil { return nil, errors.Wrapf(err, "error checking if pulling from registry for %q is blocked", transports.ImageName(srcRef)) } if blocked { return nil, errors.Errorf("pull access to registry for %q is blocked by configuration", transports.ImageName(srcRef)) } if err := checkRegistrySourcesAllows("pull from", srcRef); err != nil { return nil, err } destName, err := localImageNameForReference(ctx, store, srcRef) if err != nil { return nil, errors.Wrapf(err, "error computing local image name for %q", transports.ImageName(srcRef)) } if destName == "" { return nil, errors.Errorf("error computing local image name for %q", transports.ImageName(srcRef)) } destRef, err := is.Transport.ParseStoreReference(store, destName) if err != nil { return nil, errors.Wrapf(err, "error parsing image name %q", destName) } var maybeCachedDestRef = types.ImageReference(destRef) if options.BlobDirectory != "" { cachedRef, err := blobcache.NewBlobCache(destRef, options.BlobDirectory, types.PreserveOriginal) if err != nil { return nil, errors.Wrapf(err, "error wrapping image reference %q in blob cache at %q", transports.ImageName(destRef), options.BlobDirectory) } maybeCachedDestRef = cachedRef } policy, err := signature.DefaultPolicy(sc) if err != nil { return nil, errors.Wrapf(err, "error obtaining default signature policy") } policyContext, err := signature.NewPolicyContext(policy) if err != nil { return nil, errors.Wrapf(err, "error creating new signature policy context") } defer func() { if err2 := policyContext.Destroy(); err2 != nil { logrus.Debugf("error destroying signature policy context: %v", err2) } }() logrus.Debugf("copying %q to %q", transports.ImageName(srcRef), destName) if _, err := cp.Image(ctx, policyContext, maybeCachedDestRef, srcRef, getCopyOptions(store, options.ReportWriter, sc, nil, "")); err != nil { logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", transports.ImageName(srcRef), destName, err) return nil, err } return destRef, nil } // getImageDigest creates an image object and uses the hex value of the digest as the image ID // for parsing the store reference func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.SystemContext) (string, error) { newImg, err := src.NewImage(ctx, sc) if err != nil { return "", errors.Wrapf(err, "error opening image %q for reading", transports.ImageName(src)) } defer newImg.Close() digest := newImg.ConfigInfo().Digest if err = digest.Validate(); err != nil { return "", errors.Wrapf(err, "error getting config info from image %q", transports.ImageName(src)) } return "@" + digest.Hex(), nil } // toLocalImageName converts an image name into a 'localhost/' prefixed one func toLocalImageName(imageName string) string { return "localhost/" + strings.TrimLeft(imageName, "/") }