diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2021-09-21 16:40:36 +0200 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2021-09-22 12:38:07 +0200 |
commit | 49c5688a30ac29b258196d0be73cc7f09a9705cb (patch) | |
tree | 7625a2f58f524a3373d30438d0bed57f6b919cfd | |
parent | e9214ce81e45d227dc5017da0c252fbd601605a8 (diff) | |
download | podman-49c5688a30ac29b258196d0be73cc7f09a9705cb.tar.gz podman-49c5688a30ac29b258196d0be73cc7f09a9705cb.tar.bz2 podman-49c5688a30ac29b258196d0be73cc7f09a9705cb.zip |
podman save: add `--uncompressed`
Add an option to `podman save` to allow uncompressed layers when
copying OCI images. Do the neccessary plumbing for the remote client,
add tests and vendor in the latest commit from c/common to fetch
the neccessary changes in libimage.
Closes: #11613
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
-rw-r--r-- | cmd/podman/images/save.go | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-save.1.md | 4 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 18 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 4 | ||||
-rw-r--r-- | pkg/bindings/images/types.go | 2 | ||||
-rw-r--r-- | pkg/bindings/images/types_export_options.go | 15 | ||||
-rw-r--r-- | pkg/domain/entities/images.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/images.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 1 | ||||
-rw-r--r-- | test/system/120-load.bats | 12 | ||||
-rw-r--r-- | vendor/github.com/containers/common/libimage/copier.go | 18 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/config/default.go | 1 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/secrets/secrets.go | 6 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/secrets/secretsdb.go | 12 | ||||
-rw-r--r-- | vendor/modules.txt | 2 |
17 files changed, 84 insertions, 22 deletions
diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 19dadb2ad..4f45cb912 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -84,6 +84,8 @@ func saveFlags(cmd *cobra.Command) { flags.BoolVar(&saveOpts.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") + flags.BoolVar(&saveOpts.OciAcceptUncompressedLayers, "uncompressed", false, "Accept uncompressed layers when copying OCI images") + formatFlagName := "format" flags.StringVar(&saveOpts.Format, formatFlagName, define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteImageSaveFormat) diff --git a/docs/source/markdown/podman-save.1.md b/docs/source/markdown/podman-save.1.md index 1f1f60b22..842bc8b41 100644 --- a/docs/source/markdown/podman-save.1.md +++ b/docs/source/markdown/podman-save.1.md @@ -29,6 +29,10 @@ Note: `:` is a restricted character and cannot be part of the file name. Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type, compressed or uncompressed, as source) Note: This flag can only be set when using the **dir** transport i.e --format=oci-dir or --format=docker-dir +#### **--uncompressed** + +Accept uncompressed layers when copying OCI images. + #### **--output**, **-o**=*file* Write to a file, default is STDOUT @@ -12,7 +12,7 @@ require ( github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.9.1 github.com/containers/buildah v1.23.0 - github.com/containers/common v0.44.1-0.20210920093543-bf187ada7d0e + github.com/containers/common v0.44.1-0.20210921143342-f2f10e650c73 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.16.0 github.com/containers/ocicrypt v1.1.2 @@ -246,8 +246,8 @@ github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRD github.com/containers/buildah v1.23.0 h1:qGIeSNOczUHzvnaaOS29HSMiYAjw6JgIXYksAyvqnLs= github.com/containers/buildah v1.23.0/go.mod h1:K0iMKgy/MffkkgELBXhSXwTy2HTT6hM0X8qruDR1FwU= github.com/containers/common v0.44.0/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo= -github.com/containers/common v0.44.1-0.20210920093543-bf187ada7d0e h1:p21+CJSeryr0Vb3dottjXRNYTaRND1QSPm36NogQ7cQ= -github.com/containers/common v0.44.1-0.20210920093543-bf187ada7d0e/go.mod h1:zxv7KjdYddSGoWuLUVp6eSb++Ow1zmSMB2jwxuNB4cU= +github.com/containers/common v0.44.1-0.20210921143342-f2f10e650c73 h1:+qKOyTHbuFo3GPsrUksphfHxYMIJQmPgwpDdQnARGAI= +github.com/containers/common v0.44.1-0.20210921143342-f2f10e650c73/go.mod h1:zxv7KjdYddSGoWuLUVp6eSb++Ow1zmSMB2jwxuNB4cU= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.16.0 h1:WQcNSzb7+ngS2cfynx0vUwhk+scpgiKlldVcsF8GPbI= diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index b4f08a746..51157d204 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -289,9 +289,10 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - Compress bool `schema:"compress"` - Format string `schema:"format"` - References []string `schema:"references"` + Compress bool `schema:"compress"` + Format string `schema:"format"` + OciAcceptUncompressedLayers bool `schema:"ociAcceptUncompressedLayers"` + References []string `schema:"references"` }{ Format: define.OCIArchive, } @@ -353,11 +354,12 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { // Use the ABI image engine to share as much code as possible. opts := entities.ImageSaveOptions{ - Compress: query.Compress, - Format: query.Format, - MultiImageArchive: len(query.References) > 1, - Output: output, - RemoveSignatures: true, + Compress: query.Compress, + Format: query.Format, + MultiImageArchive: len(query.References) > 1, + OciAcceptUncompressedLayers: query.OciAcceptUncompressedLayers, + Output: output, + RemoveSignatures: true, } imageEngine := abi.ImageEngine{Libpod: runtime} diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 95981226c..dce609a4e 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -1150,6 +1150,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: compress // type: boolean // description: use compression on image + // - in: query + // name: ociAcceptUncompressedLayers + // type: boolean + // description: accept uncompressed layers when copying OCI images // produces: // - application/json // responses: diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 801f5ed96..6ff9f18ec 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -65,6 +65,8 @@ type ExportOptions struct { Compress *bool // Format of the output Format *string + // Accept uncompressed layers when copying OCI images. + OciAcceptUncompressedLayers *bool } //go:generate go run ../generator/generator.go PruneOptions diff --git a/pkg/bindings/images/types_export_options.go b/pkg/bindings/images/types_export_options.go index 6229e435c..649b6814e 100644 --- a/pkg/bindings/images/types_export_options.go +++ b/pkg/bindings/images/types_export_options.go @@ -46,3 +46,18 @@ func (o *ExportOptions) GetFormat() string { } return *o.Format } + +// WithOciAcceptUncompressedLayers set field OciAcceptUncompressedLayers to given value +func (o *ExportOptions) WithOciAcceptUncompressedLayers(value bool) *ExportOptions { + o.OciAcceptUncompressedLayers = &value + return o +} + +// GetOciAcceptUncompressedLayers returns value of field OciAcceptUncompressedLayers +func (o *ExportOptions) GetOciAcceptUncompressedLayers() bool { + if o.OciAcceptUncompressedLayers == nil { + var z bool + return z + } + return *o.OciAcceptUncompressedLayers +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index c575212b1..edd23e662 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -301,6 +301,8 @@ type ImageSaveOptions struct { // than one image. Additional tags will be interpreted as references // to images which are added to the archive. MultiImageArchive bool + // Accept uncompressed layers when copying OCI images. + OciAcceptUncompressedLayers bool // Output - write image to the specified path. Output string // Do not save the signature from the source image diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index a88d38a10..f8ee0304d 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -367,6 +367,7 @@ func (ir *ImageEngine) Load(ctx context.Context, options entities.ImageLoadOptio func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { saveOptions := &libimage.SaveOptions{} saveOptions.DirForceCompress = options.Compress + saveOptions.OciAcceptUncompressedLayers = options.OciAcceptUncompressedLayers saveOptions.RemoveSignatures = options.RemoveSignatures if !options.Quiet { diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 9a746d68c..282770613 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -256,6 +256,7 @@ func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, err error ) options := new(images.ExportOptions).WithFormat(opts.Format).WithCompress(opts.Compress) + options = options.WithOciAcceptUncompressedLayers(opts.OciAcceptUncompressedLayers) switch opts.Format { case "oci-dir", "docker-dir": diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 97ea0f528..f2f9bf4d4 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -183,4 +183,16 @@ verify_iid_and_name() { run_podman rmi -f $img1 $img2 } +@test "podman save --oci-accept-uncompressed-layers" { + archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar + untar=$PODMAN_TMPDIR/myuntar-$(random_string 8) + mkdir -p $untar + + # Create a tarball, unpack it and make sure the layers are uncompressed. + run_podman save -o $archive --format oci-archive --uncompressed $IMAGE + run tar -C $untar -xvf $archive + run file $untar/blobs/sha256/* + is "$output" ".*POSIX tar archive" "layers are uncompressed" +} + # vim: filetype=sh diff --git a/vendor/github.com/containers/common/libimage/copier.go b/vendor/github.com/containers/common/libimage/copier.go index a44f098ad..42d3690b9 100644 --- a/vendor/github.com/containers/common/libimage/copier.go +++ b/vendor/github.com/containers/common/libimage/copier.go @@ -12,6 +12,7 @@ import ( "github.com/containers/common/pkg/retry" "github.com/containers/image/v5/copy" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/signature" storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" @@ -40,6 +41,10 @@ type CopyOptions struct { // Allows for customizing the destination reference lookup. This can // be used to use custom blob caches. DestinationLookupReferenceFunc LookupReferenceFunc + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat *compression.Algorithm + // CompressionLevel specifies what compression level is used + CompressionLevel *int // containers-auth.json(5) file to use when authenticating against // container registries. @@ -65,6 +70,8 @@ type CopyOptions struct { // types. Short forms (e.g., oci, v2s2) used by some tools are not // supported. ManifestMIMEType string + // Accept uncompressed layers when copying OCI images. + OciAcceptUncompressedLayers bool // If OciEncryptConfig is non-nil, it indicates that an image should be // encrypted. The encryption options is derived from the construction // of EncryptConfig object. Note: During initial encryption process of @@ -242,6 +249,17 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) { c.systemContext.DockerCertPath = options.CertDirPath } + if options.CompressionFormat != nil { + c.systemContext.CompressionFormat = options.CompressionFormat + } + + if options.CompressionLevel != nil { + c.systemContext.CompressionLevel = options.CompressionLevel + } + + // NOTE: for the sake of consistency it's called Oci* in the CopyOptions. + c.systemContext.OCIAcceptUncompressedLayers = options.OciAcceptUncompressedLayers + policy, err := signature.DefaultPolicy(c.systemContext) if err != nil { return nil, err diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index a3fdc9529..34d17d72c 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -198,7 +198,6 @@ func DefaultConfig() (*Config, error) { TZ: "", Umask: "0022", UTSNS: "private", - UserNS: "host", UserNSSize: DefaultUserNSSize, }, Network: NetworkConfig{ diff --git a/vendor/github.com/containers/common/pkg/secrets/secrets.go b/vendor/github.com/containers/common/pkg/secrets/secrets.go index 2e7802369..aea983cb1 100644 --- a/vendor/github.com/containers/common/pkg/secrets/secrets.go +++ b/vendor/github.com/containers/common/pkg/secrets/secrets.go @@ -24,8 +24,8 @@ const secretIDLength = 25 // errInvalidPath indicates that the secrets path is invalid var errInvalidPath = errors.New("invalid secrets path") -// errNoSuchSecret indicates that the secret does not exist -var errNoSuchSecret = errors.New("no such secret") +// ErrNoSuchSecret indicates that the secret does not exist +var ErrNoSuchSecret = errors.New("no such secret") // errSecretNameInUse indicates that the secret name is already in use var errSecretNameInUse = errors.New("secret name in use") @@ -152,7 +152,7 @@ func (s *SecretsManager) Store(name string, data []byte, driverType string, driv newID = newID[0:secretIDLength] _, err := s.lookupSecret(newID) if err != nil { - if errors.Cause(err) == errNoSuchSecret { + if errors.Cause(err) == ErrNoSuchSecret { secr.ID = newID break } else { diff --git a/vendor/github.com/containers/common/pkg/secrets/secretsdb.go b/vendor/github.com/containers/common/pkg/secrets/secretsdb.go index 1395d103c..0c4929995 100644 --- a/vendor/github.com/containers/common/pkg/secrets/secretsdb.go +++ b/vendor/github.com/containers/common/pkg/secrets/secretsdb.go @@ -71,14 +71,14 @@ func (s *SecretsManager) getNameAndID(nameOrID string) (name, id string, err err name, id, err = s.getExactNameAndID(nameOrID) if err == nil { return name, id, nil - } else if errors.Cause(err) != errNoSuchSecret { + } else if errors.Cause(err) != ErrNoSuchSecret { return "", "", err } // ID prefix may have been given, iterate through all IDs. // ID and partial ID has a max length of 25, so we return if its greater than that. if len(nameOrID) > secretIDLength { - return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) + return "", "", errors.Wrapf(ErrNoSuchSecret, "no secret with name or id %q", nameOrID) } exists := false var foundID, foundName string @@ -96,7 +96,7 @@ func (s *SecretsManager) getNameAndID(nameOrID string) (name, id string, err err if exists { return foundName, foundID, nil } - return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) + return "", "", errors.Wrapf(ErrNoSuchSecret, "no secret with name or id %q", nameOrID) } // getExactNameAndID takes a secret's name or ID and returns both its name and full ID. @@ -115,7 +115,7 @@ func (s *SecretsManager) getExactNameAndID(nameOrID string) (name, id string, er return name, id, nil } - return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) + return "", "", errors.Wrapf(ErrNoSuchSecret, "no secret with name or id %q", nameOrID) } // exactSecretExists checks if the secret exists, given a name or ID @@ -123,7 +123,7 @@ func (s *SecretsManager) getExactNameAndID(nameOrID string) (name, id string, er func (s *SecretsManager) exactSecretExists(nameOrID string) (bool, error) { _, _, err := s.getExactNameAndID(nameOrID) if err != nil { - if errors.Cause(err) == errNoSuchSecret { + if errors.Cause(err) == ErrNoSuchSecret { return false, nil } return false, err @@ -158,7 +158,7 @@ func (s *SecretsManager) lookupSecret(nameOrID string) (*Secret, error) { return &secret, nil } - return nil, errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) + return nil, errors.Wrapf(ErrNoSuchSecret, "no secret with name or id %q", nameOrID) } // Store creates a new secret in the secrets database. diff --git a/vendor/modules.txt b/vendor/modules.txt index 5e82b9977..f587d3aea 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -94,7 +94,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.44.1-0.20210920093543-bf187ada7d0e +# github.com/containers/common v0.44.1-0.20210921143342-f2f10e650c73 github.com/containers/common/libimage github.com/containers/common/libimage/manifests github.com/containers/common/pkg/apparmor |