Skip to content

fix(quadlet): correct replace behavior and prevent duplicates in .app files#27980

Open
amyssnippet wants to merge 1 commit intocontainers:mainfrom
amyssnippet:fix/27977
Open

fix(quadlet): correct replace behavior and prevent duplicates in .app files#27980
amyssnippet wants to merge 1 commit intocontainers:mainfrom
amyssnippet:fix/27977

Conversation

@amyssnippet
Copy link
Contributor

@amyssnippet amyssnippet commented Jan 29, 2026

Checklist

Ensure you have completed the following checklist for your pull request to be reviewed:

  • Certify you wrote the patch or otherwise have the right to pass it on as an open-source patch by signing all commits. (git commit -s). (If needed, use git commit -s --amend). The author email must match the sign-off email address. See CONTRIBUTING.md for more information.
  • Referenced issues using Fixes: #27960 in commit message (if applicable)
  • Tests have been added/updated (or no tests are needed)
  • Documentation has been updated (or no documentation changes are needed)
  • All commits pass make validatepr (format/lint checks)
  • Release note entered in the section below (or None if no user-facing changes)

Description

This PR fixes two issues with podman quadlet install:

  1. Duplicate Entries: Previously, podman quadlet install would blindly append entries to the hidden .app tracking files. Repeated installations (especially with --replace) caused the same container file to be listed multiple times in the .app file. I added a check to ensure idempotency.
  2. Replace Failure: When using --replace, the destination file was opened without O_TRUNC. If the new quadlet file was shorter than the existing one, trailing data from the old file would persist (e.g., Restart=always + old_garbage). I added the os.O_TRUNC flag to ensure a clean overwrite.

Does this PR introduce a user-facing change?

Fixed a bug where 'podman quadlet install --replace' failed to truncate existing files and caused duplicate entries in application tracking files.

fixes: #27960

@amyssnippet
Copy link
Contributor Author

amyssnippet commented Jan 29, 2026

BEFORE

Image

i build the podman because i was not getting the quadlet

amol@Amols-MacBook-Air podman %podman --help
Manage pods, containers and images

Usage:
  podman [options] [command]

Available Commands:
  attach      Attach to a running container
  auto-update Auto update containers according to their auto-update policy
  build       Build an image using instructions from Containerfiles
  commit      Create new image based on the changed container
  compose     Run compose workloads via an external provider such as docker-compose or podman-compose
  container   Manage containers
  cp          Copy files/folders between a container and the local filesystem
  create      Create but do not start a container
  diff        Display the changes to the object's file system
  events      Show podman system events
  exec        Run a process in a running container
  export      Export container's filesystem contents as a tar archive
  farm        Farm out builds to remote machines
  generate    Generate structured data based on containers, pods or volumes
  healthcheck Manage health checks on containers
  help        Help about any command
  history     Show history of a specified image
  image       Manage images
  images      List images in local storage
  import      Import a tarball to create a filesystem image
  info        Display podman system information
  init        Initialize one or more containers
  inspect     Display the configuration of object denoted by ID
  kill        Kill one or more running containers with a specific signal
  kube        Play containers, pods or volumes from a structured file
  load        Load image(s) from a tar archive
  login       Log in to a container registry
  logout      Log out of a container registry
  logs        Fetch the logs of one or more containers
  machine     Manage a virtual machine
  manifest    Manipulate manifest lists and image indexes
  mount       Mount a working container's root filesystem
  network     Manage networks
  pause       Pause all the processes in one or more containers
  pod         Manage pods
  port        List port mappings or a specific mapping for the container
  ps          List containers
  pull        Pull an image from a registry
  push        Push an image to a specified destination
  rename      Rename an existing container
  restart     Restart one or more containers
  rm          Remove one or more containers
  rmi         Remove one or more images from local storage
  run         Run a command in a new container
  save        Save image(s) to an archive
  search      Search registry for image
  secret      Manage secrets
  start       Start one or more containers
  stats       Display a live stream of container resource usage statistics
  stop        Stop one or more containers
  system      Manage podman
  tag         Add an additional name to a local image
  top         Display the running processes of a container
  unmount     Unmount working container's root filesystem
  unpause     Unpause the processes in one or more containers
  unshare     Run a command in a modified user namespace
  untag       Remove a name from a local image
  update      Update an existing container
  version     Display the Podman version information
  volume      Manage volumes
  wait        Block on one or more containers

