diff options
-rw-r--r-- | cmd/podman/manifest/push.go | 4 | ||||
-rw-r--r-- | docs/source/markdown/podman-manifest-push.1.md | 4 | ||||
-rw-r--r-- | docs/source/markdown/podman-push.1.md | 2 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images_push.go | 28 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/manifests.go | 18 | ||||
-rw-r--r-- | pkg/bindings/images/types.go | 2 | ||||
-rw-r--r-- | pkg/bindings/images/types_push_options.go | 15 | ||||
-rw-r--r-- | pkg/domain/infra/abi/manifest.go | 17 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/manifest.go | 3 | ||||
-rw-r--r-- | pkg/systemd/generate/common.go | 24 | ||||
-rw-r--r-- | pkg/systemd/generate/containers.go | 9 | ||||
-rw-r--r-- | pkg/systemd/generate/containers_test.go | 93 | ||||
-rw-r--r-- | test/e2e/manifest_test.go | 42 |
14 files changed, 234 insertions, 29 deletions
diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index 6afd8ea4c..756ed2a74 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -85,6 +85,10 @@ func init() { flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") flags.SetNormalizeFunc(utils.AliasFlags) + compressionFormat := "compression-format" + flags.StringVar(&manifestPushOpts.CompressionFormat, compressionFormat, "", "compression format to use") + _ = pushCmd.RegisterFlagCompletionFunc(compressionFormat, common.AutocompleteCompressionFormat) + if registry.IsRemote() { _ = flags.MarkHidden("cert-dir") _ = flags.MarkHidden(signByFlagName) diff --git a/docs/source/markdown/podman-manifest-push.1.md b/docs/source/markdown/podman-manifest-push.1.md index 66cdb8324..cfe2b9230 100644 --- a/docs/source/markdown/podman-manifest-push.1.md +++ b/docs/source/markdown/podman-manifest-push.1.md @@ -32,6 +32,10 @@ environment variable. `export REGISTRY_AUTH_FILE=path` Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry. (Default: /etc/containers/certs.d) Please refer to containers-certs.d(5) for details. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +#### **--compression-format**=**gzip** | *zstd* | *zstd:chunked* + +Specifies the compression format to use. Supported values are: `gzip`, `zstd` and `zstd:chunked`. The default is `gzip` unless overridden in the containers.conf file. + #### **--creds**=*creds* The [username[:password]] to use to authenticate with the registry if required. diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md index a69bdce0a..d674975b0 100644 --- a/docs/source/markdown/podman-push.1.md +++ b/docs/source/markdown/podman-push.1.md @@ -67,7 +67,7 @@ Note: This flag can only be set when using the **dir** transport #### **--compression-format**=**gzip** | *zstd* | *zstd:chunked* -Specifies the compression format to use. Supported values are: `gzip`, `zstd` and `zstd:chunked`. The default is `gzip`. +Specifies the compression format to use. Supported values are: `gzip`, `zstd` and `zstd:chunked`. The default is `gzip` unless overridden in the containers.conf file. #### **--creds**=*[username[:password]]* diff --git a/pkg/api/handlers/libpod/images_push.go b/pkg/api/handlers/libpod/images_push.go index 9ee651f5b..e931fd2f9 100644 --- a/pkg/api/handlers/libpod/images_push.go +++ b/pkg/api/handlers/libpod/images_push.go @@ -25,12 +25,13 @@ func PushImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) query := struct { - All bool `schema:"all"` - Destination string `schema:"destination"` - Format string `schema:"format"` - RemoveSignatures bool `schema:"removeSignatures"` - TLSVerify bool `schema:"tlsVerify"` - Quiet bool `schema:"quiet"` + All bool `schema:"all"` + CompressionFormat string `schema:"compressionFormat"` + Destination string `schema:"destination"` + Format string `schema:"format"` + RemoveSignatures bool `schema:"removeSignatures"` + TLSVerify bool `schema:"tlsVerify"` + Quiet bool `schema:"quiet"` }{ TLSVerify: true, // #14971: older versions did not sent *any* data, so we need @@ -71,13 +72,14 @@ func PushImage(w http.ResponseWriter, r *http.Request) { password = authconf.Password } options := entities.ImagePushOptions{ - All: query.All, - Authfile: authfile, - Format: query.Format, - Password: password, - Quiet: true, - RemoveSignatures: query.RemoveSignatures, - Username: username, + All: query.All, + Authfile: authfile, + CompressionFormat: query.CompressionFormat, + Format: query.Format, + Password: password, + Quiet: true, + RemoveSignatures: query.RemoveSignatures, + Username: username, } if _, found := r.URL.Query()["tlsVerify"]; found { diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 43c7139d3..2d6223e4e 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -306,8 +306,11 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - All bool `schema:"all"` - TLSVerify bool `schema:"tlsVerify"` + All bool `schema:"all"` + CompressionFormat string `schema:"compressionFormat"` + Format string `schema:"format"` + RemoveSignatures bool `schema:"removeSignatures"` + TLSVerify bool `schema:"tlsVerify"` }{ // Add defaults here once needed. TLSVerify: true, @@ -336,10 +339,13 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { password = authconf.Password } options := entities.ImagePushOptions{ - Authfile: authfile, - Username: username, - Password: password, - All: query.All, + All: query.All, + Authfile: authfile, + CompressionFormat: query.CompressionFormat, + Format: query.Format, + Password: password, + RemoveSignatures: query.RemoveSignatures, + Username: username, } if sys := runtime.SystemContext(); sys != nil { options.CertDir = sys.DockerCertPath diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 0e672cdea..0664afc1b 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -123,6 +123,8 @@ type PushOptions struct { Authfile *string // Compress tarball image layers when pushing to a directory using the 'dir' transport. Compress *bool + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat *string // Manifest type of the pushed image Format *string // Password for authenticating against the registry. diff --git a/pkg/bindings/images/types_push_options.go b/pkg/bindings/images/types_push_options.go index 63a19fb81..1ae031824 100644 --- a/pkg/bindings/images/types_push_options.go +++ b/pkg/bindings/images/types_push_options.go @@ -62,6 +62,21 @@ func (o *PushOptions) GetCompress() bool { return *o.Compress } +// WithCompressionFormat set field CompressionFormat to given value +func (o *PushOptions) WithCompressionFormat(value string) *PushOptions { + o.CompressionFormat = &value + return o +} + +// GetCompressionFormat returns value of field CompressionFormat +func (o *PushOptions) GetCompressionFormat() string { + if o.CompressionFormat == nil { + var z string + return z + } + return *o.CompressionFormat +} + // WithFormat set field Format to given value func (o *PushOptions) WithFormat(value string) *PushOptions { o.Format = &value diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 6606b2cd0..4b10d9b18 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -13,6 +13,7 @@ import ( "github.com/containers/common/libimage" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -321,6 +322,22 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin pushOptions.SignSigstorePrivateKeyPassphrase = opts.SignSigstorePrivateKeyPassphrase pushOptions.InsecureSkipTLSVerify = opts.SkipTLSVerify + compressionFormat := opts.CompressionFormat + if compressionFormat == "" { + config, err := ir.Libpod.GetConfigNoCopy() + if err != nil { + return "", err + } + compressionFormat = config.Engine.CompressionFormat + } + if compressionFormat != "" { + algo, err := compression.AlgorithmByName(compressionFormat) + if err != nil { + return "", err + } + pushOptions.CompressionFormat = &algo + } + if opts.All { pushOptions.ImageListSelection = cp.CopyAllImages } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 9ad408850..4f79325fd 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -240,7 +240,7 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, opts entities.ImagePushOptions) error { options := new(images.PushOptions) - options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures).WithQuiet(opts.Quiet) + options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures).WithQuiet(opts.Quiet).WithCompressionFormat(opts.CompressionFormat) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index 4a3148fac..00ecb3b59 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -99,8 +99,7 @@ func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (*entitie // ManifestPush pushes a manifest list or image index to the destination func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) { options := new(images.PushOptions) - options.WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithRemoveSignatures(opts.RemoveSignatures) - options.WithAll(opts.All) + options.WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithRemoveSignatures(opts.RemoveSignatures).WithAll(opts.All).WithFormat(opts.Format).WithCompressionFormat(opts.CompressionFormat) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 60b0c4b52..b0a441d54 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -42,7 +42,7 @@ RequiresMountsFor={{{{.RunRoot}}}} // filterPodFlags removes --pod, --pod-id-file and --infra-conmon-pidfile from the specified command. // argCount is the number of last arguments which should not be filtered, e.g. the container entrypoint. func filterPodFlags(command []string, argCount int) []string { - processed := []string{} + processed := make([]string, 0, len(command)) for i := 0; i < len(command)-argCount; i++ { s := command[i] if s == "--pod" || s == "--pod-id-file" || s == "--infra-conmon-pidfile" { @@ -63,7 +63,7 @@ func filterPodFlags(command []string, argCount int) []string { // filterCommonContainerFlags removes --sdnotify, --rm and --cgroups from the specified command. // argCount is the number of last arguments which should not be filtered, e.g. the container entrypoint. func filterCommonContainerFlags(command []string, argCount int) []string { - processed := []string{} + processed := make([]string, 0, len(command)) for i := 0; i < len(command)-argCount; i++ { s := command[i] @@ -71,7 +71,7 @@ func filterCommonContainerFlags(command []string, argCount int) []string { case s == "--rm": // Boolean flags support --flag and --flag={true,false}. continue - case s == "--sdnotify", s == "--cgroups", s == "--cidfile", s == "--restart": + case s == "--cgroups", s == "--cidfile", s == "--restart": i++ continue case strings.HasPrefix(s, "--rm="), @@ -111,6 +111,24 @@ func escapeSystemdArg(arg string) string { return arg } +func removeSdNotifyArg(args []string, argCount int) []string { + processed := make([]string, 0, len(args)) + for i := 0; i < len(args)-argCount; i++ { + s := args[i] + + switch { + case s == "--sdnotify": + i++ + continue + case strings.HasPrefix(s, "--sdnotify="): + continue + } + processed = append(processed, s) + } + processed = append(processed, args[len(args)-argCount:]...) + return processed +} + func removeDetachArg(args []string, argCount int) []string { // "--detach=false" could also be in the container entrypoint // split them off so we do not remove it there diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 6596ef73b..66905202d 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -403,8 +403,13 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst // Default to --sdnotify=conmon unless already set by the // container. - hasSdnotifyParam := fs.Lookup("sdnotify").Changed - if !hasSdnotifyParam { + sdnotifyFlag := fs.Lookup("sdnotify") + if !sdnotifyFlag.Changed { + startCommand = append(startCommand, "--sdnotify=conmon") + } else if sdnotifyFlag.Value.String() == libpodDefine.SdNotifyModeIgnore { + // If ignore is set force conmon otherwise the unit with Type=notify will fail. + logrus.Infof("Forcing --sdnotify=conmon for container %s", info.ContainerNameOrID) + remainingCmd = removeSdNotifyArg(remainingCmd, fs.NArg()) startCommand = append(startCommand, "--sdnotify=conmon") } diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 640aa298e..9a9e03a58 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -2,6 +2,7 @@ package generate import ( "fmt" + "strings" "testing" "github.com/containers/podman/v4/pkg/domain/entities" @@ -317,6 +318,39 @@ NotifyAccess=all WantedBy=default.target ` + goodWithNameAndSdnotifyIgnore := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman container run \ + --cidfile=%t/%n.ctr-id \ + --cgroups=no-conmon \ + --rm \ + --sdnotify=conmon \ + -d \ + --replace \ + --name jadda-jadda \ + --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id +Type=notify +NotifyAccess=all + +[Install] +WantedBy=default.target +` + goodWithExplicitShortDetachParam := `# jadda-jadda.service # autogenerated by Podman CI @@ -992,7 +1026,7 @@ WantedBy=default.target false, false, }, - {"good with name and sdnotify", + {"good with name and --sdnotify=container", containerInfo{ Executable: "/usr/bin/podman", ServiceName: "jadda-jadda", @@ -1011,6 +1045,63 @@ WantedBy=default.target false, false, }, + {"good with name and --sdnotify container", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "container", "run", "--sdnotify", "container", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN", "foo=arg \"with \" space"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + strings.ReplaceAll(goodWithNameAndSdnotify, "--sdnotify=container", "--sdnotify container"), + true, + false, + false, + false, + }, + {"good with name and --sdnotify=ignore", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "container", "run", "--sdnotify=ignore", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN", "foo=arg \"with \" space"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + goodWithNameAndSdnotifyIgnore, + true, + false, + false, + false, + }, + {"good with name and --sdnotify ignore", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "container", "run", "--sdnotify", "ignore", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN", "foo=arg \"with \" space"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + goodWithNameAndSdnotifyIgnore, + true, + false, + false, + false, + }, {"good with explicit short detach param", containerInfo{ Executable: "/usr/bin/podman", diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go index a7fcd1559..893210a1f 100644 --- a/test/e2e/manifest_test.go +++ b/test/e2e/manifest_test.go @@ -1,12 +1,14 @@ package integration import ( + "io/ioutil" "os" "path/filepath" "strings" podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go" . "github.com/containers/podman/v4/test/utils" + "github.com/containers/storage/pkg/archive" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -292,6 +294,46 @@ var _ = Describe("Podman manifest", func() { )) }) + It("push with compression-format", func() { + SkipIfRemote("manifest push to dir not supported in remote mode") + session := podmanTest.Podman([]string{"manifest", "create", "foo"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(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", "--compression-format=zstd", "foo", "oci:" + dest}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + foundZstdFile := false + + blobsDir := filepath.Join(dest, "blobs", "sha256") + + blobs, err := ioutil.ReadDir(blobsDir) + Expect(err).To(BeNil()) + + for _, f := range blobs { + blobPath := filepath.Join(blobsDir, f.Name()) + + sourceFile, err := ioutil.ReadFile(blobPath) + Expect(err).To(BeNil()) + + compressionType := archive.DetectCompression(sourceFile) + if compressionType == archive.Zstd { + foundZstdFile = true + break + } + } + Expect(foundZstdFile).To(BeTrue()) + }) + It("authenticated push", func() { registryOptions := &podmanRegistry.Options{ Image: "docker-archive:" + imageTarPath(REGISTRY_IMAGE), |