From 44a515015c3766809089d260917a508bf94a73fd Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Tue, 31 Mar 2020 12:54:06 +0200 Subject: 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 --- cmd/podmanV2/images/push.go | 127 +++++++++++++++++++++++++++++++++ pkg/api/handlers/compat/images_push.go | 80 +++++++++++++++++++++ pkg/api/handlers/libpod/images.go | 122 +++++++++++++++++++++++++------ pkg/api/handlers/utils/images.go | 41 +++++++++++ pkg/api/server/register_images.go | 72 +++++++++++++++++++ pkg/bindings/images/images.go | 24 +++++++ pkg/domain/entities/engine_image.go | 1 + pkg/domain/entities/images.go | 37 ++++++++++ pkg/domain/infra/abi/images.go | 60 ++++++++++++++++ pkg/domain/infra/tunnel/images.go | 4 ++ 10 files changed, 547 insertions(+), 21 deletions(-) create mode 100644 cmd/podmanV2/images/push.go create mode 100644 pkg/api/handlers/compat/images_push.go diff --git a/cmd/podmanV2/images/push.go b/cmd/podmanV2/images/push.go new file mode 100644 index 000000000..82cc0c486 --- /dev/null +++ b/cmd/podmanV2/images/push.go @@ -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) +} diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go new file mode 100644 index 000000000..2260d5557 --- /dev/null +++ b/pkg/api/handlers/compat/images_push.go @@ -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, "") + +} diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index d4fd77cd7..e7f20854c 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -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 diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 696d5f745..1c67de9db 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -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) { diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 74b245a77..e4e46025b 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -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: diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 470ce546c..dcb568d6b 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -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 +} diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index ac856c05f..04b9d34e6 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -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 } diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index d136f42fd..d66de3c5e 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -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"` diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index f8c63730c..94008f287 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -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 { diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 155f5e4bd..028603d98 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -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) +} -- cgit v1.2.3-54-g00ecf