Options:
      --cgroup-manager string       Cgroup manager to use ("cgroupfs"|"systemd") (default "systemd")
      --conmon string               Path of the conmon binary
  -c, --connection string           Connection to use for remote Podman service
      --events-backend string       Events backend to use ("file"|"journald"|"none") (default "journald")
      --help                        Help for podman
      --hooks-dir strings           Set the OCI hooks directory path (may be set multiple times) (default [/usr/share/containers/oci/hooks.d])
      --identity string             path to SSH identity file, (CONTAINER_SSHKEY)
      --imagestore string           Path to the 'image store', different from 'graph root', use this to split storing the image into a separate 'image store', see 'man containers-storage.conf' for details
      --log-level string            Log messages above specified level (trace, debug, info, warn, warning, error, fatal, panic) (default "warn")
      --module strings              Load the containers.conf(5) module
      --network-cmd-path string     Path to the command for configuring the network
      --network-config-dir string   Path of the configuration directory for networks
      --out string                  Send output (stdout) from podman to a file
  -r, --remote                      Access remote Podman service
      --root string                 Path to the graph root directory where images, containers, etc. are stored
      --runroot string              Path to the 'run directory' where all state information is stored
      --runtime string              Path to the OCI-compatible binary used to run containers. (default "runc")
      --runtime-flag stringArray    add global flags for the container runtime
      --ssh string                  define the ssh mode (default "golang")
      --storage-driver string       Select which storage driver is used to manage storage of images and containers
      --storage-opt stringArray     Used to pass an option to the storage driver
      --syslog                      Output logging information to syslog as well as the console (default false)
      --tmpdir string               Path to the tmp directory for libpod state content.
                                    
                                    Note: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.
                                     (default "/run/user/1000/libpod/tmp")
      --transient-store             Enable transient container storage
      --url string                  URL to access Podman service (CONTAINER_HOST) (default "unix:///run/user/1000/podman/podman.sock")
  -v, --version                     version for podman
      --volumepath string           Path to the volume directory in which volume data is stored
amol@Amols-MacBook-Air podman %./bin/podman
Manage pods, containers and images

Usage:
  podman [options] [command]

Available Commands:
  artifact    Manage OCI artifacts
  attach      Attach to a running container
  auto-update Auto update containers according to their auto-update policy
  build       Build an image using instructions from Containerfiles
  commit      Create new image based on the changed container
  compose     Run compose workloads via an external provider such as docker-compose or podman-compose
  container   Manage containers
  cp          Copy files/folders between a container and the local filesystem
  create      Create but do not start a container
  diff        Display the changes to the object's file system
  events      Show podman system events
  exec        Run a process in a running container
  export      Export container's filesystem contents as a tar archive
  farm        Farm out builds to remote machines
  generate    Generate structured data based on containers, pods or volumes
  healthcheck Manage health checks on containers
  help        Help about any command
  history     Show history of a specified image
  image       Manage images
  images      List images in local storage
  import      Import a tarball to create a filesystem image
  info        Display podman system information
  init        Initialize one or more containers
  inspect     Display the configuration of object denoted by ID
  kill        Kill one or more running containers with a specific signal
  kube        Play containers, pods or volumes from a structured file
  load        Load image(s) from a tar archive
  login       Log in to a container registry
  logout      Log out of a container registry
  logs        Fetch the logs of one or more containers
  machine     Manage a virtual machine
  manifest    Manipulate manifest lists and image indexes
  mount       Mount a working container's root filesystem
  network     Manage networks
  pause       Pause all the processes in one or more containers
  pod         Manage pods
  port        List port mappings or a specific mapping for the container
  ps          List containers
  pull        Pull an image from a registry
  push        Push an image to a specified destination
  quadlet     Allows users to manage Quadlets
  rename      Rename an existing container
  restart     Restart one or more containers
  rm          Remove one or more containers
  rmi         Remove one or more images from local storage
  run         Run a command in a new container
  save        Save image(s) to an archive
  search      Search registry for image
  secret      Manage secrets
  start       Start one or more containers
  stats       Display a live stream of container resource usage statistics
  stop        Stop one or more containers
  system      Manage podman
  tag         Add an additional name to a local image
  top         Display the running processes of a container
  unmount     Unmount working container's root filesystem
  unpause     Unpause the processes in one or more containers
  unshare     Run a command in a modified user namespace
  untag       Remove a name from a local image
  update      Update an existing container
  version     Display the Podman version information
  volume      Manage volumes
  wait        Block on one or more containers

