Skip to content

Introduce PodmanCommand #558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Introduce PodmanCommand #558

wants to merge 2 commits into from

Conversation

apyrgio
Copy link

@apyrgio apyrgio commented Jun 19, 2025

Introduce the PodmanCommand class, which is responsible for providing a Pythonic interface over the podman binary. The only prerequisite is that the podman binary must be installed in the user's system.

More specifically, this class brings the following improvements to the codebase:

  1. Allows users to run arbitrary Podman commands, in a more friendly interface than subprocess.run().
  2. Provides a Pythonic interface for podman machine commands, where arguments are type-checked and results are native Python objects.
  3. Allows Linux users to easily start a Podman REST API using a new context manager (with PodmanCommand.service())

As an example, Linux users can now do the following:

import podman
import os

XDG_RUNTIME_DIR = os.environ.get("XDG_RUNTIME_DIR", "/tmp")
SOCK_URI = f"unix:///{XDG_RUNTIME_DIR}/podman/podman.sock"

# Run an arbitrary Podman command.
command = podman.PodmanCommand()
version = command.run(["--version"])
print(f"Podman version is: {version}")

# Start an API service and connect the PodmanClient to it.
with command.service(uri=SOCK_URI) as p:
    with podman.PodmanClient(base_url=SOCK_URI) as client:
        containers = client.containers.list()
        print(f"Containers: {containers}")

Also, macOS users can work more easily with Podman machines:

import podman

# Run `podman machine` commands through a Pythonic interface.
command = podman.PodmanCommand()
command.options.log_level = "debug"
command.machine.init("dz", now=True, capture_output=False)
machines = command.machine.list()
print(f"Machines: {machines}")

# Or run arbitrary Podman commands for which podman-py has no support yet.
connection = command.run(["system", "connection", "list"]).split("\n")[1]
uri = connection.split()[1]
identity = connection.split()[2]
print(f"Using socket URI {uri}")
print(f"Using identity {identity}")

# Start an API service and connect the PodmanClient to it.
with podman.PodmanClient(base_url=uri, identity=identity) as client:
    containers = client.containers.list()
    print(f"Containers: {containers}")

This PR is not totally complete yet, since tests are missing for PodmanCommand.machine. Also, I'm not sure if the docstrings are in the style that this repo expects. In any case, before polishing this PR, I'd like to get some feedback about this approach. Let me know if it's something that works for you, or if you prefer a different way.

Refs #545

Introduce the PodmanCommand class, which is responsible for providing a
Pythonic interface over the `podman` binary. The only prerequisite is
that the `podman` binary must be installed in the user's system.

More specifically, this class brings the following improvements to the
codebase:

1. Allows users to run arbitrary Podman commands, in a more friendly
   interface than subprocess.run().
2. Provides a Pythonic interface for `podman machine` commands, where
   arguments are type-checked and results are native Python objects.
3. Allows Linux users to easily start a Podman REST API using a new
   context manager (`with PodmanCommand.service()`)

As an example, Linux users can now do the following:

    import podman
    import os

    XDG_RUNTIME_DIR = os.environ.get("XDG_RUNTIME_DIR", "/tmp")
    SOCK_URI = f"unix:///{XDG_RUNTIME_DIR}/podman/podman.sock"

    # Run an arbitrary Podman command.
    command = podman.PodmanCommand()
    version = command.run(["--version"])
    print(f"Podman version is: {version}")

    # Start an API service and connect the PodmanClient to it.
    with command.service(uri=SOCK_URI) as p:
        with podman.PodmanClient(base_url=SOCK_URI) as client:
            containers = client.containers.list()
            print(f"Containers: {containers}")

Also, macOS users can work more easily with Podman machines:

    import podman

    # Run `podman machine` commands through a Pythonic interface.
    command = podman.PodmanCommand()
    command.options.log_level = "debug"
    command.machine.init("dz", now=True, capture_output=False)
    machines = command.machine.list()
    print(f"Machines: {machines}")

    # Or run arbitrary Podman commands for which podman-py has no support yet.
    connection = command.run(["system", "connection", "list"]).split("\n")[1]
    uri = connection.split()[1]
    identity = connection.split()[2]
    print(f"Using socket URI {uri}")
    print(f"Using identity {identity}")

    # Start an API service and connect the PodmanClient to it.
    with podman.PodmanClient(base_url=uri, identity=identity) as client:
        containers = client.containers.list()
        print(f"Containers: {containers}")

Refs containers#545

Signed-off-by: Alex Pyrgiotis <[email protected]>
Copy link
Contributor

openshift-ci bot commented Jun 19, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: apyrgio
Once this PR has been reviewed and has the lgtm label, please assign lsm5 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Use the PodmanCommand in integration tests, and more specifically its
ability to start a REST API service. As a result, we can remove the
previous code that did this.

