mirror of
https://github.com/containers/podman.git
synced 2025-07-01 16:17:06 +08:00
Merge pull request #8942 from rhatdan/push
Allow podman push to push manifest lists
This commit is contained in:
@ -75,6 +75,8 @@ func init() {
|
|||||||
func pushFlags(cmd *cobra.Command) {
|
func pushFlags(cmd *cobra.Command) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
|
// For now default All flag to true, for pushing of manifest lists
|
||||||
|
pushOptions.All = true
|
||||||
authfileFlagName := "authfile"
|
authfileFlagName := "authfile"
|
||||||
flags.StringVar(&pushOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
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)
|
_ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package manifest
|
package manifest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/containers/common/pkg/auth"
|
"github.com/containers/common/pkg/auth"
|
||||||
"github.com/containers/common/pkg/completion"
|
"github.com/containers/common/pkg/completion"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/containers/podman/v2/cmd/podman/common"
|
"github.com/containers/podman/v2/cmd/podman/common"
|
||||||
"github.com/containers/podman/v2/cmd/podman/registry"
|
"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/domain/entities"
|
||||||
"github.com/containers/podman/v2/pkg/util"
|
"github.com/containers/podman/v2/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -15,7 +18,7 @@ import (
|
|||||||
// manifestPushOptsWrapper wraps entities.ManifestPushOptions and prevents leaking
|
// manifestPushOptsWrapper wraps entities.ManifestPushOptions and prevents leaking
|
||||||
// CLI-only fields into the API types.
|
// CLI-only fields into the API types.
|
||||||
type manifestPushOptsWrapper struct {
|
type manifestPushOptsWrapper struct {
|
||||||
entities.ManifestPushOptions
|
entities.ImagePushOptions
|
||||||
|
|
||||||
TLSVerifyCLI bool // CLI only
|
TLSVerifyCLI bool // CLI only
|
||||||
CredentialsCLI string
|
CredentialsCLI string
|
||||||
@ -41,8 +44,8 @@ func init() {
|
|||||||
Parent: manifestCmd,
|
Parent: manifestCmd,
|
||||||
})
|
})
|
||||||
flags := pushCmd.Flags()
|
flags := pushCmd.Flags()
|
||||||
flags.BoolVar(&manifestPushOpts.Purge, "purge", false, "remove the manifest list if push succeeds")
|
flags.BoolVar(&manifestPushOpts.Rm, "rm", false, "remove the manifest list if push succeeds")
|
||||||
flags.BoolVar(&manifestPushOpts.All, "all", false, "also push the images in the list")
|
flags.BoolVar(&manifestPushOpts.All, "all", true, "also push the images in the list")
|
||||||
|
|
||||||
authfileFlagName := "authfile"
|
authfileFlagName := "authfile"
|
||||||
flags.StringVar(&manifestPushOpts.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
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.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.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists")
|
||||||
|
flags.SetNormalizeFunc(utils.AliasFlags)
|
||||||
|
|
||||||
if registry.IsRemote() {
|
if registry.IsRemote() {
|
||||||
_ = flags.MarkHidden("cert-dir")
|
_ = flags.MarkHidden("cert-dir")
|
||||||
@ -107,8 +111,15 @@ func push(cmd *cobra.Command, args []string) error {
|
|||||||
if cmd.Flags().Changed("tls-verify") {
|
if cmd.Flags().Changed("tls-verify") {
|
||||||
manifestPushOpts.SkipTLSVerify = types.NewOptionalBool(!manifestPushOpts.TLSVerifyCLI)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
if manifestPushOpts.DigestFile != "" {
|
||||||
|
if err := ioutil.WriteFile(manifestPushOpts.DigestFile, []byte(digest), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
|||||||
name = "ns"
|
name = "ns"
|
||||||
case "storage":
|
case "storage":
|
||||||
name = "external"
|
name = "external"
|
||||||
|
case "purge":
|
||||||
|
name = "rm"
|
||||||
}
|
}
|
||||||
return pflag.NormalizedName(name)
|
return pflag.NormalizedName(name)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ The list image's ID and the digest of the image's manifest.
|
|||||||
#### **--all**
|
#### **--all**
|
||||||
|
|
||||||
Push the images mentioned in the manifest list or image index, in addition to
|
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*
|
#### **--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).
|
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**
|
#### **--quiet**, **-q**
|
||||||
|
|
||||||
When writing the manifest, suppress progress output
|
When writing the manifest, suppress progress output
|
||||||
|
|
||||||
|
#### **--rm**
|
||||||
|
|
||||||
|
Delete the manifest list or image index from local storage if pushing succeeds.
|
||||||
|
|
||||||
#### **--remove-signatures**
|
#### **--remove-signatures**
|
||||||
|
|
||||||
Don't copy signatures when pushing images.
|
Don't copy signatures when pushing images.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
% podman-push(1)
|
% podman-push(1)
|
||||||
|
|
||||||
## NAME
|
## 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
|
## SYNOPSIS
|
||||||
**podman push** [*options*] *image* [*destination*]
|
**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*]
|
**podman image push** [*options*] *image* [*destination*]
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
Pushes an image from local storage to a specified destination.
|
Pushes an image, manifest list or image index from local storage to a specified
|
||||||
Push is mainly used to push images to registries, however **podman push**
|
destination. Push is mainly used to push images to registries, however
|
||||||
can be used to save images to tarballs and directories using the following
|
**podman push** can be used to save images to tarballs and directories using the
|
||||||
transports: **dir:**, **docker-archive:**, **docker-daemon:** and **oci-archive:**.
|
following transports:
|
||||||
|
**dir:**, **docker-archive:**, **docker-daemon:** and **oci-archive:**.
|
||||||
|
|
||||||
## Image storage
|
## Image storage
|
||||||
Images are pushed from those stored in local 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-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-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-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-rename(1)](podman-rename.1.md) | Rename an existing container. |
|
||||||
| [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
|
| [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
|
||||||
| [podman-rm(1)](podman-rm.1.md) | Remove 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"
|
utils2 "github.com/containers/podman/v2/utils"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commit
|
// Commit
|
||||||
@ -410,6 +409,8 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
query := struct {
|
query := struct {
|
||||||
Destination string `schema:"destination"`
|
Destination string `schema:"destination"`
|
||||||
TLSVerify bool `schema:"tlsVerify"`
|
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
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newImage, err := runtime.ImageRuntime().NewFromLocal(source)
|
authconf, authfile, key, err := auth.GetCredentials(r)
|
||||||
if err != nil {
|
|
||||||
utils.ImageNotFound(w, source, errors.Wrapf(err, "failed to find image %s", source))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authConf, authfile, key, err := auth.GetCredentials(r)
|
|
||||||
if err != nil {
|
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()))
|
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
|
return
|
||||||
}
|
}
|
||||||
defer auth.RemoveAuthfile(authfile)
|
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 {
|
options := entities.ImagePushOptions{
|
||||||
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
|
Authfile: authfile,
|
||||||
dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Format: query.Format,
|
||||||
|
All: query.All,
|
||||||
}
|
}
|
||||||
if _, found := r.URL.Query()["tlsVerify"]; found {
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||||
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = newImage.PushImageToHeuristicDestination(
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||||
context.Background(),
|
if err := imageEngine.Push(context.Background(), source, destination, options); err != nil {
|
||||||
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))
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"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/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"
|
||||||
"github.com/containers/podman/v2/libpod/image"
|
"github.com/containers/podman/v2/libpod/image"
|
||||||
"github.com/containers/podman/v2/pkg/api/handlers"
|
"github.com/containers/podman/v2/pkg/api/handlers"
|
||||||
"github.com/containers/podman/v2/pkg/api/handlers/utils"
|
"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/containers/podman/v2/pkg/domain/infra/abi"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/opencontainers/go-digest"
|
"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})
|
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
|
||||||
}
|
}
|
||||||
func ManifestPush(w http.ResponseWriter, r *http.Request) {
|
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)
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
query := struct {
|
query := struct {
|
||||||
All bool `schema:"all"`
|
All bool `schema:"all"`
|
||||||
Destination string `schema:"destination"`
|
Destination string `schema:"destination"`
|
||||||
|
Format string `schema:"format"`
|
||||||
|
TLSVerify bool `schema:"tlsVerify"`
|
||||||
}{
|
}{
|
||||||
// Add defaults here once needed.
|
// 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()))
|
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := utils.GetName(r)
|
if _, err := utils.ParseDockerReference(query.Destination); err != nil {
|
||||||
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
|
||||||
if err != nil {
|
|
||||||
utils.ImageNotFound(w, name, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dest, err := alltransports.ParseImageName(query.Destination)
|
|
||||||
|
source := utils.GetName(r)
|
||||||
|
authConf, authfile, key, err := auth.GetCredentials(r)
|
||||||
if err != nil {
|
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
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
|
utils.WriteResponse(w, http.StatusOK, digest)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,8 @@ type ImportOptions struct {
|
|||||||
//go:generate go run ../generator/generator.go PushOptions
|
//go:generate go run ../generator/generator.go PushOptions
|
||||||
// PushOptions are optional options for importing images
|
// PushOptions are optional options for importing images
|
||||||
type PushOptions struct {
|
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
|
// Authfile is the path to the authentication file. Ignored for remote
|
||||||
// calls.
|
// calls.
|
||||||
Authfile *string
|
Authfile *string
|
||||||
|
@ -87,6 +87,22 @@ func (o *PushOptions) ToParams() (url.Values, error) {
|
|||||||
return params, nil
|
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
|
// WithAuthfile
|
||||||
func (o *PushOptions) WithAuthfile(value string) *PushOptions {
|
func (o *PushOptions) WithAuthfile(value string) *PushOptions {
|
||||||
v := &value
|
v := &value
|
||||||
|
@ -5,11 +5,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/image/v5/manifest"
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/podman/v2/pkg/api/handlers"
|
"github.com/containers/podman/v2/pkg/api/handlers"
|
||||||
"github.com/containers/podman/v2/pkg/bindings"
|
"github.com/containers/podman/v2/pkg/bindings"
|
||||||
|
"github.com/containers/podman/v2/pkg/bindings/images"
|
||||||
jsoniter "github.com/json-iterator/go"
|
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,
|
// 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
|
// the name will be used instead. If the optional all boolean is specified, all images specified
|
||||||
// in the list will be pushed as well.
|
// 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 (
|
var (
|
||||||
idr handlers.IDResponse
|
idr handlers.IDResponse
|
||||||
)
|
)
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = new(PushOptions)
|
options = new(images.PushOptions)
|
||||||
}
|
}
|
||||||
if len(destination) < 1 {
|
if len(destination) < 1 {
|
||||||
destination = name
|
destination = name
|
||||||
@ -130,8 +132,15 @@ func Push(ctx context.Context, name, destination string, options *PushOptions) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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("image", name)
|
||||||
params.Set("destination", destination)
|
params.Set("destination", destination)
|
||||||
|
params.Set("format", *options.Format)
|
||||||
_, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
|
_, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -28,9 +28,3 @@ type AddOptions struct {
|
|||||||
// RemoveOptions are optional options for removing manifests
|
// RemoveOptions are optional options for removing manifests
|
||||||
type RemoveOptions struct {
|
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)
|
ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error)
|
||||||
ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error)
|
ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error)
|
||||||
ManifestRemove(ctx context.Context, names []string) (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)
|
Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error)
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,8 @@ type ImagePullReport struct {
|
|||||||
|
|
||||||
// ImagePushOptions are the arguments for pushing images.
|
// ImagePushOptions are the arguments for pushing images.
|
||||||
type ImagePushOptions struct {
|
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
|
// Authfile is the path to the authentication file. Ignored for remote
|
||||||
// calls.
|
// calls.
|
||||||
Authfile string
|
Authfile string
|
||||||
@ -189,6 +191,8 @@ type ImagePushOptions struct {
|
|||||||
// Quiet can be specified to suppress pull progress when pulling. Ignored
|
// Quiet can be specified to suppress pull progress when pulling. Ignored
|
||||||
// for remote calls.
|
// for remote calls.
|
||||||
Quiet bool
|
Quiet bool
|
||||||
|
// Rm indicates whether to remove the manifest list if push succeeds
|
||||||
|
Rm bool
|
||||||
// RemoveSignatures, discard any pre-existing signatures in the image.
|
// RemoveSignatures, discard any pre-existing signatures in the image.
|
||||||
// Ignored for remote calls.
|
// Ignored for remote calls.
|
||||||
RemoveSignatures bool
|
RemoveSignatures bool
|
||||||
|
@ -33,11 +33,3 @@ type ManifestAnnotateOptions struct {
|
|||||||
OSVersion string `json:"os_version" schema:"os_version"`
|
OSVersion string `json:"os_version" schema:"os_version"`
|
||||||
Variant string `json:"variant" schema:"variant"`
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newImage.PushImageToHeuristicDestination(
|
err = newImage.PushImageToHeuristicDestination(
|
||||||
ctx,
|
ctx,
|
||||||
destination,
|
destination,
|
||||||
manifestType,
|
manifestType,
|
||||||
@ -379,39 +379,15 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
|
|||||||
signOptions,
|
signOptions,
|
||||||
&dockerRegistryOptions,
|
&dockerRegistryOptions,
|
||||||
nil)
|
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 {
|
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
|
||||||
newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
|
newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -24,9 +23,8 @@ import (
|
|||||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ManifestCreate implements logic for creating manifest lists via ImageEngine
|
// 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
|
// 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)
|
listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name)
|
||||||
if err != nil {
|
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)
|
dest, err := alltransports.ParseImageName(destination)
|
||||||
if err != nil {
|
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
|
var manifestType string
|
||||||
@ -261,7 +265,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin
|
|||||||
case "v2s2", "docker":
|
case "v2s2", "docker":
|
||||||
manifestType = manifest.DockerV2Schema2MediaType
|
manifestType = manifest.DockerV2Schema2MediaType
|
||||||
default:
|
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
|
options.ReportWriter = os.Stderr
|
||||||
}
|
}
|
||||||
manDigest, err := listImage.PushManifest(dest, options)
|
manDigest, err := listImage.PushManifest(dest, options)
|
||||||
if err == nil && opts.Purge {
|
if err == nil && opts.Rm {
|
||||||
_, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true)
|
_, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true)
|
||||||
}
|
}
|
||||||
if opts.DigestFile != "" {
|
return manDigest.String(), err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"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/bindings/manifests"
|
||||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
"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
|
// 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) {
|
||||||
options := new(manifests.PushOptions).WithAll(opts.All)
|
options := new(images.PushOptions)
|
||||||
_, err := manifests.Push(ir.ClientCtx, name, destination, options)
|
options.WithUsername(opts.Username).WithSignaturePolicy(opts.SignaturePolicy).WithQuiet(opts.Quiet)
|
||||||
return err
|
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() {
|
It("podman manifest push", func() {
|
||||||
|
SkipIfRemote("manifest push to dir not supported in remote mode")
|
||||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
@ -199,8 +200,38 @@ var _ = Describe("Podman manifest", func() {
|
|||||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
|
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman manifest push purge", func() {
|
It("podman push --all", func() {
|
||||||
SkipIfRemote("remote does not support --purge")
|
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 := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Reference in New Issue
Block a user