Options:
      --cdi-spec-dir stringArray    Set the CDI spec directory path (may be set multiple times) (default [/etc/cdi,/var/run/cdi])
      --cgroup-manager string       Cgroup manager to use ("cgroupfs"|"systemd") (default "systemd")
      --config string               Path to directory containing authentication config file
      --conmon string               Path of the conmon binary
  -c, --connection string           Connection to use for remote Podman service (CONTAINER_CONNECTION)
      --events-backend string       Events backend to use ("file"|"journald"|"none") (default "journald")
      --help                        Help for podman
      --hooks-dir stringArray       Set the OCI hooks directory path (may be set multiple times) (default [/usr/share/containers/oci/hooks.d])
      --identity string             path to SSH identity file, (CONTAINER_SSHKEY)
      --imagestore string           Path to the 'image store', different from 'graph root', use this to split storing the image into a separate 'image store', see 'man containers-storage.conf' for details
      --log-level string            Log messages above specified level (trace, debug, info, warn, warning, error, fatal, panic) (default "warn")
      --module stringArray          Load the containers.conf(5) module
      --network-config-dir string   Path of the configuration directory for networks
      --out string                  Send output (stdout) from podman to a file
  -r, --remote                      Access remote Podman service
      --root string                 Path to the graph root directory where images, containers, etc. are stored
      --runroot string              Path to the 'run directory' where all state information is stored
      --runtime string              Path to the OCI-compatible binary used to run containers. (default "runc")
      --runtime-flag stringArray    add global flags for the container runtime
      --ssh string                  define the ssh mode (default "golang")
      --storage-driver string       Select which storage driver is used to manage storage of images and containers
      --storage-opt stringArray     Used to pass an option to the storage driver
      --syslog                      Output podman-internal logs to syslog as well as the console (default false)
      --tls-ca string               path to TLS certificate Authority PEM file for remote.
      --tls-cert string             path to TLS client certificate PEM file for remote.
      --tls-key string              path to TLS client certificate private key PEM file for remote.
      --tmpdir string               Path to the tmp directory for libpod state content.
                                    
                                    Note: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.
                                     (default "/run/user/1000/libpod/tmp")
      --transient-store             Enable transient container storage
      --url string                  URL to access Podman service (CONTAINER_HOST) (default "unix:///run/user/1000/podman/podman.sock")
  -v, --version                     version for podman
      --volumepath string           Path to the volume directory in which volume data is stored
Error: missing command 'podman COMMAND'
amol@Amols-MacBook-Air podman %podman --version 
podman version 4.9.3
amol@Amols-MacBook-Air podman %./bin/podman --version
podman version 6.0.0-dev
amol@Amols-MacBook-Air podman %git status
On branch fix/27977
Your branch is up to date with 'origin/main'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        my_quadlet_dir/
        test.container

nothing added to commit but untracked files present (use "git add" to track)

@amyssnippet
Copy link
Contributor Author

AFTER

amol@Amols-MacBook-Air podman %git status
On branch fix/27977
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   pkg/domain/infra/abi/quadlet.go

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        my_quadlet_dir/
        test.container

