summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2021-01-17 06:52:35 -0500
committerGitHub <noreply@github.com>2021-01-17 06:52:35 -0500
commit341c4b1fd94dda26be5ce588490e3d7284a54dbb (patch)
tree82f8c6bfa113b8b350aa069e81436b88bddf3080
parent73b036db566a8f3606f0b376728efe03fcf8685d (diff)
parentcf51c7ed9f955390a0e417f208046e0b8fbadb26 (diff)
downloadpodman-341c4b1fd94dda26be5ce588490e3d7284a54dbb.tar.gz
podman-341c4b1fd94dda26be5ce588490e3d7284a54dbb.tar.bz2
podman-341c4b1fd94dda26be5ce588490e3d7284a54dbb.zip
Merge pull request #8942 from rhatdan/push
Allow podman push to push manifest lists
-rw-r--r--cmd/podman/images/push.go2
-rw-r--r--cmd/podman/manifest/push.go19
-rw-r--r--cmd/podman/utils/alias.go2
-rw-r--r--docs/source/markdown/podman-manifest-push.1.md10
-rw-r--r--docs/source/markdown/podman-push.1.md11
-rw-r--r--docs/source/markdown/podman.1.md2
-rw-r--r--pkg/api/handlers/libpod/images.go49
-rw-r--r--pkg/api/handlers/libpod/manifests.go61
-rw-r--r--pkg/bindings/images/types.go2
-rw-r--r--pkg/bindings/images/types_push_options.go16
-rw-r--r--pkg/bindings/manifests/manifests.go13
-rw-r--r--pkg/bindings/manifests/types.go6
-rw-r--r--pkg/bindings/manifests/types_push_options.go104
-rw-r--r--pkg/domain/entities/engine_image.go2
-rw-r--r--pkg/domain/entities/images.go4
-rw-r--r--pkg/domain/entities/manifest.go8
-rw-r--r--pkg/domain/infra/abi/images.go40
-rw-r--r--pkg/domain/infra/abi/manifest.go27
-rw-r--r--pkg/domain/infra/tunnel/manifest.go22
-rw-r--r--test/e2e/manifest_test.go35
20 files changed, 189 insertions, 246 deletions
diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go
index d82083cd8..d53a9c066 100644
--- a/cmd/podman/images/push.go
+++ b/cmd/podman/images/push.go
@@ -75,6 +75,8 @@ func init() {
func pushFlags(cmd *cobra.Command) {
flags := cmd.Flags()
+ // For now default All flag to true, for pushing of manifest lists
+ pushOptions.All = true
authfileFlagName := "authfile"
flags.StringVar(&pushOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
_ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go
index 96fea4a21..40b45c40e 100644
--- a/cmd/podman/manifest/push.go
+++ b/cmd/podman/manifest/push.go
@@ -1,11 +1,14 @@
package manifest
import (
+ "io/ioutil"
+
"github.com/containers/common/pkg/auth"
"github.com/containers/common/pkg/completion"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
@@ -15,7 +18,7 @@ import (
// manifestPushOptsWrapper wraps entities.ManifestPushOptions and prevents leaking
// CLI-only fields into the API types.
type manifestPushOptsWrapper struct {
- entities.ManifestPushOptions
+ entities.ImagePushOptions
TLSVerifyCLI bool // CLI only
CredentialsCLI string
@@ -41,8 +44,8 @@ func init() {
Parent: manifestCmd,
})
flags := pushCmd.Flags()
- flags.BoolVar(&manifestPushOpts.Purge, "purge", false, "remove the manifest list if push succeeds")
- flags.BoolVar(&manifestPushOpts.All, "all", false, "also push the images in the list")
+ flags.BoolVar(&manifestPushOpts.Rm, "rm", false, "remove the manifest list if push succeeds")
+ flags.BoolVar(&manifestPushOpts.All, "all", true, "also push the images in the list")
authfileFlagName := "authfile"
flags.StringVar(&manifestPushOpts.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
@@ -72,6 +75,7 @@ func init() {
flags.BoolVar(&manifestPushOpts.TLSVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists")
+ flags.SetNormalizeFunc(utils.AliasFlags)
if registry.IsRemote() {
_ = flags.MarkHidden("cert-dir")
@@ -107,8 +111,15 @@ func push(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("tls-verify") {
manifestPushOpts.SkipTLSVerify = types.NewOptionalBool(!manifestPushOpts.TLSVerifyCLI)
}
- if err := registry.ImageEngine().ManifestPush(registry.Context(), args[0], args[1], manifestPushOpts.ManifestPushOptions); err != nil {
+ digest, err := registry.ImageEngine().ManifestPush(registry.Context(), args[0], args[1], manifestPushOpts.ImagePushOptions)
+ if err != nil {
return err
}
+ if manifestPushOpts.DigestFile != "" {
+ if err := ioutil.WriteFile(manifestPushOpts.DigestFile, []byte(digest), 0644); err != nil {
+ return err
+ }
+ }
+
return nil
}
diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go
index 10b96fa98..469233b59 100644
--- a/cmd/podman/utils/alias.go
+++ b/cmd/podman/utils/alias.go
@@ -23,6 +23,8 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
name = "ns"
case "storage":
name = "external"
+ case "purge":
+ name = "rm"
}
return pflag.NormalizedName(name)
}
diff --git a/docs/source/markdown/podman-manifest-push.1.md b/docs/source/markdown/podman-manifest-push.1.md
index 0c1e5ea04..2beb65517 100644
--- a/docs/source/markdown/podman-manifest-push.1.md
+++ b/docs/source/markdown/podman-manifest-push.1.md
@@ -17,7 +17,7 @@ The list image's ID and the digest of the image's manifest.
#### **--all**
Push the images mentioned in the manifest list or image index, in addition to
-the list or index itself.
+the list or index itself. (Default true)
#### **--authfile**=*path*
@@ -46,14 +46,14 @@ After copying the image, write the digest of the resulting image to the file.
Manifest list type (oci or v2s2) to use when pushing the list (default is oci).
-#### **--purge**
-
-Delete the manifest list or image index from local storage if pushing succeeds.
-
#### **--quiet**, **-q**
When writing the manifest, suppress progress output
+#### **--rm**
+
+Delete the manifest list or image index from local storage if pushing succeeds.
+
#### **--remove-signatures**
Don't copy signatures when pushing images.
diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md
index 68ea528cb..f7624ed5f 100644
--- a/docs/source/markdown/podman-push.1.md
+++ b/docs/source/markdown/podman-push.1.md
@@ -1,7 +1,7 @@
% podman-push(1)
## NAME
-podman\-push - Push an image from local storage to elsewhere
+podman\-push - Push an image, manifest list or image index from local storage to elsewhere
## SYNOPSIS
**podman push** [*options*] *image* [*destination*]
@@ -9,10 +9,11 @@ podman\-push - Push an image from local storage to elsewhere
**podman image push** [*options*] *image* [*destination*]
## DESCRIPTION
-Pushes an image from local storage to a specified destination.
-Push is mainly used to push images to registries, however **podman push**
-can be used to save images to tarballs and directories using the following
-transports: **dir:**, **docker-archive:**, **docker-daemon:** and **oci-archive:**.
+Pushes an image, manifest list or image index from local storage to a specified
+destination. Push is mainly used to push images to registries, however
+**podman push** can be used to save images to tarballs and directories using the
+following transports:
+**dir:**, **docker-archive:**, **docker-daemon:** and **oci-archive:**.
## Image storage
Images are pushed from those stored in local image storage.
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index 67484c3ec..733fadc30 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -246,7 +246,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-port(1)](podman-port.1.md) | List port mappings for a container. |
| [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. |
| [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. |
-| [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. |
+| [podman-push(1)](podman-push.1.md) | Push an image, manifest list or image index from local storage to elsewhere.|
| [podman-rename(1)](podman-rename.1.md) | Rename an existing container. |
| [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
| [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. |
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 5b15527b7..97cd5a65e 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -25,7 +25,6 @@ import (
utils2 "github.com/containers/podman/v2/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
// Commit
@@ -410,6 +409,8 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
query := struct {
Destination string `schema:"destination"`
TLSVerify bool `schema:"tlsVerify"`
+ Format string `schema:"format"`
+ All bool `schema:"all"`
}{
// This is where you can override the golang default value for one of fields
}
@@ -434,45 +435,31 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
- newImage, err := runtime.ImageRuntime().NewFromLocal(source)
- if err != nil {
- utils.ImageNotFound(w, source, errors.Wrapf(err, "failed to find image %s", source))
- return
- }
-
- authConf, authfile, key, err := auth.GetCredentials(r)
+ authconf, authfile, key, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
return
}
defer auth.RemoveAuthfile(authfile)
- logrus.Errorf("AuthConf: %v", authConf)
+ var username, password string
+ if authconf != nil {
+ username = authconf.Username
+ password = authconf.Password
- dockerRegistryOptions := &image.DockerRegistryOptions{
- DockerRegistryCreds: authConf,
}
- if sys := runtime.SystemContext(); sys != nil {
- dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
+ options := entities.ImagePushOptions{
+ Authfile: authfile,
+ Username: username,
+ Password: password,
+ Format: query.Format,
+ All: query.All,
}
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 {
+ options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+ if err := imageEngine.Push(context.Background(), source, destination, options); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination))
return
}
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index 2031dd42f..dce861f6f 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -1,17 +1,18 @@
package libpod
import (
+ "context"
"encoding/json"
"net/http"
- "github.com/containers/buildah/manifests"
- copy2 "github.com/containers/image/v5/copy"
"github.com/containers/image/v5/manifest"
- "github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/image/v5/types"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/image"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/auth"
+ "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/opencontainers/go-digest"
@@ -123,15 +124,13 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
}
func ManifestPush(w http.ResponseWriter, r *http.Request) {
- // FIXME: parameters are missing (tlsVerify, format).
- // Also, we should use the ABI function to avoid duplicate code.
- // Also, support for XRegistryAuth headers are missing.
-
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool `schema:"all"`
Destination string `schema:"destination"`
+ Format string `schema:"format"`
+ TLSVerify bool `schema:"tlsVerify"`
}{
// Add defaults here once needed.
}
@@ -140,35 +139,43 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
- name := utils.GetName(r)
- newImage, err := runtime.ImageRuntime().NewFromLocal(name)
- if err != nil {
- utils.ImageNotFound(w, name, err)
+ if _, err := utils.ParseDockerReference(query.Destination); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
return
}
- dest, err := alltransports.ParseImageName(query.Destination)
+
+ source := utils.GetName(r)
+ authConf, authfile, key, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "invalid destination parameter", http.StatusBadRequest, errors.Errorf("invalid destination parameter %q", query.Destination))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
return
}
- rtc, err := runtime.GetConfig()
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ defer auth.RemoveAuthfile(authfile)
+ var username, password string
+ if authConf != nil {
+ username = authConf.Username
+ password = authConf.Password
+
}
- sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
- opts := manifests.PushOptions{
- Store: runtime.GetStore(),
- ImageListSelection: copy2.CopySpecificImages,
- SystemContext: sc,
+
+ options := entities.ImagePushOptions{
+ Authfile: authfile,
+ Username: username,
+ Password: password,
+ Format: query.Format,
+ All: query.All,
+ }
+ if sys := runtime.SystemContext(); sys != nil {
+ options.CertDir = sys.DockerCertPath
}
- if query.All {
- opts.ImageListSelection = copy2.CopyAllImages
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
- newD, err := newImage.PushManifest(dest, opts)
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+ digest, err := imageEngine.ManifestPush(context.Background(), source, query.Destination, options)
if err != nil {
- utils.InternalServerError(w, err)
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", query.Destination))
return
}
- utils.WriteResponse(w, http.StatusOK, newD.String())
+ utils.WriteResponse(w, http.StatusOK, digest)
}
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
index 3adb4356b..b3799b8c4 100644
--- a/pkg/bindings/images/types.go
+++ b/pkg/bindings/images/types.go
@@ -99,6 +99,8 @@ type ImportOptions struct {
//go:generate go run ../generator/generator.go PushOptions
// PushOptions are optional options for importing images
type PushOptions struct {
+ // All indicates whether to push all images related to the image list
+ All *bool
// Authfile is the path to the authentication file. Ignored for remote
// calls.
Authfile *string
diff --git a/pkg/bindings/images/types_push_options.go b/pkg/bindings/images/types_push_options.go
index 15210f30b..0c12ce4ac 100644
--- a/pkg/bindings/images/types_push_options.go
+++ b/pkg/bindings/images/types_push_options.go
@@ -87,6 +87,22 @@ func (o *PushOptions) ToParams() (url.Values, error) {
return params, nil
}
+// WithAll
+func (o *PushOptions) WithAll(value bool) *PushOptions {
+ v := &value
+ o.All = v
+ return o
+}
+
+// GetAll
+func (o *PushOptions) GetAll() bool {
+ var all bool
+ if o.All == nil {
+ return all
+ }
+ return *o.All
+}
+
// WithAuthfile
func (o *PushOptions) WithAuthfile(value string) *PushOptions {
v := &value
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index ef4e2a908..b6db64b02 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -5,11 +5,13 @@ import (
"errors"
"net/http"
"net/url"
+ "strconv"
"strings"
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/bindings"
+ "github.com/containers/podman/v2/pkg/bindings/images"
jsoniter "github.com/json-iterator/go"
)
@@ -112,12 +114,12 @@ func Remove(ctx context.Context, name, digest string, options *RemoveOptions) (s
// Push takes a manifest list and pushes to a destination. If the destination is not specified,
// the name will be used instead. If the optional all boolean is specified, all images specified
// in the list will be pushed as well.
-func Push(ctx context.Context, name, destination string, options *PushOptions) (string, error) {
+func Push(ctx context.Context, name, destination string, options *images.PushOptions) (string, error) {
var (
idr handlers.IDResponse
)
if options == nil {
- options = new(PushOptions)
+ options = new(images.PushOptions)
}
if len(destination) < 1 {
destination = name
@@ -130,8 +132,15 @@ func Push(ctx context.Context, name, destination string, options *PushOptions) (
if err != nil {
return "", err
}
+ //SkipTLSVerify is special. We need to delete the param added by
+ //toparams and change the key and flip the bool
+ if options.SkipTLSVerify != nil {
+ params.Del("SkipTLSVerify")
+ params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
+ }
params.Set("image", name)
params.Set("destination", destination)
+ params.Set("format", *options.Format)
_, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
if err != nil {
return "", err
diff --git a/pkg/bindings/manifests/types.go b/pkg/bindings/manifests/types.go
index f5054d424..7f84d69fc 100644
--- a/pkg/bindings/manifests/types.go
+++ b/pkg/bindings/manifests/types.go
@@ -28,9 +28,3 @@ type AddOptions struct {
// RemoveOptions are optional options for removing manifests
type RemoveOptions struct {
}
-
-//go:generate go run ../generator/generator.go PushOptions
-// RemoveOptions are optional options for pushing manifests
-type PushOptions struct {
- All *bool
-}
diff --git a/pkg/bindings/manifests/types_push_options.go b/pkg/bindings/manifests/types_push_options.go
deleted file mode 100644
index 1d689f699..000000000
--- a/pkg/bindings/manifests/types_push_options.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package manifests
-
-import (
- "net/url"
- "reflect"
- "strconv"
- "strings"
-
- jsoniter "github.com/json-iterator/go"
- "github.com/pkg/errors"
-)
-
-/*
-This file is generated automatically by go generate. Do not edit.
-*/
-
-// Changed
-func (o *PushOptions) Changed(fieldName string) bool {
- r := reflect.ValueOf(o)
- value := reflect.Indirect(r).FieldByName(fieldName)
- return !value.IsNil()
-}
-
-// ToParams
-func (o *PushOptions) ToParams() (url.Values, error) {
- params := url.Values{}
- if o == nil {
- return params, nil
- }
- json := jsoniter.ConfigCompatibleWithStandardLibrary
- s := reflect.ValueOf(o)
- if reflect.Ptr == s.Kind() {
- s = s.Elem()
- }
- sType := s.Type()
- for i := 0; i < s.NumField(); i++ {
- fieldName := sType.Field(i).Name
- if !o.Changed(fieldName) {
- continue
- }
- fieldName = strings.ToLower(fieldName)
- f := s.Field(i)
- if reflect.Ptr == f.Kind() {
- f = f.Elem()
- }
- switch f.Kind() {
- case reflect.Bool:
- params.Set(fieldName, strconv.FormatBool(f.Bool()))
- case reflect.String:
- params.Set(fieldName, f.String())
- case reflect.Int, reflect.Int64:
- // f.Int() is always an int64
- params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
- case reflect.Uint, reflect.Uint64:
- // f.Uint() is always an uint64
- params.Set(fieldName, strconv.FormatUint(f.Uint(), 10))
- case reflect.Slice:
- typ := reflect.TypeOf(f.Interface()).Elem()
- switch typ.Kind() {
- case reflect.String:
- sl := f.Slice(0, f.Len())
- s, ok := sl.Interface().([]string)
- if !ok {
- return nil, errors.New("failed to convert to string slice")
- }
- for _, val := range s {
- params.Add(fieldName, val)
- }
- default:
- return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
- }
- case reflect.Map:
- lowerCaseKeys := make(map[string][]string)
- iter := f.MapRange()
- for iter.Next() {
- lowerCaseKeys[iter.Key().Interface().(string)] = iter.Value().Interface().([]string)
-
- }
- s, err := json.MarshalToString(lowerCaseKeys)
- if err != nil {
- return nil, err
- }
-
- params.Set(fieldName, s)
- }
- }
- return params, nil
-}
-
-// WithAll
-func (o *PushOptions) WithAll(value bool) *PushOptions {
- v := &value
- o.All = v
- return o
-}
-
-// GetAll
-func (o *PushOptions) GetAll() bool {
- var all bool
- if o.All == nil {
- return all
- }
- return *o.All
-}
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 26a136f13..935ee6f20 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -36,6 +36,6 @@ type ImageEngine interface {
ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error)
ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error)
ManifestRemove(ctx context.Context, names []string) (string, error)
- ManifestPush(ctx context.Context, name, destination string, manifestPushOpts ManifestPushOptions) error
+ ManifestPush(ctx context.Context, name, destination string, imagePushOpts ImagePushOptions) (string, error)
Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error)
}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 0805152c3..78a7d8aa7 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -165,6 +165,8 @@ type ImagePullReport struct {
// ImagePushOptions are the arguments for pushing images.
type ImagePushOptions struct {
+ // All indicates that all images referenced in an manifest list should be pushed
+ All bool
// Authfile is the path to the authentication file. Ignored for remote
// calls.
Authfile string
@@ -189,6 +191,8 @@ type ImagePushOptions struct {
// Quiet can be specified to suppress pull progress when pulling. Ignored
// for remote calls.
Quiet bool
+ // Rm indicates whether to remove the manifest list if push succeeds
+ Rm bool
// RemoveSignatures, discard any pre-existing signatures in the image.
// Ignored for remote calls.
RemoveSignatures bool
diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go
index 01180951a..6a645e20b 100644
--- a/pkg/domain/entities/manifest.go
+++ b/pkg/domain/entities/manifest.go
@@ -33,11 +33,3 @@ type ManifestAnnotateOptions struct {
OSVersion string `json:"os_version" schema:"os_version"`
Variant string `json:"variant" schema:"variant"`
}
-
-type ManifestPushOptions struct {
- Purge, Quiet, All, RemoveSignatures bool
-
- Authfile, CertDir, Username, Password, DigestFile, Format, SignBy string
-
- SkipTLSVerify types.OptionalBool
-}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 1c233d9d5..1288ab09b 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -367,7 +367,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
return err
}
- return newImage.PushImageToHeuristicDestination(
+ err = newImage.PushImageToHeuristicDestination(
ctx,
destination,
manifestType,
@@ -379,39 +379,15 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
signOptions,
&dockerRegistryOptions,
nil)
+ if err != nil && errors.Cause(err) != storage.ErrImageUnknown {
+ // Image might be a manifest list so attempt a manifest push
+ if _, manifestErr := ir.ManifestPush(ctx, source, destination, options); manifestErr == nil {
+ return nil
+ }
+ }
+ return err
}
-// 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 {
-// return nil, err
-// }
-//
-// results, err := r.libpod.RemoveImage(ctx, image, opts.Force)
-// if err != nil {
-// return nil, err
-// }
-//
-// report := entities.ImageDeleteReport{}
-// if err := domainUtils.DeepCopy(&report, results); err != nil {
-// return nil, err
-// }
-// return &report, nil
-// }
-//
-// func (r *imageRuntime) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
-// // TODO: map FilterOptions
-// id, err := r.libpod.ImageEngine().PruneImages(ctx, opts.All, []string{})
-// if err != nil {
-// return nil, err
-// }
-//
-// // TODO: Determine Size
-// report := entities.ImagePruneReport{}
-// copy(report.Report.ID, id)
-// return &report, nil
-// }
-
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
if err != nil {
diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go
index a68ed8788..139032ad6 100644
--- a/pkg/domain/infra/abi/manifest.go
+++ b/pkg/domain/infra/abi/manifest.go
@@ -7,7 +7,6 @@ import (
"context"
"encoding/json"
"fmt"
- "io/ioutil"
"os"
"strings"
@@ -24,9 +23,8 @@ import (
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/sirupsen/logrus"
-
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// ManifestCreate implements logic for creating manifest lists via ImageEngine
@@ -243,14 +241,20 @@ func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (stri
}
// ManifestPush pushes a manifest list or image index to the destination
-func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ManifestPushOptions) error {
+func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) {
listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name)
if err != nil {
- return errors.Wrapf(err, "error retrieving local image from image name %s", name)
+ return "", errors.Wrapf(err, "error retrieving local image from image name %s", name)
}
dest, err := alltransports.ParseImageName(destination)
if err != nil {
- return err
+ oldErr := err
+ // Try adding the images default transport
+ destination2 := libpodImage.DefaultTransport + destination
+ dest, err = alltransports.ParseImageName(destination2)
+ if err != nil {
+ return "", oldErr
+ }
}
var manifestType string
@@ -261,7 +265,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
- return errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format)
+ return "", errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format)
}
}
@@ -297,13 +301,8 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin
options.ReportWriter = os.Stderr
}
manDigest, err := listImage.PushManifest(dest, options)
- if err == nil && opts.Purge {
+ if err == nil && opts.Rm {
_, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true)
}
- if opts.DigestFile != "" {
- if err = ioutil.WriteFile(opts.DigestFile, []byte(manDigest.String()), 0644); err != nil {
- return buildahUtil.GetFailureCause(err, errors.Wrapf(err, "failed to write digest to file %q", opts.DigestFile))
- }
- }
- return err
+ return manDigest.String(), err
}
diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go
index c71349fe0..22ca44165 100644
--- a/pkg/domain/infra/tunnel/manifest.go
+++ b/pkg/domain/infra/tunnel/manifest.go
@@ -6,6 +6,8 @@ import (
"fmt"
"strings"
+ "github.com/containers/image/v5/types"
+ images "github.com/containers/podman/v2/pkg/bindings/images"
"github.com/containers/podman/v2/pkg/bindings/manifests"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@@ -73,8 +75,20 @@ func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (stri
}
// ManifestPush pushes a manifest list or image index to the destination
-func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ManifestPushOptions) error {
- options := new(manifests.PushOptions).WithAll(opts.All)
- _, err := manifests.Push(ir.ClientCtx, name, destination, options)
- return err
+func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) {
+ options := new(images.PushOptions)
+ options.WithUsername(opts.Username).WithSignaturePolicy(opts.SignaturePolicy).WithQuiet(opts.Quiet)
+ options.WithPassword(opts.Password).WithCertDir(opts.CertDir).WithAuthfile(opts.Authfile)
+ options.WithCompress(opts.Compress).WithDigestFile(opts.DigestFile).WithFormat(opts.Format)
+ options.WithRemoveSignatures(opts.RemoveSignatures).WithSignBy(opts.SignBy)
+
+ if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
+ if s == types.OptionalBoolTrue {
+ options.WithSkipTLSVerify(true)
+ } else {
+ options.WithSkipTLSVerify(false)
+ }
+ }
+ digest, err := manifests.Push(ir.ClientCtx, name, destination, options)
+ return digest, err
}
diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go
index bc47f7500..3e13057d3 100644
--- a/test/e2e/manifest_test.go
+++ b/test/e2e/manifest_test.go
@@ -171,6 +171,7 @@ var _ = Describe("Podman manifest", func() {
})
It("podman manifest push", func() {
+ SkipIfRemote("manifest push to dir not supported in remote mode")
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -199,8 +200,38 @@ var _ = Describe("Podman manifest", func() {
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
})
- It("podman manifest push purge", func() {
- SkipIfRemote("remote does not support --purge")
+ It("podman push --all", func() {
+ SkipIfRemote("manifest push to dir not supported in remote mode")
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ dest := filepath.Join(podmanTest.TempDir, "pushed")
+ err := os.MkdirAll(dest, os.ModePerm)
+ Expect(err).To(BeNil())
+ defer func() {
+ os.RemoveAll(dest)
+ }()
+ session = podmanTest.Podman([]string{"push", "foo", "dir:" + dest})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ files, err := filepath.Glob(dest + string(os.PathSeparator) + "*")
+ Expect(err).To(BeNil())
+ check := SystemExec("sha256sum", files)
+ check.WaitWithDefaultTimeout()
+ Expect(check.ExitCode()).To(Equal(0))
+ prefix := "sha256:"
+ Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListAMD64InstanceDigest, prefix)))
+ Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARMInstanceDigest, prefix)))
+ Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListPPC64LEInstanceDigest, prefix)))
+ Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListS390XInstanceDigest, prefix)))
+ Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
+ })
+
+ It("podman manifest push --rm", func() {
+ SkipIfRemote("remote does not support --rm")
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))