Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/connectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Connectors enable pyinfra to integrate with other tools out of the box. Connecto

+ Implement how commands are executed (``@ssh``, ``@local``)
+ Generate inventory hosts and data (``@terraform`` and ``@vagrant``)
+ Both of the above (``@docker``)
+ Both of the above (``@docker``, ``@podman``, and other container connectors)

Each connector page is listed below and contains examples as well as a list of available data that can be used to configure the connector.

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ podman = "pyinfra.connectors.docker:PodmanConnector"
local = "pyinfra.connectors.local:LocalConnector"
ssh = "pyinfra.connectors.ssh:SSHConnector"
dockerssh = "pyinfra.connectors.dockerssh:DockerSSHConnector"
podmanssh = "pyinfra.connectors.dockerssh:PodmanSSHConnector"
# Inventory only connectors
terraform = "pyinfra.connectors.terraform:TerraformInventoryConnector"
vagrant = "pyinfra.connectors.vagrant:VagrantInventoryConnector"
Expand Down
8 changes: 8 additions & 0 deletions src/pyinfra/connectors/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class DockerConnector(BaseConnector):
The Docker connector is great for testing pyinfra operations locally, rather than connecting to
a remote host over SSH each time. This gives you a fast, local-first devloop to iterate on when
writing deploys, operations or facts.

.. note::

For running Docker containers on remote hosts, see the :doc:`dockerssh` connector.
"""

# enable the use of other docker cli compatible tools like podman
Expand Down Expand Up @@ -364,6 +368,10 @@ class PodmanConnector(DockerConnector):
The Podman connector is great for testing pyinfra operations locally, rather than connecting to
a remote host over SSH each time. This gives you a fast, local-first devloop to iterate on when
writing deploys, operations or facts.

.. note::

For running Podman containers on remote hosts, see the :doc:`podmanssh` connector.
"""

docker_cmd = "podman"
Expand Down
99 changes: 89 additions & 10 deletions src/pyinfra/connectors/dockerssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class DockerSSHConnector(BaseConnector):
The ``@dockerssh`` connector allows you to run commands on Docker containers \
on a remote machine.

.. note::

For running Podman containers on remote hosts, see the :doc:`podmanssh` connector.

.. code:: shell

# A Docker base image must be provided
Expand All @@ -43,6 +47,8 @@ class DockerSSHConnector(BaseConnector):
"""

handles_execution = True
# enable the use of other docker cli compatible tools like podman
docker_cmd = "docker"

ssh: SSHConnector

Expand Down Expand Up @@ -77,11 +83,11 @@ def connect(self) -> None:
return

try:
with progress_spinner({"docker run"}):
with progress_spinner({f"{self.docker_cmd} run"}):
# last line is the container ID
status, output = self.ssh.run_shell_command(
StringCommand(
"docker",
self.docker_cmd,
"run",
"-d",
self.host.data.docker_image,
Expand All @@ -103,20 +109,23 @@ def connect(self) -> None:
def disconnect(self) -> None:
container_id = self.host.host_data["docker_container_id"][:12]

with progress_spinner({"docker commit"}):
_, output = self.ssh.run_shell_command(StringCommand("docker", "commit", container_id))
with progress_spinner({f"{self.docker_cmd} commit"}):
_, output = self.ssh.run_shell_command(
StringCommand(self.docker_cmd, "commit", container_id)
)

# Last line is the image ID, get sha256:[XXXXXXXXXX]...
image_id = output.stdout_lines[-1][7:19]

with progress_spinner({"docker rm"}):
with progress_spinner({f"{self.docker_cmd} rm"}):
self.ssh.run_shell_command(
StringCommand("docker", "rm", "-f", container_id),
StringCommand(self.docker_cmd, "rm", "-f", container_id),
)

logger.info(
"{0}docker build complete, image ID: {1}".format(
"{0}{1} build complete, image ID: {2}".format(
self.host.print_prefix,
self.docker_cmd,
click.style(image_id, bold=True),
),
)
Expand All @@ -138,7 +147,7 @@ def run_shell_command(

docker_flags = "-it" if local_arguments.get("_get_pty") else "-i"
docker_command = StringCommand(
"docker",
self.docker_cmd,
"exec",
docker_flags,
container_id,
Expand Down Expand Up @@ -192,7 +201,7 @@ def put_file(
try:
docker_id = self.host.host_data["docker_container_id"]
docker_command = StringCommand(
"docker",
self.docker_cmd,
"cp",
remote_temp_filename,
f"{docker_id}:{remote_filename}",
Expand Down Expand Up @@ -246,7 +255,7 @@ def get_file(
try:
docker_id = self.host.host_data["docker_container_id"]
docker_command = StringCommand(
"docker",
self.docker_cmd,
"cp",
f"{docker_id}:{remote_filename}",
remote_temp_filename,
Expand Down Expand Up @@ -295,3 +304,73 @@ def remote_remove(self, filename, print_output: bool = False, print_input: bool

if not remove_status:
raise IOError(output.stderr)


@memoize
def show_warning_podman() -> None:
logger.warning("The @podmanssh connector is in beta!")


class PodmanSSHConnector(DockerSSHConnector):
"""
**Note**: this connector is in beta!

The ``@podmanssh`` connector allows you to run commands on Podman containers
on a remote machine over SSH. This is useful when you need to manage containers
running on remote hosts where Podman is installed instead of Docker.

.. note::

This connector requires SSH access to the remote host and Podman to be installed
on the target machine. It operates similarly to ``@dockerssh`` but uses the
``podman`` command instead of ``docker``.

+ **Remote container creation**: Creates a new container from the specified image on the remote host
+ **Command execution**: Runs operations inside the container via ``podman exec``
+ **File operations**: Supports uploading/downloading files to/from containers via ``podman cp``
+ **Container cleanup**: Automatically commits and removes containers when operations complete

.. code:: shell

# A Podman base image must be provided
pyinfra @podmanssh/remotehost:alpine:3.8 ...

# Run operations on multiple remote Podman containers in parallel
pyinfra @podmanssh/web1:nginx:latest,@podmanssh/web2:nginx:latest deploy.py

# Use with specific SSH connection settings
pyinfra @podmanssh/production-server:alpine:3.18 --sudo --port 2222 operations/

**Comparison with other connectors:**

+ Use ``@podman`` for local Podman containers
+ Use ``@dockerssh`` for remote Docker containers
+ Use ``@podmanssh`` for remote Podman containers (this connector)

The Podman SSH connector is particularly useful in environments where:

+ Docker is not available but Podman is installed
+ Rootless containers are preferred for security
+ You need OCI-compliant container operations on remote hosts
"""

docker_cmd = "podman"

@override
@staticmethod
def make_names_data(name):
try:
hostname, image = name.split(":", 1)
except (AttributeError, ValueError): # failure to parse the name
raise InventoryError("No ssh host or podman base image provided!")

if not image:
raise InventoryError("No podman base image provided!")

show_warning_podman()

yield (
"@podmanssh/{0}:{1}".format(hostname, image),
{"ssh_hostname": hostname, "docker_image": image},
["@podmanssh"],
)
Loading