Skip to content

refactor: podman quadlet sub-command#28335

Open
axel7083 wants to merge 1 commit into
containers:mainfrom
axel7083:refactor/quadlet-applications
Open

refactor: podman quadlet sub-command#28335
axel7083 wants to merge 1 commit into
containers:mainfrom
axel7083:refactor/quadlet-applications

Conversation

@axel7083
Copy link
Copy Markdown
Contributor

@axel7083 axel7083 commented Mar 20, 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: #00000 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)

Does this PR introduce a user-facing change?

refactor the podman quadlet command

While working on #28117 and the comment from @Luap99 (#28117 (comment)) I had a lot of issues working around the .app and .asset file, a suggestion made was to get rid of those and consider an application as a folder.

Here is a POC / proposal of how it could works

podman quadlet install

When trying to install from a directory, you will need to specify --application as we want to avoid dumping all the content of the directory at the root.

Installing from a directory (support recursive)

$: podman quadlet install --application flask-redis ./flask-redis
$: ree /home/axel7083/.config/containers/systemd
/home/axel7083/.config/containers/systemd
└── flask-redis
    ├── app
    │   ├── app.py
    │   ├── Containerfile
    │   └── requirements.txt
    ├── flask.kube
    ├── play.yaml
    └── README.md

podman quadlet ls --format=json

Following flask-redis installed above we get

$: podman quadlet ls --format=json
[
  {
    "Name": "flask.kube",
    "UnitName": "flask.service",
    "Path": "/home/axel7083/.config/containers/systemd/flask-redis/flask.kube",
    "Status": "inactive/dead",
    "App": "flask-redis" <-- application name is the name of the sub directory
  },
]

Now let's add an nginx.image

$: podman quadlet install ./nginx.image
$: podman quadlet ls --format=json
[
  {
    "Name": "flask.kube",
    "UnitName": "flask.service",
    "Path": "/home/axel7083/.config/containers/systemd/flask-redis/flask.kube",
    "Status": "inactive/dead",
    "App": "flask-redis"
  },
  {
    "Name": "nginx.image",
    "UnitName": "nginx-image.service",
    "Path": "/home/axel7083/.config/containers/systemd/nginx.image",
    "Status": "inactive/dead",
    "App": "" <-- No application name as it is at the root
  }
]

⚠️ this is only taking the first directory, if we have /home/axel7083/.config/containers/systemd/flask-redis/nested/flask.kube" it will still be flask-redis.

podman quadlet rm

I added a --recursive option, the rm support two input, a quadlet file (E.g. foo.image) or an application name. However when trying t o delete an application it will throw an error

$: /bin/podman quadlet rm flask-redis
Error: unable to remove Quadlet: cannot find application "flask-redis"

You need to specify the --recursive flag to delete the quadlets in the application

I did not in this POC deleted the full application directory, I was not sure, open to suggestion

@github-actions github-actions Bot added the kind/api-change Change to remote API; merits scrutiny label Mar 20, 2026
Copy link
Copy Markdown
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.

Only a short high level look I am definitely in favour of this directory approach.

Question do we care about backwards compatibility? We still have 3-4 weeks till podman6 so we can break if wanted. And given quadlet install is a rather new command maybe reworking that now without worrying about compatibility is the easiest.

cc @mheon

@mheon
Copy link
Copy Markdown
Member

mheon commented Mar 23, 2026

Ummm. Given how new these commands are, it would not be unprecedented to break folks, but it's still a very bad user experience...

Our migration options are also not particularly pretty though. I suppose we could detect legacy project files and convert to a directory and remove the file on finding them?

@Luap99
Copy link
Copy Markdown
Member

Luap99 commented Mar 24, 2026

Our migration options are also not particularly pretty though. I suppose we could detect legacy project files and convert to a directory and remove the file on finding them?

Convert when? "inside quadlet"? when running "podman quadlet list/install/remove"? For install/remove it is not clear to me we should migrate other files. And on list it seems strange to do a rewrite of files, i.e. there is no sane way to make this in a atomic fashion so we risk leaving the files in a bad state when getting killed.

@simonbrauner
Copy link
Copy Markdown
Contributor

I also like using directories instead of the .app files, introducing the --application option instead depending on directory name, as well as removing the If a quadlet is part of an application, removing that specific quadlet will remove the entire application. functionality.

Service naming conflicts

