podmanV2: implement push

* Implement `podman-push` and `podman-image-push` for the podmanV2
  client.

* Tests for `pkg/bindings` are not possible at the time of writing as we
  don't have a local registry running.

* Implement `/images/{name}/push` compat endpoint. Tests are not
  implemented for this v2 endpoint.  It has been tested manually.

General note: The auth config extraction from the http header is not
implement for push.  Since it's not yet supported for other endpoints
either, I deferred it to future work.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2020-03-31 12:54:06 +02:00
parent ffd2d78391
commit 44a515015c
10 changed files with 547 additions and 21 deletions

127
cmd/podmanV2/images/push.go Normal file
View File

@ -0,0 +1,127 @@
package images
import (
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// pushOptionsWrapper wraps entities.ImagepushOptions and prevents leaking
// CLI-only fields into the API types.
type pushOptionsWrapper struct {
entities.ImagePushOptions
TLSVerifyCLI bool // CLI only
}
var (
pushOptions = pushOptionsWrapper{}
pushDescription = `Pushes a source image to a specified destination.
The Image "DESTINATION" uses a "transport":"details" format. See podman-push(1) section "DESTINATION" for the expected format.`
// Command: podman push
pushCmd = &cobra.Command{
Use: "push [flags] SOURCE DESTINATION",
Short: "Push an image to a specified destination",
Long: pushDescription,
PreRunE: preRunE,
RunE: imagePush,
Example: `podman push imageID docker://registry.example.com/repository:tag
podman push imageID oci-archive:/path/to/layout:image:tag`,
}
// Command: podman image push
// It's basically a clone of `pushCmd` with the exception of being a
// child of the images command.
imagePushCmd = &cobra.Command{
Use: pushCmd.Use,
Short: pushCmd.Short,
Long: pushCmd.Long,
PreRunE: pushCmd.PreRunE,
RunE: pushCmd.RunE,
Example: `podman image push imageID docker://registry.example.com/repository:tag
podman image push imageID oci-archive:/path/to/layout:image:tag`,
}
)
func init() {
// push
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: pushCmd,
})
pushCmd.SetHelpTemplate(registry.HelpTemplate())
pushCmd.SetUsageTemplate(registry.UsageTemplate())
flags := pushCmd.Flags()
pushFlags(flags)
// images push
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: imagePushCmd,
Parent: imageCmd,
})
imagePushCmd.SetHelpTemplate(registry.HelpTemplate())
imagePushCmd.SetUsageTemplate(registry.UsageTemplate())
pushFlags(imagePushCmd.Flags())
}
// pushFlags set the flags for the push command.
func pushFlags(flags *pflag.FlagSet) {
flags.StringVar(&pushOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.StringVar(&pushOptions.CertDir, "cert-dir", "", "Path to a directory containing TLS certificates and keys")
flags.BoolVar(&pushOptions.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)")
flags.StringVar(&pushOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&pushOptions.DigestFile, "digestfile", "", "Write the digest of the pushed image to the specified file")
flags.StringVarP(&pushOptions.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir' transport (default is manifest type of source)")
flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images")
flags.BoolVar(&pushOptions.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image")
flags.StringVar(&pushOptions.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file")
flags.StringVar(&pushOptions.SignBy, "sign-by", "", "Add a signature at the destination using the specified key")
flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
if registry.IsRemote() {
_ = flags.MarkHidden("authfile")
_ = flags.MarkHidden("cert-dir")
_ = flags.MarkHidden("compress")
_ = flags.MarkHidden("quiet")
_ = flags.MarkHidden("signature-policy")
_ = flags.MarkHidden("tls-verify")
}
}
// imagePush is implement the command for pushing images.
func imagePush(cmd *cobra.Command, args []string) error {
var source, destination string
switch len(args) {
case 1:
source = args[0]
case 2:
source = args[0]
destination = args[1]
case 0:
fallthrough
default:
return errors.New("push requires at least one image name, or optionally a second to specify a different destination")
}
pushOptsAPI := pushOptions.ImagePushOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI)
}
// Let's do all the remaining Yoga in the API to prevent us from scattering
// logic across (too) many parts of the code.
return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptsAPI)
}

View File

@ -0,0 +1,80 @@
package compat
import (
"context"
"net/http"
"os"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
// PushImage is the handler for the compat http endpoint for pushing images.
func PushImage(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
Tag string `schema:"tag"`
}{
// This is where you can override the golang default value for one of fields
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
// Note that Docker's docs state "Image name or ID" to be in the path
// parameter but it really must be a name as Docker does not allow for
// pushing an image by ID.
imageName := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
if query.Tag != "" {
imageName += ":" + query.Tag
}
if _, err := utils.ParseStorageReference(imageName); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", imageName))
return
}
newImage, err := runtime.ImageRuntime().NewFromLocal(imageName)
if err != nil {
utils.ImageNotFound(w, imageName, errors.Wrapf(err, "Failed to find image %s", imageName))
return
}
// TODO: the X-Registry-Auth header is not checked yet here nor in any other
// endpoint. Pushing does NOT work with authentication at the moment.
dockerRegistryOptions := &image.DockerRegistryOptions{}
authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
authfile = sys.AuthFilePath
}
err = newImage.PushImageToHeuristicDestination(
context.Background(),
imageName,
"", // manifest type
authfile,
"", // digest file
"", // signature policy
os.Stderr,
false, // force compression
image.SigningOptions{},
dockerRegistryOptions,
nil, // additional tags
)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Error pushing image %q", imageName))
return
}
utils.WriteResponse(w, http.StatusOK, "")
}