no changes added to commit (use "git add" and/or "git commit -a")
amol@Amols-MacBook-Air podman %make bin/podman
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build \
         \
        -ldflags '-X github.com/containers/podman/v6/libpod/define.gitCommit=b60d234da4266d7ce08b950a68dc2c37844b018c-dirty -X github.com/containers/podman/v6/libpod/define.buildInfo=1769650004  -X github.com/containers/podman/v6/libpod/config._installPrefix=/usr/local -X github.com/containers/podman/v6/libpod/config._etcDir=/etc -X github.com/containers/podman/v6/pkg/systemd/quadlet._binDir=/usr/local/bin -X go.podman.io/image/v5/signature/internal/sequoia.sequoiaLibraryDir='""' -X go.podman.io/common/pkg/config.additionalHelperBinariesDir= ' \
        -tags "grpcnotrace   libsqlite3 systemd   seccomp " \
        -o bin/podman ./cmd/podman
test -z "" || chcon -t container_runtime_exec_t bin/podman
amol@Amols-MacBook-Air podman %./bin/podman --version
podman version 6.0.0-dev
amol@Amols-MacBook-Air podman %rm ~/.config/containers/systemd/myservice.container
amol@Amols-MacBook-Air podman %rm ~/.config/containers/systemd/.my_quadlet_dir.app
amol@Amols-MacBook-Air podman %./bin/podman quadlet install my_quadlet_dir
/home/amol/.config/containers/systemd/myservice.container
amol@Amols-MacBook-Air podman %./bin/podman quadlet install --replace my_quadlet_dir
/home/amol/.config/containers/systemd/myservice.container
amol@Amols-MacBook-Air podman %cat ~/.config/containers/systemd/.my_quadlet_dir.app
myservice.container
amol@Amols-MacBook-Air podman %   
amol@Amols-MacBook-Air podman %
                                                                                                                             
amol@Amols-MacBook-Air podman %

@amyssnippet
Copy link
Contributor Author

i guess this solves pr solves the issue completely. if any comments then let me know. Thanks!

@jankaluza
Copy link
Member

jankaluza commented Jan 29, 2026

Thanks for your contribution.

It would be great to add tests for these two cases.

For the first one, you could extend https://github.com/containers/podman/blob/main/test/system/253-podman-quadlet.bats#L467. I think the only change needed there to reproduce the issue is ensuring the first quadlet_file is longer than the second one. Currently, this is not the case.

For the second issue, maybe you can use the very same test with --replace and simply check the .app file?

You can run the tests using $ hack/bats --rootless 253-podman-quadlet, more info in test/README.md.

@packit-as-a-service
Copy link

[NON-BLOCKING] Packit jobs failed. @containers/packit-build please check. Everyone else, feel free to ignore.

@amyssnippet amyssnippet force-pushed the fix/27977 branch 4 times, most recently from 0dafcde to b17664b Compare January 29, 2026 18:05
@amyssnippet
Copy link
Contributor Author

@jankaluza i guess you changes are made, so have a look. and i am stuck at the ci checks failures, i am not sure why its happening. i am very doubtful to this failures

@amyssnippet amyssnippet requested a review from jankaluza January 29, 2026 18:21
@amyssnippet
Copy link
Contributor Author

amyssnippet commented Jan 31, 2026

@jankaluza everything good now??

@amyssnippet amyssnippet requested a review from jankaluza January 31, 2026 06:08
@amyssnippet
Copy link
Contributor Author