But this introduces the possibility of having multiple services of the same name.

$ podman quadlet install postgres-17-test --application=a
/home/sbrauner/.config/containers/systemd/a/A.container
/home/sbrauner/.config/containers/systemd/a/B.container
/home/sbrauner/.config/containers/systemd/a/README.md
$ podman quadlet install postgres-17-test --application=b
/home/sbrauner/.config/containers/systemd/b/A.container
/home/sbrauner/.config/containers/systemd/b/B.container
/home/sbrauner/.config/containers/systemd/b/README.md
$ podman quadlet list
ERRO[0000] Unexpected unit returned by systemd - was not searching for A.service 
ERRO[0000] Unexpected unit returned by systemd - was not searching for B.service 
NAME         UNIT NAME   PATH ON DISK                                             STATUS         APPLICATION
A.container  A.service   /home/sbrauner/.config/containers/systemd/b/A.container  inactive/dead  b
B.container  B.service   /home/sbrauner/.config/containers/systemd/b/B.container  inactive/dead  b
             A.service                                                            inactive/dead  
             B.service                                                            inactive/dead  

So I suppose it should either check for naming conflicts, or make the application name part of the service name, so that multiple quadlets with the same application-local name can exist independently.

Implicit merge of applications

I am also thinking that maybe it would be slightly more convenient for the user if they were notified that they are adding quadlets to an existing application. Now it happens implicitly, so it can hypothetically happen that the user:

creates application `webserver`, with containers `A` and `B`
forgets about it
creates different application and decides to also call it `webserver`, with containers `C` and `D`
removes `webserver`, thinking they are removing `C` and `D`, but `A` and `B` would also get removed.

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch 6 times, most recently from df7607c to 42c6321 Compare April 13, 2026 12:29
@axel7083 axel7083 changed the title refactor: podman quadlet sub-command WIP / POC refactor: podman quadlet sub-command Apr 13, 2026
@axel7083 axel7083 marked this pull request as ready for review April 13, 2026 13:09
@axel7083 axel7083 requested a review from Luap99 April 13, 2026 13:09
@Luap99 Luap99 added the 6.0 Breaking changes for Podman 6.0 label Apr 21, 2026
@simonbrauner
Copy link
Copy Markdown
Contributor

Also took a mostly high level look, it looks nice overall.

It does not look like it's ready to be merged, there are TODOs.

My previous comment should probably be addressed, as installing multiple quadlets with the same name breaks.

Removing the whole application keeps non-quadlet files and the directory itself.

The word refactor does not seem right, as this changes functionality.

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from 42c6321 to 5d12165 Compare April 28, 2026 13:14
@axel7083
Copy link
Copy Markdown
Contributor Author

Hey @simonbrauner !

It does not look like it's ready to be merged, there are TODOs.

I removed the TODOs

My previous comment should probably be addressed, as installing multiple quadlets with the same name breaks.

This is something already possible with the current, so not sure how this could been addressed? Maybe we need a dedicated issue to try to address that?

Removing the whole application keeps non-quadlet files and the directory itself.

It does if there is no errors while removing each quadlet in the application, the directory will be removed 👍

// clean up application folder when no error
if len(report.Errors) == 0 {
appPath, err := getApplicationPath(quadlet)
if err != nil {
report.Errors[quadlet] = err
} else {
err = os.RemoveAll(appPath)
if err != nil {
report.Errors[quadlet] = err
}
}
}

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from 5d12165 to af3ed8d Compare April 28, 2026 13:25
@packit-as-a-service
Copy link
Copy Markdown

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

@simonbrauner
Copy link
Copy Markdown
Contributor

simonbrauner commented Apr 29, 2026

Hello @axel7083.

I removed the TODOs

There is still one about validation of application name https://github.com/containers/podman/pull/28335/changes#diff-98a6e1ecc741f449262cca173c6e557c64dbf2706e5b062967b07b76018b62adR10

This is something already possible with the current, so not sure how this could been addressed? Maybe we need a dedicated issue to try to address that?

I would say this is introduced by the possibility of having multiple quadlets of the same name across different applications.

With the original approach, installing multiple quadlets of the same name fails:

$ podman quadlet install postgres-17-test
/home/sbrauner/.config/containers/systemd/README.md
/home/sbrauner/.config/containers/systemd/A.container
/home/sbrauner/.config/containers/systemd/B.container

