2017-02-11 00:48:15 +08:00
|
|
|
package buildah
|
|
|
|
|
|
|
|
import (
|
2018-04-12 22:20:36 +08:00
|
|
|
"context"
|
2019-01-07 15:06:36 +08:00
|
|
|
"fmt"
|
2018-08-09 04:38:53 +08:00
|
|
|
"io"
|
2017-07-29 05:29:37 +08:00
|
|
|
"strings"
|
|
|
|
|
2018-10-18 06:06:16 +08:00
|
|
|
"github.com/containers/buildah/pkg/blobcache"
|
2018-09-18 03:20:16 +08:00
|
|
|
"github.com/containers/buildah/util"
|
2017-06-05 21:42:30 +08:00
|
|
|
cp "github.com/containers/image/copy"
|
2019-02-02 20:29:05 +08:00
|
|
|
"github.com/containers/image/directory"
|
2019-01-07 15:06:36 +08:00
|
|
|
"github.com/containers/image/docker"
|
2019-02-02 20:29:05 +08:00
|
|
|
dockerarchive "github.com/containers/image/docker/archive"
|
2017-02-14 00:44:47 +08:00
|
|
|
"github.com/containers/image/docker/reference"
|
2018-03-19 10:16:47 +08:00
|
|
|
tarfile "github.com/containers/image/docker/tarfile"
|
|
|
|
ociarchive "github.com/containers/image/oci/archive"
|
2019-02-20 05:59:59 +08:00
|
|
|
oci "github.com/containers/image/oci/layout"
|
2017-02-11 00:48:15 +08:00
|
|
|
"github.com/containers/image/signature"
|
|
|
|
is "github.com/containers/image/storage"
|
2017-07-29 05:29:37 +08:00
|
|
|
"github.com/containers/image/transports"
|
2017-03-22 04:38:50 +08:00
|
|
|
"github.com/containers/image/transports/alltransports"
|
2017-02-11 00:48:15 +08:00
|
|
|
"github.com/containers/image/types"
|
2017-05-17 23:53:28 +08:00
|
|
|
"github.com/containers/storage"
|
2019-02-22 06:35:51 +08:00
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
2017-10-10 03:05:56 +08:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-02-11 00:48:15 +08:00
|
|
|
)
|
|
|
|
|
2018-08-09 04:38:53 +08:00
|
|
|
// 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
|
2018-10-18 06:06:16 +08:00
|
|
|
// 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
|
2019-01-07 15:06:36 +08:00
|
|
|
// AllTags is a boolean value that determines if all tagged images
|
|
|
|
// will be downloaded from the repository. The default is false.
|
|
|
|
AllTags bool
|
2018-08-09 04:38:53 +08:00
|
|
|
}
|
|
|
|
|
2019-02-22 07:01:08 +08:00
|
|
|
func localImageNameForReference(ctx context.Context, store storage.Store, srcRef types.ImageReference) (string, error) {
|
2017-07-29 05:29:37 +08:00
|
|
|
if srcRef == nil {
|
|
|
|
return "", errors.Errorf("reference to image is empty")
|
|
|
|
}
|
2018-03-19 10:16:47 +08:00
|
|
|
var name string
|
|
|
|
switch srcRef.Transport().Name() {
|
2019-02-02 20:29:05 +08:00
|
|
|
case dockerarchive.Transport.Name():
|
2019-02-22 07:01:08 +08:00
|
|
|
file := srcRef.StringWithinTransport()
|
2018-03-19 10:16:47 +08:00
|
|
|
tarSource, err := tarfile.NewSourceFromFile(file)
|
|
|
|
if err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return "", errors.Wrapf(err, "error opening tarfile %q as a source image", file)
|
2017-07-29 05:29:37 +08:00
|
|
|
}
|
2018-03-19 10:16:47 +08:00
|
|
|
manifest, err := tarSource.LoadTarManifest()
|
|
|
|
if err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return "", errors.Errorf("error retrieving manifest.json from tarfile %q: %v", file, err)
|
2018-03-19 10:16:47 +08:00
|
|
|
}
|
|
|
|
// 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
|
2018-04-12 22:20:36 +08:00
|
|
|
name, err = getImageDigest(ctx, srcRef, nil)
|
2018-03-19 10:16:47 +08:00
|
|
|
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
|
2018-04-12 22:20:36 +08:00
|
|
|
name, err = getImageDigest(ctx, srcRef, nil)
|
2018-03-19 10:16:47 +08:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-02 20:29:05 +08:00
|
|
|
case ociarchive.Transport.Name():
|
2018-03-19 10:16:47 +08:00
|
|
|
// retrieve the manifest from index.json to access the image name
|
|
|
|
manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
|
|
|
|
if err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return "", errors.Wrapf(err, "error loading manifest for %q", transports.ImageName(srcRef))
|
2018-03-19 10:16:47 +08:00
|
|
|
}
|
2018-05-17 00:38:11 +08:00
|
|
|
// if index.json has no reference name, compute the image digest instead
|
2018-03-19 10:16:47 +08:00
|
|
|
if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
|
2018-05-17 00:38:11 +08:00
|
|
|
name, err = getImageDigest(ctx, srcRef, nil)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
name = manifest.Annotations["org.opencontainers.image.ref.name"]
|
2018-03-19 10:16:47 +08:00
|
|
|
}
|
2019-02-02 20:29:05 +08:00
|
|
|
case directory.Transport.Name():
|
2018-03-19 10:16:47 +08:00
|
|
|
// supports pull from a directory
|
2019-02-22 07:01:08 +08:00
|
|
|
name = srcRef.StringWithinTransport()
|
2018-03-19 10:16:47 +08:00
|
|
|
// remove leading "/"
|
|
|
|
if name[:1] == "/" {
|
|
|
|
name = name[1:]
|
|
|
|
}
|
2019-02-20 05:59:59 +08:00
|
|
|
case oci.Transport.Name():
|
|
|
|
// supports pull from a directory
|
2019-02-22 07:01:08 +08:00
|
|
|
split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2)
|
|
|
|
name = split[0]
|
2019-02-20 05:59:59 +08:00
|
|
|
// remove leading "/"
|
|
|
|
if name[:1] == "/" {
|
|
|
|
name = name[1:]
|
|
|
|
}
|
2018-03-19 10:16:47 +08:00
|
|
|
default:
|
|
|
|
ref := srcRef.DockerReference()
|
|
|
|
if ref == nil {
|
2018-04-19 16:52:24 +08:00
|
|
|
name = srcRef.StringWithinTransport()
|
2018-03-19 10:16:47 +08:00
|
|
|
_, err := is.Transport.ParseStoreReference(store, name)
|
2017-07-29 05:29:37 +08:00
|
|
|
if err == nil {
|
|
|
|
return name, nil
|
|
|
|
}
|
2018-10-03 22:05:46 +08:00
|
|
|
logrus.Debugf("error parsing local storage reference %q: %v", name, err)
|
2018-03-19 10:16:47 +08:00
|
|
|
if strings.LastIndex(name, "/") != -1 {
|
|
|
|
name = name[strings.LastIndex(name, "/")+1:]
|
|
|
|
_, err = is.Transport.ParseStoreReference(store, name)
|
|
|
|
if err == nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return name, errors.Wrapf(err, "error parsing local storage reference %q", name)
|
2018-03-19 10:16:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", errors.Errorf("reference to image %q is not a named reference", transports.ImageName(srcRef))
|
2017-07-29 05:29:37 +08:00
|
|
|
}
|
|
|
|
|
2018-03-19 10:16:47 +08:00
|
|
|
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()
|
|
|
|
}
|
2017-07-29 05:29:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := is.Transport.ParseStoreReference(store, name); err != nil {
|
|
|
|
return "", errors.Wrapf(err, "error parsing computed local image name %q", name)
|
|
|
|
}
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
2018-10-03 04:44:12 +08:00
|
|
|
// Pull copies the contents of the image from somewhere else to local storage.
|
2019-01-07 15:06:36 +08:00
|
|
|
func Pull(ctx context.Context, imageName string, options PullOptions) error {
|
2018-08-09 04:38:53 +08:00
|
|
|
systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath)
|
2019-02-02 20:29:05 +08:00
|
|
|
|
|
|
|
boptions := BuilderOptions{
|
|
|
|
FromImage: imageName,
|
|
|
|
SignaturePolicyPath: options.SignaturePolicyPath,
|
|
|
|
SystemContext: systemContext,
|
|
|
|
PullBlobDirectory: options.BlobDirectory,
|
|
|
|
ReportWriter: options.ReportWriter,
|
2019-01-07 15:06:36 +08:00
|
|
|
}
|
2019-02-02 20:29:05 +08:00
|
|
|
|
|
|
|
storageRef, transport, img, err := resolveImage(ctx, systemContext, options.Store, boptions)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-01-24 02:33:09 +08:00
|
|
|
}
|
2019-02-02 20:29:05 +08:00
|
|
|
|
|
|
|
var errs *multierror.Error
|
2019-01-07 15:06:36 +08:00
|
|
|
if options.AllTags {
|
2019-02-02 20:29:05 +08:00
|
|
|
if transport != util.DefaultTransport {
|
|
|
|
return errors.New("Non-docker transport is not supported, for --all-tags pulling")
|
|
|
|
}
|
|
|
|
|
2019-02-22 06:45:30 +08:00
|
|
|
repo := reference.TrimNamed(storageRef.DockerReference())
|
2019-02-22 07:08:02 +08:00
|
|
|
dockerRef, err := alltransports.ParseImageName(transport + storageRef.DockerReference().String())
|
2019-02-02 20:29:05 +08:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error getting repository tags")
|
2019-01-07 15:06:36 +08:00
|
|
|
}
|
2019-02-22 06:35:51 +08:00
|
|
|
tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef)
|
2019-01-07 15:06:36 +08:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error getting repository tags")
|
|
|
|
}
|
|
|
|
for _, tag := range tags {
|
2019-02-22 06:45:30 +08:00
|
|
|
tagged, err := reference.WithTag(repo, tag)
|
|
|
|
if err != nil {
|
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
continue
|
|
|
|
}
|
2019-02-02 20:29:05 +08:00
|
|
|
if options.ReportWriter != nil {
|
2019-02-22 06:45:30 +08:00
|
|
|
options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n"))
|
2019-02-02 20:29:05 +08:00
|
|
|
}
|
2019-02-22 06:45:30 +08:00
|
|
|
ref, err := pullImage(ctx, options.Store, transport, tagged.String(), options, systemContext)
|
2019-02-02 20:29:05 +08:00
|
|
|
if err != nil {
|
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
continue
|
|
|
|
}
|
2019-02-23 10:08:09 +08:00
|
|
|
taggedImg, err := is.Transport.GetStoreImage(options.Store, ref)
|
2019-02-02 20:29:05 +08:00
|
|
|
if err != nil {
|
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
continue
|
|
|
|
}
|
2019-02-23 10:08:09 +08:00
|
|
|
fmt.Printf("%s\n", taggedImg.ID)
|
2019-01-07 15:06:36 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fmt.Printf("%s\n", img.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return errs.ErrorOrNil()
|
2018-08-09 04:38:53 +08:00
|
|
|
}
|
|
|
|
|
2019-02-02 20:29:05 +08:00
|
|
|
func pullImage(ctx context.Context, store storage.Store, transport string, imageName string, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) {
|
2019-02-22 06:48:21 +08:00
|
|
|
if transport == "" {
|
|
|
|
transport = util.DefaultTransport
|
|
|
|
} else {
|
|
|
|
if transport != util.DefaultTransport {
|
|
|
|
transport = transport + ":"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spec := transport + imageName
|
2017-06-29 05:07:58 +08:00
|
|
|
srcRef, err := alltransports.ParseImageName(spec)
|
2017-02-11 00:48:15 +08:00
|
|
|
if err != nil {
|
2019-02-22 06:48:21 +08:00
|
|
|
return nil, errors.Wrapf(err, "error parsing image name %q", spec)
|
2017-02-11 00:48:15 +08:00
|
|
|
}
|
2018-10-03 04:44:12 +08:00
|
|
|
logrus.Debugf("parsed image name %q", spec)
|
2017-02-11 00:48:15 +08:00
|
|
|
|
2018-10-03 02:48:11 +08:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2019-02-22 07:01:08 +08:00
|
|
|
destName, err := localImageNameForReference(ctx, store, srcRef)
|
2017-07-29 05:29:37 +08:00
|
|
|
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))
|
2017-02-11 00:48:15 +08:00
|
|
|
}
|
|
|
|
|
2017-07-29 05:29:37 +08:00
|
|
|
destRef, err := is.Transport.ParseStoreReference(store, destName)
|
2017-02-11 00:48:15 +08:00
|
|
|
if err != nil {
|
2017-07-29 05:29:37 +08:00
|
|
|
return nil, errors.Wrapf(err, "error parsing image name %q", destName)
|
2017-02-11 00:48:15 +08:00
|
|
|
}
|
2018-10-18 06:06:16 +08:00
|
|
|
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
|
|
|
|
}
|
2017-02-11 00:48:15 +08:00
|
|
|
|
|
|
|
policy, err := signature.DefaultPolicy(sc)
|
|
|
|
if err != nil {
|
2017-07-29 05:29:37 +08:00
|
|
|
return nil, errors.Wrapf(err, "error obtaining default signature policy")
|
2017-02-11 00:48:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
policyContext, err := signature.NewPolicyContext(policy)
|
|
|
|
if err != nil {
|
2017-07-29 05:29:37 +08:00
|
|
|
return nil, errors.Wrapf(err, "error creating new signature policy context")
|
2017-02-11 00:48:15 +08:00
|
|
|
}
|
|
|
|
|
2017-07-29 00:58:29 +08:00
|
|
|
defer func() {
|
|
|
|
if err2 := policyContext.Destroy(); err2 != nil {
|
2017-06-29 05:07:58 +08:00
|
|
|
logrus.Debugf("error destroying signature policy context: %v", err2)
|
2017-07-29 00:58:29 +08:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-06-29 05:07:58 +08:00
|
|
|
logrus.Debugf("copying %q to %q", spec, destName)
|
2018-10-18 06:06:16 +08:00
|
|
|
if _, err := cp.Image(ctx, policyContext, maybeCachedDestRef, srcRef, getCopyOptions(options.ReportWriter, srcRef, sc, maybeCachedDestRef, nil, "")); err != nil {
|
2018-10-20 05:24:49 +08:00
|
|
|
logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", spec, destName, err)
|
2018-08-04 09:29:16 +08:00
|
|
|
return nil, err
|
2017-06-29 05:07:58 +08:00
|
|
|
}
|
2018-08-04 09:29:16 +08:00
|
|
|
return destRef, nil
|
2017-02-11 00:48:15 +08:00
|
|
|
}
|
2018-03-19 10:16:47 +08:00
|
|
|
|
|
|
|
// getImageDigest creates an image object and uses the hex value of the digest as the image ID
|
|
|
|
// for parsing the store reference
|
2018-04-12 22:20:36 +08:00
|
|
|
func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.SystemContext) (string, error) {
|
|
|
|
newImg, err := src.NewImage(ctx, sc)
|
2018-03-19 10:16:47 +08:00
|
|
|
if err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return "", errors.Wrapf(err, "error opening image %q for reading", transports.ImageName(src))
|
2018-03-19 10:16:47 +08:00
|
|
|
}
|
|
|
|
defer newImg.Close()
|
|
|
|
|
|
|
|
digest := newImg.ConfigInfo().Digest
|
|
|
|
if err = digest.Validate(); err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return "", errors.Wrapf(err, "error getting config info from image %q", transports.ImageName(src))
|
2018-03-19 10:16:47 +08:00
|
|
|
}
|
|
|
|
return "@" + digest.Hex(), nil
|
|
|
|
}
|