hi @jankaluza , i have reviewed both the ci failures very carefully and thats flaky not related to my quadlet changes. i am telling the reasons here

  1. which is the debian14 root failure. which is a podman kube play test with reserved Seccomp annotation in yaml and is located at test/e2e/play_kube_test.go:6090 and thats a timeout error in the test, it is declared as hardcoded 90 seconds in test/utils/utils.go:33 and in the test the failure is at PodmanTestIntegration.Cleanup() where 3 tasks are executed, podman stop --all -t 0, podman pod rm -fa -t 0, podman rm -fa -t 0. out of these 3 tasks a random one is causing the issue. and thats why this ci is failing.

  2. which is the fedora42 one, and is caused by podman kube play [It] test with sysctl defined located at test/e2e/play_kube_test.go:5891, caused by the rmAll() function during temp directory cleanup where container rootfs directory contains files, preventing cleanup (container didn't shut down cleanly or overlay unmount failed)

my observation tells these are flaky, whats your opinion, and my working style also suggests to report these flaky tests as issue in the repo and solve them in a new pr so that code doesnt broke.

what should i do, guide me, Thanks!

@amyssnippet
Copy link
Contributor Author

i did all the changes you recommended still there are failing checks

@jankaluza
Copy link
Member

I think this looks fine. There is still a possibility for a small race in appendLineToFile. In theory the appendLineToFile can be called twice concurrently and both processes can read the file and decide to add new line there at the same time, leading to duplication. But I think this is not a common scenario.

@containers/podman-maintainers , what do you think? I think we did lot of work on this PR and its ready for a review from maintainer now :-).

Copy link
Member

@mheon mheon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Test looks good, thank you.
Restarted the flake.

@mheon mheon enabled auto-merge February 17, 2026 17:29
@mheon mheon disabled auto-merge February 17, 2026 17:29
Copy link
Member

@Luap99 Luap99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amyssnippet Can you please squash the commits into logical chunks (one commit should be fine), there should be no fixup commits or anything as part of the PR as we merge commits as is and like this it makes no sense right now to look at commits one by one.

(Note I did not do an actual review of the changes)

@amyssnippet amyssnippet force-pushed the fix/27977 branch 2 times, most recently from 0b73808 to 9e867f8 Compare February 19, 2026 04:02
@amyssnippet
Copy link
Contributor Author

@Luap99 i squashed all commits into a single commit,

the failure is a consistent version mismatch issue. The golangci-lint v2.6.0 was built with Go 1.25, but some files in the codebase require Go 1.26 features, causing the panic.

@amyssnippet amyssnippet force-pushed the fix/27977 branch 2 times, most recently from 5eac1bf to ec3edeb Compare February 20, 2026 05:21
Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@amyssnippet
Copy link
Contributor Author

@Luap99 and @jankaluza any updates??

@jankaluza
Copy link
Member

@amyssnippet , the test fails with AWS issue. I think the solution to that is to rebase the PR against the latest main branch. Can you try to do that?

@amyssnippet
Copy link
Contributor Author

@amyssnippet , the test fails with AWS issue. I think the solution to that is to rebase the PR against the latest main branch. Can you try to do that?

Yeah sure

@amyssnippet amyssnippet force-pushed the fix/27977 branch 5 times, most recently from 5b5ef0d to fb8ee14 Compare February 24, 2026 03:35
@amyssnippet
Copy link
Contributor Author

  • ./bin/golangci-lint run --build-tags=remote,containers_image_openpgp
    panic: file requires newer Go version go1.26 (application built with go1.25) [recovered, repanicked]

Copy link
Member

@Honny1 Honny1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, the code looks good, but I have one suspicion.

I am not sure about adding comments for every single change. This is just my personal opinion, as I prefer a clean code approach, though the information about TOCTOU is great.

Regarding the CI: please rebase on main and drop the commit that edits the Makefile.

…race condition and better permission handling

Signed-off-by: Amol Yadav <amyssnipet@yahoo.com>
@amyssnippet
Copy link
Contributor Author

@Honny1 now??

@amyssnippet amyssnippet requested a review from Honny1 March 2, 2026 11:07
Copy link
Member

@Honny1 Honny1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, once CI passes.

The CI is failing:

time="2026-03-01T09:07:58-06:00" level=info msg="using commit range: 559dce7bf836cf78458cff3aa1410517b4003825..HEAD"
 * 0e5daa47e5 "fixed: quadlet.go with simple check for duplicates and a func to fix race condition and better permission handling" ... FAIL
  - FAIL - commit subject exceeds 90 characters
1 commits to fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

podman quadlet install/rm issues

6 participants