$ podman/bin/podman quadlet install postgres-17-test-2
Error: quadlet "postgres-17-test-2/A.container" failed to install: a Quadlet with name A.container already exists, refusing to overwrite
Error: quadlet "postgres-17-test-2/B.container" failed to install: a Quadlet with name B.container already exists, refusing to overwrite
Error: quadlet "postgres-17-test-2/README.md" failed to install: a Quadlet with name README.md already exists, refusing to overwrite
Error: errors occurred installing some Quadlets

So I am thinking, the two ideas would be:

  1. Check if quadlet of that name exists anywhere regardless of the application and fail if it does.
  2. Add some prefix to the unit name, so that systemd is not confused, for example a/A.service and b/A.service would be two separate services which just happen to have the same file name of a corresponding quadlet, but can coexist independently.

But I am not saying this is something that has to be done as part of this PR, I was just thinking of the consequences of your changes and this is what I thought of.

It does if there is no errors while removing each quadlet in the application, the directory will be removed 👍

👍

@axel7083
Copy link
Copy Markdown
Contributor Author

So I am thinking, the two ideas would be:

  1. Check if quadlet of that name exists anywhere regardless of the application and fail if it does.

I will try to add this tomorrow morning 👍

@axel7083
Copy link
Copy Markdown
Contributor Author

@simonbrauner I reviewed the problem, and the current behavior that you are seeing is not preventing "duplicate" quadlets with the same service name, but rather preventing you to override, the logic is technically still here.

If you try to podman quadlet install ./foo.image twice with the change you will get the same error.

if errors.Is(err, fs.ErrExist) {
return "", fmt.Errorf("a Quadlet with name %s already exists, refusing to overwrite", filepath.Base(finalPath))
}

The problem of duplicates service name is a bit more annoying to solve, let me explain. A service name is either define by its name or by its Service=<name> key.

The quadlet systemd generator is reading and parsing the unit file, and lookup for the Service key in the [Unit] group as shown bellow

func getServiceName(quadletUnitFile *parser.UnitFile, groupName string, defaultExtraSuffix string) string {
if serviceName, ok := quadletUnitFile.Lookup(groupName, KeyServiceName); ok {
return serviceName
}
baseServiceName := removeExtension(quadletUnitFile.Filename, "", "")
if baseServiceName[len(baseServiceName)-1] == '@' {
baseServiceName = baseServiceName[:len(baseServiceName)-1]
defaultExtraSuffix += "@"
}
return baseServiceName + defaultExtraSuffix
}