View File

@ -14,7 +14,6 @@ import (
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
@ -331,29 +330,16 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
return
}
// Enforce the docker transport. This is just a precaution as some callers
// might be accustomed to using the "transport:reference" notation. Using
// another than the "docker://" transport does not really make sense for a
// remote case. For loading tarballs, the load and import endpoints should
// be used.
dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
imageRef, err := alltransports.ParseImageName(query.Reference)
if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
imageRef, err := utils.ParseDockerReference(query.Reference)
if err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Errorf("reference %q must be a docker reference", query.Reference))
errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference))
return
} else if err != nil {
origErr := err
imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, query.Reference))
if err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(origErr, "reference %q must be a docker reference", query.Reference))
return
}
}
// Trim the docker-transport prefix.
rawImage := strings.TrimPrefix(query.Reference, dockerPrefix)
rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name()))
// all-tags doesn't work with a tagged reference, so let's check early
namedRef, err := reference.Parse(rawImage)
@ -385,7 +371,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
OSChoice: query.OverrideOS,
ArchitectureChoice: query.OverrideArch,
}
if query.TLSVerify {
if _, found := r.URL.Query()["tlsVerify"]; found {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
@ -408,13 +394,19 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
}
}
authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
authfile = sys.AuthFilePath
}
// Finally pull the images
for _, img := range imagesToPull {
newImage, err := runtime.ImageRuntime().New(
context.Background(),
img,
"",
"",
authfile,
os.Stderr,
&dockerRegistryOptions,
image.SigningOptions{},
@ -430,6 +422,94 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, res)
}
// PushImage is the handler for the compat http endpoint for pushing images.
func PushImage(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
Credentials string `schema:"credentials"`
Destination string `schema:"destination"`
TLSVerify bool `schema:"tlsVerify"`
}{
// This is where you can override the golang default value for one of fields
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
if _, err := utils.ParseStorageReference(source); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", source))
return
}
destination := query.Destination
if destination == "" {
destination = source
}
if _, err := utils.ParseDockerReference(destination); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "image destination %q is not a docker-transport reference", destination))
return
}
newImage, err := runtime.ImageRuntime().NewFromLocal(source)
if err != nil {
utils.ImageNotFound(w, source, errors.Wrapf(err, "Failed to find image %s", source))
return
}
var registryCreds *types.DockerAuthConfig
if len(query.Credentials) != 0 {
creds, err := util.ParseRegistryCreds(query.Credentials)
if err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "error parsing credentials %q", query.Credentials))
return
}
registryCreds = creds
}
// TODO: the X-Registry-Auth header is not checked yet here nor in any other
// endpoint. Pushing does NOT work with authentication at the moment.
dockerRegistryOptions := &image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
}
authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
authfile = sys.AuthFilePath
}
if _, found := r.URL.Query()["tlsVerify"]; found {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
err = newImage.PushImageToHeuristicDestination(
context.Background(),
destination,
"", // manifest type
authfile,
"", // digest file
"", // signature policy
os.Stderr,
false, // force compression
image.SigningOptions{},
dockerRegistryOptions,
nil, // additional tags
)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Error pushing image %q", destination))
return
}
utils.WriteResponse(w, http.StatusOK, "")
}
func CommitContainer(w http.ResponseWriter, r *http.Request) {
var (
destImage string

View File

@ -4,11 +4,52 @@ import (
"fmt"
"net/http"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/storage"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
// ParseDockerReference parses the specified image name to a
// `types.ImageReference` and enforces it to refer to a docker-transport
// reference.
func ParseDockerReference(name string) (types.ImageReference, error) {
dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
imageRef, err := alltransports.ParseImageName(name)
if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
return nil, errors.Errorf("reference %q must be a docker reference", name)
} else if err != nil {
origErr := err
imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, name))
if err != nil {
return nil, errors.Wrapf(origErr, "reference %q must be a docker reference", name)
}
}
return imageRef, nil
}
// ParseStorageReference parses the specified image name to a
// `types.ImageReference` and enforces it to refer to a
// containers-storage-transport reference.
func ParseStorageReference(name string) (types.ImageReference, error) {
storagePrefix := fmt.Sprintf("%s:", storage.Transport.Name())
imageRef, err := alltransports.ParseImageName(name)
if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
return nil, errors.Errorf("reference %q must be a storage reference", name)
} else if err != nil {
origErr := err
imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", storagePrefix, name))
if err != nil {
return nil, errors.Wrapf(origErr, "reference %q must be a storage reference", name)
}
}
return imageRef, nil
}
// GetImages is a common function used to get images for libpod and other compatibility
// mechanisms
func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {

View File

@ -211,6 +211,41 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
r.Handle(VersionedPath("/images/{name:.*}"), s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete)
// Added non version path to URI to support docker non versioned paths
r.Handle("/images/{name:.*}", s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation POST /images/{name:.*}/push compat pushImage
// ---
// tags:
// - images (compat)
// summary: Push Image
// description: Push an image to a container registry
// parameters:
// - in: path
// name: name:.*
// type: string
// required: true
// description: Name of image to push.
// - in: query
// name: tag
// type: string
// description: The tag to associate with the image on the registry.
// - in: header
// name: X-Registry-Auth
// type: string
// description: A base64-encoded auth configuration.
// produces:
// - application/json
// responses:
// 200:
// description: no error
// schema:
// type: string
// format: binary
// 404:
// $ref: '#/responses/NoSuchImage'
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/images/{name:.*}/push"), s.APIHandler(compat.PushImage)).Methods(http.MethodPost)
// Added non version path to URI to support docker non versioned paths
r.Handle("/images/{name:.*}/push", s.APIHandler(compat.PushImage)).Methods(http.MethodPost)
// swagger:operation GET /images/{name:.*}/get compat exportImage
// ---
// tags:
@ -583,6 +618,43 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
libpod endpoints
*/
// swagger:operation POST /libpod/images/{name:.*}/push libpod libpodPushImage
// ---
// tags:
// - images (libpod)
// summary: Push Image
// description: Push an image to a container registry
// parameters:
// - in: path
// name: name:.*
// type: string
// required: true
// description: Name of image to push.
// - in: query
// name: tag
// type: string
// description: The tag to associate with the image on the registry.
// - in: query
// name: credentials
// description: username:password for the registry.
// type: string
// - in: header
// name: X-Registry-Auth
// type: string
// description: A base64-encoded auth configuration.
// produces:
// - application/json
// responses:
// 200:
// description: no error
// schema:
// type: string
// format: binary
// 404:
// $ref: '#/responses/NoSuchImage'
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/{name:.*}/push"), s.APIHandler(libpod.PushImage)).Methods(http.MethodPost)
// swagger:operation GET /libpod/images/{name:.*}/exists libpod libpodImageExists
// ---
// tags:

View File

@ -3,6 +3,7 @@ package images
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
@ -283,3 +284,26 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption
return pulledImages, nil
}
// Push is the binding for libpod's v2 endpoints for push images. Note that
// `source` must be a refering to an image in the remote's container storage.
// The destination must be a reference to a registry (i.e., of docker transport
// or be normalized to one). Other transports are rejected as they do not make
// sense in a remote context.
func Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
params := url.Values{}
params.Set("credentials", options.Credentials)
params.Set("destination", destination)
if options.TLSVerify != types.OptionalBoolUndefined {
val := bool(options.TLSVerify == types.OptionalBoolTrue)
params.Set("tlsVerify", strconv.FormatBool(val))
}
path := fmt.Sprintf("/images/%s/push", source)
_, err = conn.DoRequest(nil, http.MethodPost, path, params)
return err
}

View File

@ -16,4 +16,5 @@ type ImageEngine interface {
Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error
Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error)
Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error)
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
}

View File

@ -144,6 +144,43 @@ type ImagePullReport struct {
Images []string
}
// ImagePushOptions are the arguments for pushing images.
type ImagePushOptions struct {
// 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
// Compress tarball image layers when pushing to a directory using the 'dir'
// transport. Default is same compression type as source. Ignored for remote
// calls.
Compress bool
// Credentials for authenticating against the registry in the format
// USERNAME:PASSWORD.
Credentials string
// DigestFile, after copying the image, write the digest of the resulting
// image to the file. Ignored for remote calls.
DigestFile string
// Format is the Manifest type (oci, v2s1, or v2s2) to use when pushing an
// image using the 'dir' transport. Default is manifest type of source.
// Ignored for remote calls.
Format string
// Quiet can be specified to suppress pull progress when pulling. Ignored
// for remote calls.
Quiet bool
// RemoveSignatures, discard any pre-existing signatures in the image.
// Ignored for remote calls.
RemoveSignatures bool
// SignaturePolicy to use when pulling. Ignored for remote calls.
SignaturePolicy string
// SignBy adds a signature at the destination using the specified key.
// Ignored for remote calls.
SignBy string
// TLSVerify to enable/disable HTTPS and certificate verification.
TLSVerify types.OptionalBool
}
type ImageListOptions struct {
All bool `json:"all" schema:"all"`
Filter []string `json:"Filter,omitempty"`

View File

@ -12,6 +12,7 @@ import (
"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/manifest"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/image"
@ -20,6 +21,7 @@ import (
domainUtils "github.com/containers/libpod/pkg/domain/utils"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -260,6 +262,64 @@ func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entitie
return &report, nil
}
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error {
var writer io.Writer
if !options.Quiet {
writer = os.Stderr
}
var manifestType string
switch options.Format {
case "":
// Default
case "oci":
manifestType = imgspecv1.MediaTypeImageManifest
case "v2s1":
manifestType = manifest.DockerV2Schema1SignedMediaType
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format)
}
var registryCreds *types.DockerAuthConfig
if options.Credentials != "" {
creds, err := util.ParseRegistryCreds(options.Credentials)
if err != nil {
return err
}
registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
DockerInsecureSkipTLSVerify: options.TLSVerify,
}
signOptions := image.SigningOptions{
RemoveSignatures: options.RemoveSignatures,
SignBy: options.SignBy,
}
newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source)
if err != nil {
return err
}
return newImage.PushImageToHeuristicDestination(
ctx,
destination,
manifestType,
options.Authfile,
options.DigestFile,
options.SignaturePolicy,
writer,
options.Compress,
signOptions,
&dockerRegistryOptions,
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 {

View File

@ -184,3 +184,7 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti
}
return images.Import(ir.ClientCxt, opts.Changes, &opts.Message, &opts.Reference, sourceURL, f)
}
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error {
return images.Push(ir.ClientCxt, source, destination, options)
}