mirror of
https://github.com/containers/podman.git
synced 2025-08-06 03:19:52 +08:00
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:
127
cmd/podmanV2/images/push.go
Normal file
127
cmd/podmanV2/images/push.go
Normal 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)
|
||||
}
|
80
pkg/api/handlers/compat/images_push.go
Normal file
80
pkg/api/handlers/compat/images_push.go
Normal 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, "")
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"`
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user