Signed-off-by: Alex Pyrgiotis <[email protected]>
@inknos
Copy link
Contributor

inknos commented Jun 19, 2025

@jwhonce please take a look

@inknos
Copy link
Contributor

inknos commented Jun 19, 2025

Thanks for the PR.

This will require some time and discussion, so here's my initial comment. Please, @apyrgio , correct me if I understand your PR wrong.

If I am getting it right, you want to run podman machine as part of Podman Py calls. The new class will fill the gap between the RESTful API and podman's command line arguments (from the top of my mind, the gaps are only podman machine and podman compose, but there could be a couple more). The way you thought about it, is by separating the logic into PodmanCommand class. This will reduce the overlap between PodmanClient and PodmanCommand to minimal or none.

If this is correct, I would very soon consider splitting this feature in a subpackage. This is a nice feature, but podman-py provides binding to the API, so it's not a core feature. So the final goal has to be to differentiate between pip install podman, and pip install podman[cli]. I am quite convinced that this should be a hard requirement for podman-py

@apyrgio
Copy link
Author

apyrgio commented Jun 19, 2025

Hi @inknos! Yeap, I think you got the gist of it (although I'm not really sure what you mean with "This will reduce the overlap between PodmanClient and PodmanCommand to minimal or none.")

As for splitting this in a subpackage, that's makes sense to me, I do get that the repo mainly involves around Podman's REST API. One thing I wanted to note is that I've purposefully added the core features necessary to start the REST API service, either via PodmanCommand.machine on Windows/macOS or PodmanCommand.service on Linux. So, if the logic were to go in a podman[cli] subpackage, it would be very barebones. Perhaps a podman[bootstrap] subpackage would be more fitting?

One last question, I wonder how users across OSes currently bootstrap this API. In practice, Windows/macOS users have to work with podman machine commands to bring it up. Linux users have to run podman system service to do the same. I guess people do this manually, or have their own CLI glue, but let me know if I'm missing something.

@inknos
Copy link
Contributor

inknos commented Jun 19, 2025

This will reduce the overlap between PodmanClient and PodmanCommand to minimal or none

It means that I really don't want to have duplicate features. The cli features should be implemented as part of api calls only, and having PodmanCommand could potentially introduce overlaps and double some maintenance costs.

I've purposefully added the core features necessary to start the REST API service, either via PodmanCommand.machine on Windows/macOS or PodmanCommand.service on Linux.

yup, it does make sense from a design perspective, but machine on Linux is useless.

Linux users have to run podman system service to do the same. I guess people do this manually, or have their own CLI glue, but let me know if I'm missing something

Right, under the hood, podman has a client server implementation. We provide podman and podman-remote binaries, which differ only for how they interact with libpod underneath. for podman-remote you have to enable and run podman.socket. technically speaking, you always run podman-remote when you type podman in win/macos. that's one of the reasons why everything is done in a podman machine (many reasons actually, but one of them is that you can talk with the server via socket). Python Podman does essentially the same and talks as a client, so needs a socket (systemd on linux and machine on win/macos).

@apyrgio
Copy link
Author

apyrgio commented Jun 19, 2025

It means that I really don't want to have duplicate features. The cli features should be implemented as part of api calls only, and having PodmanCommand could potentially introduce overlaps and double some maintenance costs.

Ok, got it. I'm totally behind that, I don't want to reimplement a feature of PodmanClient in PodmanCommand.

Also, thanks for the rest of the clarifications 🙂 .

@inknos
Copy link
Contributor

inknos commented Jun 19, 2025

One more thing that comes to mind, and then I'll let the PR rest for a bit, is that in podman-py we don't test on win/macos. The reason is that the userbase of podman-py does not interact with machines, and so the code is run within linux all the time, whether it's a VM or on someone's machine. I am not sure how tricky it would be to start testing on different OS, surely interesting, but could be complex. Maybe the tests could be run on Podman's CI. 🤔

@apyrgio
Copy link
Author

apyrgio commented Jun 20, 2025

Well, for better or worse, we (the Dangerzone team) are going to make use of podman-py on Windows and macOS. Which means that we will encounter the same issue, i.e., how do we make sure that what we've put together works.

What we plan to do in the immediate feature is take advantage of the WSL2 support in the windows-2025 GitHub runner image, and use it to test the podman machine commands.

If this code splits into a different subpackage and you're ok with integration testing on GHA, I'd be more than happy to assist there and do some knowledge sharing.

@apyrgio
Copy link
Author

apyrgio commented Jul 7, 2025

Hey folks. Let me know if there's any actionable item I can do from my side to get this moving, or if you want to shelf it. It will help us find a proper place for this code internally.

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.

2 participants