From f21dacc4fb13d9e401ba122dcd3a7587c179cd45 Mon Sep 17 00:00:00 2001 From: Hayato Kihara Date: Tue, 24 Jun 2025 22:06:26 +0900 Subject: [PATCH] cmd/podman: add --latest option to update #26380 Allow users to target the most recently created container with `podman update --latest` (short `-l`). The same option already exists on many other commands, so this brings update in line with the rest of the CLI and saves users from typing or looking up the newest container. Fixes: #26380 Signed-off-by: Hayato Kihara --- cmd/podman/containers/update.go | 33 +++++++++++++------- docs/source/markdown/options/latest.md | 2 +- docs/source/markdown/podman-update.1.md.in | 8 +++++ pkg/domain/entities/types/containers.go | 1 + pkg/domain/infra/abi/containers.go | 2 +- test/e2e/update_test.go | 35 ++++++++++++++++++++++ 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/cmd/podman/containers/update.go b/cmd/podman/containers/update.go index 6f6ed362c4..1385f241ba 100644 --- a/cmd/podman/containers/update.go +++ b/cmd/podman/containers/update.go @@ -7,6 +7,7 @@ import ( "github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/cmd/podman/validate" "github.com/containers/podman/v5/libpod/define" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/specgen" @@ -24,7 +25,7 @@ var ( Short: "Update an existing container", Long: updateDescription, RunE: update, - Args: cobra.ExactArgs(1), + Args: validate.IDOrLatestArgs, ValidArgsFunction: common.AutocompleteContainers, Example: `podman update --cpus=5 foobar_container`, } @@ -39,13 +40,17 @@ var ( Example: `podman container update --cpus=5 foobar_container`, } ) -var ( - updateOpts entities.ContainerCreateOptions -) + +type ContainerUpdateOptions struct { + entities.ContainerCreateOptions + Latest bool +} + +var updateOptions ContainerUpdateOptions func updateFlags(cmd *cobra.Command) { - common.DefineCreateDefaults(&updateOpts) - common.DefineCreateFlags(cmd, &updateOpts, entities.UpdateMode) + common.DefineCreateDefaults(&updateOptions.ContainerCreateOptions) + common.DefineCreateFlags(cmd, &updateOptions.ContainerCreateOptions, entities.UpdateMode) } func init() { @@ -53,12 +58,14 @@ func init() { Command: updateCommand, }) updateFlags(updateCommand) + validate.AddLatestFlag(updateCommand, &updateOptions.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Command: containerUpdateCommand, Parent: containerCmd, }) updateFlags(containerUpdateCommand) + validate.AddLatestFlag(containerUpdateCommand, &updateOptions.Latest) } func GetChangedHealthCheckConfiguration(cmd *cobra.Command, vals *entities.ContainerCreateOptions) define.UpdateHealthCheckConfig { @@ -129,12 +136,12 @@ func update(cmd *cobra.Command, args []string) error { s := &specgen.SpecGenerator{} s.ResourceLimits = &specs.LinuxResources{} - err = createOrUpdateFlags(cmd, &updateOpts) + err = createOrUpdateFlags(cmd, &updateOptions.ContainerCreateOptions) if err != nil { return err } - s.ResourceLimits, err = specgenutil.GetResources(s, &updateOpts) + s.ResourceLimits, err = specgenutil.GetResources(s, &updateOptions.ContainerCreateOptions) if err != nil { return err } @@ -143,17 +150,21 @@ func update(cmd *cobra.Command, args []string) error { s.ResourceLimits = &specs.LinuxResources{} } - healthCheckConfig := GetChangedHealthCheckConfiguration(cmd, &updateOpts) + healthCheckConfig := GetChangedHealthCheckConfiguration(cmd, &updateOptions.ContainerCreateOptions) opts := &entities.ContainerUpdateOptions{ - NameOrID: strings.TrimPrefix(args[0], "/"), Resources: s.ResourceLimits, ChangedHealthCheckConfiguration: &healthCheckConfig, DevicesLimits: GetChangedDeviceLimits(s), + Latest: updateOptions.Latest, + } + + if !updateOptions.Latest { + opts.NameOrID = strings.TrimPrefix(args[0], "/") } if cmd.Flags().Changed("restart") { - policy, retries, err := util.ParseRestartPolicy(updateOpts.Restart) + policy, retries, err := util.ParseRestartPolicy(updateOptions.Restart) if err != nil { return err } diff --git a/docs/source/markdown/options/latest.md b/docs/source/markdown/options/latest.md index 34ba9072c0..3780ec5729 100644 --- a/docs/source/markdown/options/latest.md +++ b/docs/source/markdown/options/latest.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman attach, container diff, container inspect, diff, exec, init, inspect, kill, logs, mount, network reload, pause, pod inspect, pod kill, pod logs, pod rm, pod start, pod stats, pod stop, pod top, port, restart, rm, start, stats, stop, top, unmount, unpause, wait +####> podman attach, container diff, container inspect, diff, exec, init, inspect, kill, logs, mount, network reload, pause, pod inspect, pod kill, pod logs, pod rm, pod start, pod stats, pod stop, pod top, port, restart, rm, start, stats, stop, top, unmount, unpause, update, wait ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--latest**, **-l** diff --git a/docs/source/markdown/podman-update.1.md.in b/docs/source/markdown/podman-update.1.md.in index 4bfced600f..10ccfd146d 100644 --- a/docs/source/markdown/podman-update.1.md.in +++ b/docs/source/markdown/podman-update.1.md.in @@ -78,6 +78,8 @@ Changing this setting resets the timer, depending on the state of the container. @@option health-timeout +@@option latest + @@option memory @@option memory-reservation @@ -102,6 +104,11 @@ Update a container with a new cpu quota and period: podman update --cpus=0.5 ctrID ``` +Update the latest container with a new cpu value: +``` +podman update --latest --cpus=1 +``` + Update a container with multiple options at ones: ``` podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 \\ @@ -116,4 +123,5 @@ podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 \\ **[podman(1)](podman.1.md)**, **[podman-create(1)](podman-create.1.md)**, **[podman-run(1)](podman-run.1.md)** ## HISTORY +June 2025, Latest option added by Hayato Kihara August 2022, Originally written by Charlie Doern diff --git a/pkg/domain/entities/types/containers.go b/pkg/domain/entities/types/containers.go index 2a85a70eac..2e8441dbe0 100644 --- a/pkg/domain/entities/types/containers.go +++ b/pkg/domain/entities/types/containers.go @@ -61,6 +61,7 @@ type ContainerUpdateOptions struct { RestartRetries *uint Env []string UnsetEnv []string + Latest bool } func (u *ContainerUpdateOptions) ProcessSpecgen() { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index e7111dc5ba..c63e8f6626 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1804,7 +1804,7 @@ func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts enti // ContainerUpdate finds and updates the given container's cgroup config with the specified options func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *entities.ContainerUpdateOptions) (string, error) { updateOptions.ProcessSpecgen() - containers, err := getContainers(ic.Libpod, getContainersOptions{names: []string{updateOptions.NameOrID}}) + containers, err := getContainers(ic.Libpod, getContainersOptions{latest: updateOptions.Latest, names: []string{updateOptions.NameOrID}}) if err != nil { return "", err } diff --git a/test/e2e/update_test.go b/test/e2e/update_test.go index e442b7b36d..de83bdd95e 100644 --- a/test/e2e/update_test.go +++ b/test/e2e/update_test.go @@ -306,4 +306,39 @@ var _ = Describe("Podman update", func() { Expect(env).ToNot(ContainSubstring("FOO")) Expect(env).To(ContainSubstring("PATH=")) }) + + It("podman update the latest container", func() { + SkipIfRemote("--latest is local-only") + + restartPolicyName := ".HostConfig.RestartPolicy.Name" + restartPolicyRetries := ".HostConfig.RestartPolicy.MaximumRetryCount" + + // Arrange an old container + oldContainerName := "old-container" + oldContainer := podmanTest.Podman([]string{"run", "-d", "--name", oldContainerName, ALPINE, "top"}) + oldContainer.WaitWithDefaultTimeout() + Expect(oldContainer).Should(ExitCleanly()) + + podmanTest.CheckContainerSingleField(oldContainerName, restartPolicyName, "no") + podmanTest.CheckContainerSingleField(oldContainerName, restartPolicyRetries, "0") + + // Arrange a new container + newContainerName := "new-container" + newContainer := podmanTest.Podman([]string{"run", "-d", "--name", newContainerName, ALPINE, "top"}) + newContainer.WaitWithDefaultTimeout() + Expect(newContainer).Should(ExitCleanly()) + + podmanTest.CheckContainerSingleField(newContainerName, restartPolicyName, "no") + podmanTest.CheckContainerSingleField(newContainerName, restartPolicyRetries, "0") + + // Test the latest is updated + updatedContainer := podmanTest.Podman([]string{"update", "--restart", "on-failure:5", "--latest"}) + updatedContainer.WaitWithDefaultTimeout() + Expect(updatedContainer).Should(ExitCleanly()) + + podmanTest.CheckContainerSingleField(oldContainerName, restartPolicyName, "no") + podmanTest.CheckContainerSingleField(oldContainerName, restartPolicyRetries, "0") + podmanTest.CheckContainerSingleField(newContainerName, restartPolicyName, "on-failure") + podmanTest.CheckContainerSingleField(newContainerName, restartPolicyRetries, "5") + }) })