But for us this is a little bit a challenge, as when should we fails? If the user is installing foo.image and bar.image and they both have Service=hello.service this is a problem, and currently one will be randomly picked (I reported the problem in #28118).


So my question would be, when should we fails?

If the user already have multiple quadlets with the same service name, if we try to install another should we fail?

Does the quadlet generator should fail if multiple quadlets have the same service name? Or just a warning in the stderr?

I think we can parse every quadlets we try to install and check that in the list of quadlets we try to install (when passing a directory or a tar) we could read and parse them, but what if they are invalid? currently I don't think we are checking if the quadlets files are valid 🤔 .

Anyway, I think this is going a bit out of scope of this PR, and as it is already big enough, I think we may skip the service name issue, wdyt?

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch 3 times, most recently from 2fd9762 to 97fcbb6 Compare April 30, 2026 07:18
@simonbrauner
Copy link
Copy Markdown
Contributor

Anyway, I think this is going a bit out of scope of this PR, and as it is already big enough, I think we may skip the service name issue, wdyt?

Yes, sure. Initially I thought that this is strange behavior introduced by this PR, but if it's just something that was there already (just less likely to happen), then I have nothing against considering it out of scope.

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from 97fcbb6 to c026ffc Compare May 4, 2026 08:38
@packit-as-a-service
Copy link
Copy Markdown

Cockpit tests failed for commit c026ffc. @martinpitt, @jelly, @mvollmer please check.

@packit-as-a-service
Copy link
Copy Markdown

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

@simonbrauner
Copy link
Copy Markdown
Contributor

@axel7083 I had similar CI failures and it got solved by rebasing on the latest main.

Comment thread pkg/domain/infra/abi/quadlet.go
Comment thread pkg/domain/infra/abi/quadlet_utils.go
Comment thread pkg/domain/infra/abi/quadlet.go
Comment thread pkg/domain/infra/abi/quadlet.go
Comment thread pkg/domain/infra/abi/quadlet.go Outdated
Comment thread pkg/domain/infra/abi/quadlet_utils.go Outdated
@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from c026ffc to 8f0ef82 Compare May 5, 2026 07:20
@axel7083 axel7083 requested a review from Honny1 May 5, 2026 09:41
Comment thread pkg/domain/infra/abi/quadlet.go
Comment thread pkg/domain/infra/abi/quadlet.go Outdated
Comment thread cmd/podman/quadlet/remove.go Outdated
Comment thread test/apiv2/36-quadlets.at Outdated
Comment thread cmd/podman/quadlet/remove.go Outdated
@l0rd l0rd force-pushed the refactor/quadlet-applications branch 3 times, most recently from e2a13c5 to 2317792 Compare May 12, 2026 12:36
Copy link
Copy Markdown
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.

not a very deep review, one minor thing for the docs

logic wise LGTM overall though I did not test thoroughly

cc @inknos ref #28627

Comment thread cmd/podman/quadlet/remove.go Outdated
Comment thread pkg/api/handlers/libpod/quadlets.go Outdated

#### **--recursive**

When an application name is specified, you need to specify --recursive flag to remove all the Quadlets which belongs to that specific application.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This reads a bit confusing. Is this mandatory when an application name is specified, and does nothing otherwise? If so, do we need the dedicated argument? User intent in specifying an application seems pretty clear that they want everything gone

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree, this is probably an option that can be on by default if an application requires it it makes sense to have it on and documented

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for you feedback and for what it's worth I think that's confusing too.

The flag is required when the user removes an application (the folder, recursively). And it's not required when the user wants to delete a single quadlet (i.e. podman quadlet rm flask.container flask.volume works fine).

The rationale for requiring this flag in the case of an application is to ensure that the user understands the implications of the removal: rm -rf <app-folder>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm. As long as we have clear error messages telling folks to use --recursive when they try and remove an app, I can live with it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the current error:

$ podman quadlet rm foo
Error: unable to remove Quadlet: refusing to remove application "foo": recursive option is not set

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And I have simplified the documentation of the --recursive flag:

#### **--recursive**

Required when removing applications (default false)

Copy link
Copy Markdown
Collaborator

@inknos inknos left a comment

Choose a reason for hiding this comment

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

what happens if you do
podman quadlet install --application nameofapp.container directory/ and you have installed nameofapp.container already?

I know that disambiguation is an issue we'll have to deal with eventually out of this PR, but it makes sense to me to at least check for valid application names that can collide with quadlet files. I could definitely see a user calling "container" their application and running into an unexpected collision.


#### **--recursive**

When an application name is specified, you need to specify --recursive flag to remove all the Quadlets which belongs to that specific application.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree, this is probably an option that can be on by default if an application requires it it makes sense to have it on and documented

@l0rd
Copy link
Copy Markdown
Member

l0rd commented May 13, 2026

what happens if you do podman quadlet install --application nameofapp.container directory/ and you have installed nameofapp.container already?

I know that disambiguation is an issue we'll have to deal with eventually out of this PR, but it makes sense to me to at least check for valid application names that can collide with quadlet files. I could definitely see a user calling "container" their application and running into an unexpected collision.

Do you mean we should avoid installing an application whose name would be a valid quadlet name? Or that we should check the names of the existing quadlets (not services) and avoid collisions?

@inknos
Copy link
Copy Markdown
Collaborator

inknos commented May 13, 2026

Do you mean we should avoid installing an application whose name would be a valid quadlet name? Or that we should check the names of the existing quadlets (not services) and avoid collisions?

Avoid the application has a valid quadlet name, like it should not end with .container,.pod, etc

@l0rd l0rd force-pushed the refactor/quadlet-applications branch from 2317792 to 73ce7ff Compare May 13, 2026 21:28
@l0rd
Copy link
Copy Markdown
Member

l0rd commented May 13, 2026

Avoid the application has a valid quadlet name, like it should not end with .container,.pod, etc

I have added this condition to the function that validates application names and a new unit test as well.

Fixes: containers#28118
Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
@l0rd l0rd force-pushed the refactor/quadlet-applications branch from 73ce7ff to 6079157 Compare May 13, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.0 Breaking changes for Podman 6.0 kind/api-change Change to remote API; merits scrutiny

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants