From 3bdad6fa2af41be7e783256f3c8a213dc42ca8f6 Mon Sep 17 00:00:00 2001
From: Valentin Rothberg <rothberg@redhat.com>
Date: Mon, 30 Mar 2020 12:13:50 +0200
Subject: podmanV2: implement pull

Implement pulling images for the v2 client.  What I _really_ don't like
is the fact that we are now having a near identical code clone among
`pkg/domain/infra/abi` and `pkg/api/handlers/libpod`.  Partly because we
don't yet have a higher-level pull function and partly because we have
redudancy among `pkg/domain` and `pkg/api`.  Pull might be a high
outlier but I am concerned already by the potential of introducing more
redundancy.  I'd love to `infra/abi` and `pkg/abi` to really use the
same code in the future.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
---
 pkg/domain/entities/engine_image.go |   1 +
 pkg/domain/entities/images.go       |  34 ++++++++++++
 pkg/domain/infra/abi/images.go      | 100 ++++++++++++++++++++++++++++++++++++
 pkg/domain/infra/tunnel/images.go   |   8 +++
 4 files changed, 143 insertions(+)

(limited to 'pkg/domain')

diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index d0c860a04..cdb8d7f0d 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -10,4 +10,5 @@ type ImageEngine interface {
 	History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
 	List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)
 	Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
+	Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
 }
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 20af0356f..8e3f49be3 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -4,6 +4,7 @@ import (
 	"net/url"
 
 	"github.com/containers/image/v5/manifest"
+	"github.com/containers/image/v5/types"
 	docker "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/opencontainers/go-digest"
@@ -117,6 +118,39 @@ type ImageInspectOptions struct {
 	Latest     bool   `json:",omitempty"`
 }
 
+// ImagePullOptions are the arguments for pulling images.
+type ImagePullOptions struct {
+	// AllTags can be specified to pull all tags of the spiecifed image. Note
+	// that this only works if the specified image does not include a tag.
+	AllTags bool
+	// Authfile is the path to the authentication file. Ignored for remote
+	// calls.
+	Authfile string
+	// CertDir is the path to certificate directories.  Ignored for remote
+	// calls.
+	CertDir string
+	// Credentials for authenticating against the registry in the format
+	// USERNAME:PASSWORD.
+	Credentials string
+	// OverrideArch will overwrite the local architecture for image pulls.
+	OverrideArch string
+	// OverrideOS will overwrite the local operating system (OS) for image
+	// pulls.
+	OverrideOS string
+	// Quiet can be specified to suppress pull progress when pulling.  Ignored
+	// for remote calls.
+	Quiet bool
+	// SignaturePolicy to use when pulling.  Ignored for remote calls.
+	SignaturePolicy string
+	// TLSVerify to enable/disable HTTPS and certificate verification.
+	TLSVerify types.OptionalBool
+}
+
+// ImagePullReport is the response from pulling one or more images.
+type ImagePullReport struct {
+	Images []string
+}
+
 type ImageListOptions struct {
 	All     bool       `json:"all" schema:"all"`
 	Filter  []string   `json:"Filter,omitempty"`
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 44420c1e1..ef2879246 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -5,11 +5,22 @@ package abi
 import (
 	"context"
 	"fmt"
+	"io"
+	"os"
+	"strings"
 
+	"github.com/containers/image/v5/docker"
+	dockerarchive "github.com/containers/image/v5/docker/archive"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/transports/alltransports"
+	"github.com/containers/image/v5/types"
+	"github.com/containers/libpod/libpod/image"
 	libpodImage "github.com/containers/libpod/libpod/image"
 	"github.com/containers/libpod/pkg/domain/entities"
+	"github.com/containers/libpod/pkg/util"
 	"github.com/containers/storage"
 	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
 )
 
 func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
@@ -134,6 +145,95 @@ func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer
 	return l
 }
 
+func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) {
+	var writer io.Writer
+	if !options.Quiet {
+		writer = os.Stderr
+	}
+
+	dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
+	imageRef, err := alltransports.ParseImageName(rawImage)
+	if err != nil {
+		imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage))
+		if err != nil {
+			return nil, errors.Errorf("invalid image reference %q", rawImage)
+		}
+	}
+
+	// Special-case for docker-archive which allows multiple tags.
+	if imageRef.Transport().Name() == dockerarchive.Transport.Name() {
+		newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error pulling image %q", rawImage)
+		}
+		return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil
+	}
+
+	var registryCreds *types.DockerAuthConfig
+	if options.Credentials != "" {
+		creds, err := util.ParseRegistryCreds(options.Credentials)
+		if err != nil {
+			return nil, err
+		}
+		registryCreds = creds
+	}
+	dockerRegistryOptions := image.DockerRegistryOptions{
+		DockerRegistryCreds:         registryCreds,
+		DockerCertPath:              options.CertDir,
+		OSChoice:                    options.OverrideOS,
+		ArchitectureChoice:          options.OverrideArch,
+		DockerInsecureSkipTLSVerify: options.TLSVerify,
+	}
+
+	if !options.AllTags {
+		newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error pulling image %q", rawImage)
+		}
+		return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil
+	}
+
+	// --all-tags requires the docker transport
+	if imageRef.Transport().Name() != docker.Transport.Name() {
+		return nil, errors.New("--all-tags requires docker transport")
+	}
+
+	// Trim the docker-transport prefix.
+	rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name())
+
+	// all-tags doesn't work with a tagged reference, so let's check early
+	namedRef, err := reference.Parse(rawImage)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error parsing %q", rawImage)
+	}
+	if _, isTagged := namedRef.(reference.Tagged); isTagged {
+		return nil, errors.New("--all-tags requires a reference without a tag")
+
+	}
+
+	systemContext := image.GetSystemContext("", options.Authfile, false)
+	tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error getting repository tags")
+	}
+
+	var foundIDs []string
+	for _, tag := range tags {
+		name := rawImage + ":" + tag
+		newImage, err := ir.Libpod.ImageRuntime().New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
+		if err != nil {
+			logrus.Errorf("error pulling image %q", name)
+			continue
+		}
+		foundIDs = append(foundIDs, newImage.ID())
+	}
+
+	if len(tags) != len(foundIDs) {
+		return nil, errors.Errorf("error pulling image %q", rawImage)
+	}
+	return &entities.ImagePullReport{Images: foundIDs}, nil
+}
+
 // func (r *imageRuntime) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
 // 	image, err := r.libpod.ImageEngine().NewFromLocal(nameOrId)
 // 	if err != nil {
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 6a3adc9ee..7638d908a 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -85,3 +85,11 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
 	}
 	return &report, nil
 }
+
+func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) {
+	pulledImages, err := images.Pull(ir.ClientCxt, rawImage, options)
+	if err != nil {
+		return nil, err
+	}
+	return &entities.ImagePullReport{Images: pulledImages}, nil
+}
-- 
cgit v1.2.3-54-g00ecf