mirror of
https://github.com/containers/podman
synced 2024-10-19 08:44:11 +00:00
Merge pull request #8942 from rhatdan/push
Allow podman push to push manifest lists
This commit is contained in:
commit
341c4b1fd9
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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)
|
||||
options.SkipTLSVerify = 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 {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
defer auth.RemoveAuthfile(authfile)
|
||||
var username, password string
|
||||
if authConf != nil {
|
||||
username = authConf.Username
|
||||
password = authConf.Password
|
||||
|
||||
}
|
||||
|
||||
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 _, found := r.URL.Query()["tlsVerify"]; found {
|
||||
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||
}
|
||||
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
|
||||
}
|
||||
sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
|
||||
opts := manifests.PushOptions{
|
||||
Store: runtime.GetStore(),
|
||||
ImageListSelection: copy2.CopySpecificImages,
|
||||
SystemContext: sc,
|
||||
}
|
||||
if query.All {
|
||||
opts.ImageListSelection = copy2.CopyAllImages
|
||||
}
|
||||
newD, err := newImage.PushManifest(dest, opts)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, newD.String())
|
||||
utils.WriteResponse(w, http.StatusOK, digest)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue