1
0
Fork 0
mirror of https://github.com/containers/ansible-podman-collections.git synced 2026-02-04 07:11:49 +00:00

Add podman system connection modules (#971)

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
This commit is contained in:
Sergey 2025-08-25 09:35:27 +03:00 committed by GitHub
parent ee52d9de78
commit f333fe7fca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1410 additions and 0 deletions

View file

@ -0,0 +1,32 @@
name: Podman system connection
on:
push:
paths:
- '.github/workflows/podman_system_connection.yml'
- '.github/workflows/reusable-module-test.yml'
- 'ci/*.yml'
- 'ci/run_containers_tests.sh'
- 'ci/playbooks/containers/podman_system_connection.yml'
- 'plugins/modules/podman_system_connection.py'
- 'tests/integration/targets/podman_system_connection/**'
branches:
- main
pull_request:
paths:
- '.github/workflows/podman_system_connection.yml'
- '.github/workflows/reusable-module-test.yml'
- 'ci/*.yml'
- 'ci/run_containers_tests.sh'
- 'ci/playbooks/containers/podman_system_connection.yml'
- 'plugins/modules/podman_system_connection.py'
- 'tests/integration/targets/podman_system_connection/**'
schedule:
- cron: 4 0 * * * # Run daily at 0:03 UTC
jobs:
test_podman_system_connection:
uses: ./.github/workflows/reusable-module-test.yml
with:
module_name: 'podman_system_connection'
display_name: 'Podman system connection'

View file

@ -0,0 +1,32 @@
name: Podman system connection info
on:
push:
paths:
- '.github/workflows/podman_system_connection_info.yml'
- '.github/workflows/reusable-module-test.yml'
- 'ci/*.yml'
- 'ci/run_containers_tests.sh'
- 'ci/playbooks/containers/podman_system_connection_info.yml'
- 'plugins/modules/podman_system_connection_info.py'
- 'tests/integration/targets/podman_system_connection_info/**'
branches:
- main
pull_request:
paths:
- '.github/workflows/podman_system_connection_info.yml'
- '.github/workflows/reusable-module-test.yml'
- 'ci/*.yml'
- 'ci/run_containers_tests.sh'
- 'ci/playbooks/containers/podman_system_connection_info.yml'
- 'plugins/modules/podman_system_connection_info.py'
- 'tests/integration/targets/podman_system_connection_info/**'
schedule:
- cron: 4 0 * * * # Run daily at 0:03 UTC
jobs:
test_podman_system_connection_info:
uses: ./.github/workflows/reusable-module-test.yml
with:
module_name: 'podman_system_connection_info'
display_name: 'Podman system connection info'

View file

@ -0,0 +1,8 @@
---
- hosts: all
gather_facts: true
tasks:
- include_role:
name: podman_system_connection
vars:
ansible_python_interpreter: "{{ _ansible_python_interpreter }}"

View file

@ -0,0 +1,8 @@
---
- hosts: all
gather_facts: true
tasks:
- include_role:
name: podman_system_connection_info
vars:
ansible_python_interpreter: "{{ _ansible_python_interpreter }}"

View file

@ -0,0 +1,689 @@
#!/usr/bin/python
# Copyright (c) 2025 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: podman_system_connection
author:
- "Sagi Shnaidman (@sshnaidm)"
version_added: '1.18.0'
short_description: Manage podman system connections
notes: []
description:
- Manage podman system connections with podman system connection command.
- Add, remove, rename and set default connections to Podman services.
requirements:
- podman
options:
name:
description:
- Name of the connection
type: str
required: True
state:
description:
- State of the connection
type: str
choices:
- present
- absent
default: present
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
machine running C(podman)
default: 'podman'
type: str
destination:
description:
- Destination for the connection. Required when I(state=present).
- Can be in the format C([user@]hostname[:port]), C(ssh://[user@]hostname[:port]),
C(unix://path), C(tcp://hostname:port)
type: str
default:
description:
- Make this connection the default for this user
type: bool
identity:
description:
- Path to SSH identity file
type: str
port:
description:
- SSH port (default is 22)
type: int
socket_path:
description:
- Path to the Podman service unix domain socket on the ssh destination host
type: str
new_name:
description:
- New name for the connection when renaming (used with I(state=present))
type: str
"""
EXAMPLES = r"""
- name: Add a basic SSH connection
containers.podman.podman_system_connection:
name: production
destination: root@server.example.com
state: present
- name: Add SSH connection with custom port and identity
containers.podman.podman_system_connection:
name: staging
destination: user@staging.example.com
port: 2222
identity: ~/.ssh/staging_rsa
state: present
- name: Add connection and set as default
containers.podman.podman_system_connection:
name: development
destination: dev@dev.example.com
default: true
state: present
- name: Add unix socket connection
containers.podman.podman_system_connection:
name: local
destination: unix:///run/podman/podman.sock
state: present
- name: Add TCP connection
containers.podman.podman_system_connection:
name: remote_tcp
destination: tcp://remote.example.com:8080
state: present
- name: Rename a connection
containers.podman.podman_system_connection:
name: old_name
new_name: new_name
state: present
- name: Remove a connection
containers.podman.podman_system_connection:
name: old_connection
state: absent
"""
RETURN = r"""
connection:
description: Connection information in podman JSON format
returned: always
type: dict
sample: {
"Name": "production",
"URI": "ssh://root@server.example.com:22/run/user/0/podman/podman.sock",
"Default": true,
"ReadWrite": true
}
actions:
description: Actions performed on the connection
returned: always
type: list
sample:
- "Connection 'production' added"
- "Connection 'production' set as default"
"""
import json
from ansible.module_utils.basic import AnsibleModule
def get_connection_info(module, executable, name):
"""Get specific connection information from podman system connection list.
Retrieves connection information by executing 'podman system connection list --format json'
and searches for a connection with the specified name.
Args:
module (AnsibleModule): The Ansible module instance for running commands and error handling
executable (str): Path to the podman executable
name (str): Name of the specific connection to search for
Returns:
dict or None: Connection dictionary if found, None if not found
Connection dictionary may contain:
- Name (str): Connection name
- URI (str): Connection URI (ssh://, unix://, tcp://)
- Default (bool): Whether this is the default connection
- ReadWrite (bool, optional): Read/write access (Podman version dependent)
- Identity (str, optional): SSH identity file path (Podman version dependent)
- IsMachine (bool, optional): Whether this is a machine connection (Podman version dependent)
Raises:
AnsibleFailJson: If the podman command fails or JSON parsing fails
Note:
This function differs from the _info module's get_connection_info by returning
a single connection dict or None, rather than a list of connections.
"""
command = [executable, "system", "connection", "list", "--format", "json"]
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Failed to list connections: %s" % err)
try:
connections = json.loads(out) if out.strip() else []
for conn in connections:
if conn.get("Name") == name:
return conn
return None
except json.JSONDecodeError as e:
module.fail_json(msg="Failed to parse connection list JSON: %s" % str(e))
def connections_are_identical(conn1, conn2):
"""Check if two connections are identical by comparing their attributes (excluding Name).
Compares all relevant connection attributes to determine if two connections
represent the same configuration. The Name field is excluded from comparison
to allow for rename operations.
Args:
conn1 (dict or None): First connection dictionary to compare
conn2 (dict or None): Second connection dictionary to compare
Returns:
bool: True if connections have identical attributes, False otherwise
Compared attributes:
- URI (str): Connection URI (must match exactly)
- Default (bool): Default status (must match)
- ReadWrite (bool, optional): Read/write access (Podman version dependent)
- Identity (str, optional): SSH identity file path (Podman version dependent)
- IsMachine (bool, optional): Machine connection flag (Podman version dependent)
Note:
Returns False if either connection is None or empty. Optional fields
(ReadWrite, Identity, IsMachine) are handled gracefully - missing fields
are treated as None for comparison purposes.
"""
if not conn1 or not conn2:
return False
# Compare all relevant attributes except Name
return {**conn1, "Name": None} == {**conn2, "Name": None}
def destination_matches_uri(destination, current_uri):
"""Check if a destination specification matches an existing connection URI.
Compares a user-provided destination with the current connection URI,
handling both full protocol URIs and simplified hostname formats.
Args:
destination (str): User-provided destination specification
current_uri (str): Existing connection URI from podman
Returns:
bool: True if destination matches the current URI, False otherwise
Matching logic:
- Full URIs (ssh://, unix://, tcp://): Requires exact match
- Simple format (user@host): Checks if contained in expanded URI
Examples:
destination_matches_uri("ssh://user@host:22", "ssh://user@host:22/path") -> True
destination_matches_uri("user@host", "ssh://user@host:22/path") -> True
destination_matches_uri("unix:///tmp/sock", "unix:///tmp/sock") -> True
destination_matches_uri("user@host", "ssh://other@host:22/path") -> False
Note:
Podman typically expands simple formats like "user@host" into full URIs
like "ssh://user@host:22/run/user/uid/podman/podman.sock", so this
function allows matching against the simplified form.
"""
if destination.startswith(("ssh://", "unix://", "tcp://")):
# Exact protocol match required for full URIs
return current_uri == destination
# Simple hostname format - check if it's contained in the expanded URI
# podman expands "user@host" to "ssh://user@host:22/run/user/uid/podman/podman.sock"
return destination in current_uri
def connection_needs_update(current_conn, params):
"""Check if a connection needs to be updated based on current vs desired state.
Compares the current connection configuration with the desired module parameters
to determine if changes are needed. Generates diff information for changed fields.
Args:
current_conn (dict or None): Current connection dictionary from podman, or None if not exists
params (dict): Module parameters containing desired connection configuration
Returns:
tuple: A tuple containing:
- needs_update (bool): True if connection needs changes, False if current state matches desired
- diffs (dict): Dictionary with 'before' and 'after' keys containing changed fields
Checked parameters:
- destination: Compared against current URI using destination_matches_uri()
- default: Compared against current Default field
- identity: Compared against current Identity field (if provided)
Examples:
# Connection doesn't exist
connection_needs_update(None, {...}) -> (True, {"before": {}, "after": {}})
# URI change needed
connection_needs_update({"URI": "old"}, {"destination": "new"}) ->
(True, {"before": {"URI": "old"}, "after": {"URI": "new"}})
# No changes needed
connection_needs_update({"URI": "same", "Default": False}, {"destination": "same", "default": False}) ->
(False, {"before": {}, "after": {}})
Note:
The identity parameter is only checked if explicitly provided (not None).
Missing identity in params is not treated as a change requirement.
"""
if not current_conn:
return True, {"before": {}, "after": {}}
destination = params["destination"]
default = params["default"]
diffs = {"before": {}, "after": {}}
# Check if URI matches the expected destination format
current_uri = current_conn.get("URI", "")
if not destination_matches_uri(destination, current_uri):
diffs["before"]["URI"] = current_uri
diffs["after"]["URI"] = destination
# Check if default status needs to change
# Compare "Default" only when it's set explicitly in parameters,
# first system connection is always default
if default is not None and default != current_conn.get("Default"):
diffs["before"]["Default"] = current_conn.get("Default")
diffs["after"]["Default"] = default
# Check Identity if provided
identity = params.get("identity")
if identity is not None and identity != current_conn.get("Identity", ""):
diffs["before"]["Identity"] = current_conn.get("Identity", "")
diffs["after"]["Identity"] = identity
if diffs["before"] or diffs["after"]:
return True, diffs
return False, diffs
def add_connection(module, executable, name, destination, default, identity, port, socket_path):
"""Add a new podman system connection with specified parameters.
Executes 'podman system connection add' with the provided configuration parameters.
Supports all connection options including SSH settings and default flag.
Args:
module (AnsibleModule): The Ansible module instance for running commands and error handling
executable (str): Path to the podman executable
name (str): Name for the new connection
destination (str): Connection destination (URI or simple hostname format)
default (bool): Whether to set this connection as the default
identity (str or None): Path to SSH identity file (optional)
port (int or None): SSH port number (optional, defaults to 22 for SSH)
socket_path (str or None): Path to podman socket on remote host (optional)
Returns:
tuple: A tuple containing:
- changed (bool): Always True since a connection is being added
- actions (list): List of action messages describing what was done
Command construction:
Base: 'podman system connection add'
Options added conditionally:
--default (if default=True)
--identity <path> (if identity provided)
--port <port> (if port provided)
--socket-path <path> (if socket_path provided)
Arguments: <name> <destination>
Actions generated:
- "Connection '<name>' added" (always)
- "Connection '<name>' set as default" (if default=True)
Raises:
AnsibleFailJson: If the podman command fails
Note:
In check mode, the command is not executed but actions are still generated
to show what would happen. This allows for proper diff and change reporting.
"""
actions = []
command = [executable, "system", "connection", "add"]
if default:
command.extend(["--default"])
if identity:
command.extend(["--identity", identity])
if port:
command.extend(["--port", str(port)])
if socket_path:
command.extend(["--socket-path", socket_path])
command.extend([name, destination])
if not module.check_mode:
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Failed to add connection '%s': %s" % (name, err))
actions.append("Connection '%s' added" % name)
if default:
actions.append("Connection '%s' set as default" % name)
return True, actions
def remove_connection(module, executable, name):
"""Remove an existing podman system connection.
Executes 'podman system connection remove' to delete the specified connection.
Args:
module (AnsibleModule): The Ansible module instance for running commands and error handling
executable (str): Path to the podman executable
name (str): Name of the connection to remove
Returns:
tuple: A tuple containing:
- changed (bool): Always True since a connection is being removed
- actions (list): List containing single action message about the removal
Command executed:
'podman system connection remove <name>'
Actions generated:
- "Connection '<name>' removed"
Raises:
AnsibleFailJson: If the podman command fails (e.g., connection doesn't exist)
Note:
In check mode, the command is not executed but the action message is still
generated to show what would happen. The caller should verify the connection
exists before calling this function if idempotent behavior is required.
"""
command = [executable, "system", "connection", "remove", name]
if not module.check_mode:
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Failed to remove connection '%s': %s" % (name, err))
return True, ["Connection '%s' removed" % name]
def rename_connection(module, executable, old_name, new_name):
"""Rename an existing podman system connection.
Executes 'podman system connection rename' to change the name of an existing connection.
All connection attributes (URI, default status, etc.) remain unchanged.
Args:
module (AnsibleModule): The Ansible module instance for running commands and error handling
executable (str): Path to the podman executable
old_name (str): Current name of the connection to rename
new_name (str): New name for the connection
Returns:
tuple: A tuple containing:
- changed (bool): Always True since a connection is being renamed
- actions (list): List containing single action message about the rename
Command executed:
'podman system connection rename <old_name> <new_name>'
Actions generated:
- "Connection '<old_name>' renamed to '<new_name>'"
Raises:
AnsibleFailJson: If the podman command fails (e.g., old connection doesn't exist,
new name already exists, etc.)
Note:
In check mode, the command is not executed but the action message is still
generated to show what would happen. The caller should verify the old connection
exists and the new name is available before calling this function.
"""
command = [executable, "system", "connection", "rename", old_name, new_name]
if not module.check_mode:
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Failed to rename connection '%s' to '%s': %s" % (old_name, new_name, err))
return True, ["Connection '%s' renamed to '%s'" % (old_name, new_name)]
def set_default_connection(module, executable, name):
"""Set an existing connection as the default connection.
Executes 'podman system connection default' to mark the specified connection
as the default for the current user. Any previously default connection will
no longer be the default.
Args:
module (AnsibleModule): The Ansible module instance for running commands and error handling
executable (str): Path to the podman executable
name (str): Name of the connection to set as default
Returns:
tuple: A tuple containing:
- changed (bool): Always True since default status is being changed
- actions (list): List containing single action message about setting default
Command executed:
'podman system connection default <name>'
Actions generated:
- "Connection '<name>' set as default"
Raises:
AnsibleFailJson: If the podman command fails (e.g., connection doesn't exist)
Note:
In check mode, the command is not executed but the action message is still
generated to show what would happen. This function does not verify that
the connection exists - the caller should handle that validation.
Setting a connection as default automatically unsets any previous default
connection, but this function only reports the action for the specified connection.
"""
command = [executable, "system", "connection", "default", name]
if not module.check_mode:
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Failed to set connection '%s' as default: %s" % (name, err))
return True, ["Connection '%s' set as default" % name]
def main():
"""Main entry point for the podman_system_connection Ansible module.
This function sets up the Ansible module, validates parameters, and orchestrates
the management of podman system connections based on the desired state.
The module supports the following operations:
- Creating new connections (state=present with destination)
- Updating existing connections (state=present with different parameters)
- Renaming connections (state=present with new_name)
- Removing connections (state=absent)
- Setting connections as default (default=true)
Module Parameters:
name (str, required): Name of the connection
state (str): 'present' or 'absent' (default: 'present')
executable (str): Path to podman executable (default: 'podman')
destination (str): Connection destination (required for state=present, except rename)
default (bool): Set as default connection (default: False)
identity (str): SSH identity file path (optional)
port (int): SSH port number (optional)
socket_path (str): Remote podman socket path (optional)
new_name (str): New name for rename operation (optional)
Parameter constraints:
- destination and new_name are mutually exclusive
- destination is required when state=present (except for rename operations)
Operation logic:
state=present with new_name: Rename operation
- Fails if source connection doesn't exist
- Idempotent if target name exists with identical configuration
- Performs rename if target name doesn't exist
state=present with destination: Add/update operation
- Checks if connection needs update using connection_needs_update()
- Removes existing connection if changes needed
- Adds new connection with desired parameters
- Idempotent if existing connection matches desired state
state=absent: Remove operation
- Removes connection if it exists
- Idempotent if connection doesn't exist
Cross-version compatibility:
Handles optional fields (ReadWrite, Identity, IsMachine) that may not be
present in all Podman versions. Missing fields are treated gracefully.
Note:
The module follows the 'always require full description of desired state'
design pattern, requiring destination for all present operations except rename.
"""
module = AnsibleModule(
argument_spec=dict(
name=dict(type="str", required=True),
state=dict(type="str", choices=["present", "absent"], default="present"),
executable=dict(type="str", default="podman"),
destination=dict(type="str"),
default=dict(type="bool"),
identity=dict(type="str"),
port=dict(type="int"),
socket_path=dict(type="str"),
new_name=dict(type="str"),
),
supports_check_mode=True,
mutually_exclusive=[
("destination", "new_name"),
],
)
name = module.params["name"]
state = module.params["state"]
executable = module.get_bin_path(module.params["executable"], required=True)
destination = module.params["destination"]
default = module.params["default"]
identity = module.params["identity"]
port = module.params["port"]
socket_path = module.params["socket_path"]
new_name = module.params["new_name"]
# Validate required parameters - always require destination when state=present (except for rename)
if state == "present" and not new_name and not destination:
module.fail_json(msg="destination is required when state=present")
# Get current connection state
current_conn = get_connection_info(module, executable, name)
changed = False
actions = []
connection_info = {}
diff = {"before": {}, "after": {}}
if state == "present":
if new_name:
# Handle rename operation
if not current_conn:
module.fail_json(msg="Cannot rename non-existent connection '%s'" % name)
# Check if new name already exists
new_conn = get_connection_info(module, executable, new_name)
if new_conn:
# Check if the existing connection with new_name is identical to current_conn
if connections_are_identical(current_conn, new_conn):
# Connections are identical - idempotent, no changes needed
connection_info = new_conn
else:
# Different connection exists with the same name - fail
module.fail_json(msg="Connection with name '%s' already exists and is different" % new_name)
else:
# Perform rename
changed, rename_actions = rename_connection(module, executable, name, new_name)
actions.extend(rename_actions)
# Get connection info after rename
if not module.check_mode:
final_conn = get_connection_info(module, executable, new_name)
connection_info = final_conn
else:
old_conn_info = get_connection_info(module, executable, name)
old_conn_info["Name"] = new_name
connection_info = old_conn_info
else:
# Handle add/update operation (destination is always provided)
needs_update, diff = connection_needs_update(current_conn, module.params)
if needs_update:
# Remove existing connection if it exists with different parameters
if current_conn:
changed, remove_actions = remove_connection(module, executable, name)
actions.extend(remove_actions)
# Add the connection with desired parameters
changed, add_actions = add_connection(
module, executable, name, destination, default, identity, port, socket_path
)
actions.extend(add_actions)
# Get final connection state
if not module.check_mode:
final_conn = get_connection_info(module, executable, name)
connection_info = final_conn
else:
# In check mode, provide expected values
connection_info = {
"Name": name,
"URI": destination,
"Default": default,
}
else:
# Connection exists and matches desired state - idempotent
connection_info = current_conn
elif state == "absent":
if current_conn:
changed, remove_actions = remove_connection(module, executable, name)
actions.extend(remove_actions)
connection_info = {}
module.log("PODMAN-SYSTEM-CONNECTION: changed=%s, actions=%s, diff=%s" % (changed, actions, diff))
diff_info = {}
if diff["before"] or diff["after"]:
diff_info = {
"before": "\n".join(["%s - %s" % (k, v) for k, v in sorted(diff["before"].items())]) + "\n",
"after": "\n".join(["%s - %s" % (k, v) for k, v in sorted(diff["after"].items())]) + "\n",
}
module.exit_json(
changed=changed,
connection=connection_info,
actions=actions,
diff=diff_info,
)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,174 @@
#!/usr/bin/python
# Copyright (c) 2025 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: podman_system_connection_info
author:
- "Sagi Shnaidman (@sshnaidm)"
version_added: '1.18.0'
short_description: Gather info about podman system connections
notes: []
description:
- Gather info about podman system connections with podman system connection list command.
requirements:
- "Podman installed on host"
options:
name:
description:
- Name of the connection to gather info about
- If not provided, info about all connections will be returned
type: str
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
machine running C(podman)
default: 'podman'
type: str
"""
EXAMPLES = r"""
- name: Gather info about all connections
containers.podman.podman_system_connection_info:
- name: Gather info about specific connection
containers.podman.podman_system_connection_info:
name: production
- name: Get connection info and register result
containers.podman.podman_system_connection_info:
name: staging
register: staging_connection
- name: Display connection URI
debug:
msg: "Staging connection URI: {{ staging_connection.connections[0].URI }}"
when: staging_connection.connections | length > 0
"""
RETURN = r"""
connections:
description: Facts from all or specified connections
returned: always
type: list
sample: [
{
"Name": "production",
"URI": "ssh://root@server.example.com:22/run/user/0/podman/podman.sock",
"Identity": "/home/user/.ssh/id_rsa",
"Default": true,
"ReadWrite": true
},
{
"Name": "local",
"URI": "unix:///run/user/1000/podman/podman.sock",
"Identity": "",
"Default": false,
"ReadWrite": false
},
{
"Name": "development",
"URI": "ssh://dev@dev.example.com:22/run/user/1000/podman/podman.sock",
"Identity": "/home/user/.ssh/dev_rsa",
"Default": false,
"ReadWrite": true
}
]
"""
import json
from ansible.module_utils.basic import AnsibleModule
def get_connection_info(module, executable, name):
"""Get connection information from podman system connection list.
Retrieves connection information by executing 'podman system connection list --format json'
and optionally filters the results for a specific connection by name.
Args:
module (AnsibleModule): The Ansible module instance for running commands and error handling
executable (str): Path to the podman executable
name (str or None): Name of specific connection to filter for, or None to get all connections
Returns:
tuple: A tuple containing:
- connections (list): List of connection dictionaries matching the filter criteria
- out (str): Raw stdout from the podman command
- err (str): Raw stderr from the podman command
Raises:
AnsibleFailJson: If the podman command fails or JSON parsing fails
Note:
When a specific connection name is provided but not found, an empty list is returned
rather than failing. This allows for idempotent behavior when checking if connections exist.
"""
command = [executable, "system", "connection", "list", "--format", "json"]
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Unable to get list of connections: %s" % err)
try:
connections = json.loads(out) if out.strip() else []
except json.JSONDecodeError as e:
module.fail_json(msg="Failed to parse connection list JSON: %s" % str(e))
if name:
# Filter for specific connection
filtered_connections = [conn for conn in connections if conn.get("Name") == name]
# if not filtered_connections:
# module.fail_json(msg="Connection '%s' not found" % name)
return filtered_connections, out, err
return connections, out, err
def main():
"""Main entry point for the podman_system_connection_info Ansible module.
This function sets up the Ansible module, validates parameters, retrieves connection
information from podman, and returns the results in the expected format.
The module supports check mode and never reports changes since it's an info-gathering
module that doesn't modify system state.
Module Parameters:
executable (str): Path to podman executable (default: "podman")
name (str, optional): Name of specific connection to get info about.
If not provided, returns info about all connections.
Each connection dictionary may contain the following fields:
- Name (str): Connection name
- URI (str): Connection URI (ssh://, unix://, tcp://)
- Default (bool): Whether this is the default connection
- ReadWrite (bool, optional): Read/write access (Podman version dependent)
- Identity (str, optional): SSH identity file path (Podman version dependent)
- IsMachine (bool, optional): Whether this is a machine connection (Podman version dependent)
Note:
Some fields like ReadWrite, Identity, and IsMachine are optional and may not be present
in all Podman versions. The module handles this gracefully.
"""
module = AnsibleModule(
argument_spec=dict(executable=dict(type="str", default="podman"), name=dict(type="str")),
supports_check_mode=True,
)
name = module.params["name"]
executable = module.get_bin_path(module.params["executable"], required=True)
inspect_results, out, err = get_connection_info(module, executable, name)
results = {"changed": False, "connections": inspect_results, "stderr": err}
module.exit_json(**results)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,280 @@
- name: Test podman_system_connection
block:
- name: Print podman version
command: podman version
- name: Generate random values for connection names
set_fact:
test_connection_1: "{{ 'ansible-test-conn1-%0x' % ((2**32) | random) }}"
test_connection_2: "{{ 'ansible-test-conn2-%0x' % ((2**32) | random) }}"
test_connection_3: "{{ 'ansible-test-conn3-%0x' % ((2**32) | random) }}"
- name: Clean up any existing test connections
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ item }}"
state: absent
ignore_errors: true
loop:
- "{{ test_connection_1 }}"
- "{{ test_connection_2 }}"
- "{{ test_connection_3 }}"
- name: Test adding a basic unix socket connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_1 }}"
destination: "unix:///tmp/test-socket-{{ test_connection_1 }}.sock"
state: present
register: add_result
- name: Check add connection results
assert:
that:
- add_result is changed
- add_result.connection.Name == test_connection_1
- "'unix://' in add_result.connection.URI"
- add_result.actions | length > 0
- "'added' in add_result.actions[0]"
- name: Test idempotency - add same connection again
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_1 }}"
destination: "unix:///tmp/test-socket-{{ test_connection_1 }}.sock"
state: present
register: add_idem_result
- name: Check idempotency
assert:
that:
- add_idem_result is not changed
- add_idem_result.connection.Name == test_connection_1
- name: Test adding connection with unix socket
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_2 }}"
destination: "unix:///tmp/test-socket-{{ test_connection_2 }}.sock"
state: present
register: add_custom_result
- name: Check custom connection results
assert:
that:
- add_custom_result is changed
- add_custom_result.connection.Name == test_connection_2
- "'unix://' in add_custom_result.connection.URI"
- add_custom_result.actions | length > 0
- name: Test adding connection with default flag
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_3 }}"
destination: "unix:///tmp/test-socket-{{ test_connection_3 }}.sock"
default: true
state: present
register: add_default_result
- name: Check default connection results
assert:
that:
- add_default_result is changed
- add_default_result.connection.Name == test_connection_3
- add_default_result.connection.Default == true
- name: Test unix socket connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "test-unix"
destination: "unix:///run/podman/podman.sock"
state: present
register: unix_result
- name: Check unix socket connection
assert:
that:
- unix_result is changed
- unix_result.connection.Name == "test-unix"
- "'unix://' in unix_result.connection.URI"
- name: Test unix socket connection again for idempotency
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "test-unix"
destination: "unix:///run/podman/podman.sock"
state: present
register: unix_result2
- name: Check unix socket connection
assert:
that:
- unix_result2 is not changed
- unix_result2.connection.Name == "test-unix"
- "'unix://' in unix_result2.connection.URI"
- name: Test unix socket connection - different
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "test-unix"
destination: "unix:///run/podman/podman2.sock"
state: present
register: unix_result3
- name: Check unix socket connection
assert:
that:
- unix_result3 is changed
- unix_result3.connection.Name == "test-unix"
- "'unix://' in unix_result3.connection.URI"
- name: Test TCP connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "test-tcp"
destination: "tcp://localhost:8080"
state: present
register: tcp_result
- name: Check TCP connection
assert:
that:
- tcp_result is changed
- tcp_result.connection.Name == "test-tcp"
- "'tcp://' in tcp_result.connection.URI"
- name: Test connection rename
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_1 }}"
new_name: "{{ test_connection_1 }}-renamed"
state: present
register: rename_result
- name: Check rename results
assert:
that:
- rename_result is changed
- rename_result.connection.Name == test_connection_1 + "-renamed"
- name: Test rename non-existent connection (should fail)
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "non-existent-connection"
new_name: "new-name"
state: present
register: rename_fail_result
ignore_errors: true
- name: Check rename failure
assert:
that:
- rename_fail_result is failed
- "'Cannot rename non-existent connection' in rename_fail_result.msg"
- name: Test setting existing connection as default
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_2 }}"
destination: "unix:///tmp/test-socket-{{ test_connection_2 }}.sock"
default: true
state: present
register: set_default_result
- name: Check set default results
assert:
that:
- set_default_result is changed
- set_default_result.actions | select('search', 'set as default') | list | length > 0
- name: Test removing connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_2 }}"
state: absent
register: remove_result
- name: Check remove results
assert:
that:
- remove_result is changed
- "'removed' in remove_result.actions[0]"
- name: Test idempotency - remove non-existent connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_2 }}"
state: absent
register: remove_idem_result
- name: Check remove idempotency
assert:
that:
- remove_idem_result is not changed
- name: Test check mode for adding connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "check-mode-test"
destination: "unix:///tmp/test-socket-check.sock"
state: present
check_mode: true
register: check_add_result
- name: Verify check mode add
assert:
that:
- check_add_result is changed
- check_add_result.connection.Name == "check-mode-test"
- name: Verify connection was not actually added in check mode
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "check-mode-test"
register: check_verify
- name: Check that connection doesn't exist
assert:
that:
- check_verify.connections | length == 0
- name: Test check mode for removing connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_3 }}"
state: absent
check_mode: true
register: check_remove_result
- name: Verify check mode remove
assert:
that:
- check_remove_result is changed
- name: Verify connection still exists after check mode
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_3 }}"
register: check_still_exists
- name: Check that connection still exists
assert:
that:
- check_still_exists.connections | length > 0
always:
- name: Clean up test connections
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ item }}"
state: absent
ignore_errors: true
loop:
- "{{ test_connection_1 }}"
- "{{ test_connection_1 }}-renamed"
- "{{ test_connection_2 }}"
- "{{ test_connection_3 }}"
- "test-unix"
- "test-tcp"
- "check-mode-test"

View file

@ -0,0 +1,187 @@
- name: Test podman_system_connection_info
block:
- name: Print podman version
command: podman version
- name: Generate random values for connection names
set_fact:
test_connection_1: "{{ 'ansible-test-info1-%0x' % ((2**32) | random) }}"
test_connection_2: "{{ 'ansible-test-info2-%0x' % ((2**32) | random) }}"
- name: Clean up any existing test connections
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ item }}"
state: absent
ignore_errors: true
loop:
- "{{ test_connection_1 }}"
- "{{ test_connection_2 }}"
- name: Get info about all connections (empty list)
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
register: empty_list_result
- name: Check empty connections list
assert:
that:
- empty_list_result is not changed
- empty_list_result.connections is defined
- empty_list_result.connections is iterable
- name: Create test connection 1 (unix socket)
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_1 }}"
destination: "unix:///tmp/test-socket-1.sock"
state: present
- name: Create test connection 2 as default (tcp)
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_2 }}"
destination: "tcp://127.0.0.1:8080"
default: true
state: present
- name: Get info about all connections
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
register: all_connections_result
- name: Check all connections results
assert:
that:
- all_connections_result is not changed
- all_connections_result.connections | length >= 2
- all_connections_result.connections | selectattr('Name', 'equalto', test_connection_1) | list | length == 1
- all_connections_result.connections | selectattr('Name', 'equalto', test_connection_2) | list | length == 1
- name: Get info about specific connection
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_1 }}"
register: specific_connection_result
- name: Check specific connection results
assert:
that:
- specific_connection_result is not changed
- specific_connection_result.connections | length == 1
- specific_connection_result.connections[0].Name == test_connection_1
- "'unix://' in specific_connection_result.connections[0].URI"
- name: Get info about default connection
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "{{ test_connection_2 }}"
register: default_connection_result
- name: Check default connection results
assert:
that:
- default_connection_result is not changed
- default_connection_result.connections | length == 1
- default_connection_result.connections[0].Name == test_connection_2
- default_connection_result.connections[0].Default == true
- "'tcp://' in default_connection_result.connections[0].URI"
- name: Try to get info about non-existent connection
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "non-existent-connection"
register: non_existent_result
- name: Check non-existent connection failure
assert:
that:
- non_existent_result.connections | length == 0
- name: Test check mode
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
check_mode: true
register: check_mode_result
- name: Check check mode results
assert:
that:
- check_mode_result is not changed
- check_mode_result.connections is defined
- name: Verify connection attributes for readwrite connections
set_fact:
test_conn_1_info: "{{ all_connections_result.connections | selectattr('Name', 'equalto', test_connection_1) | first }}"
test_conn_2_info: "{{ all_connections_result.connections | selectattr('Name', 'equalto', test_connection_2) | first }}"
- name: Check connection 1 attributes
assert:
that:
- test_conn_1_info.Name == test_connection_1
- test_conn_1_info.URI is defined
- test_conn_1_info.Default is defined
- test_conn_1_info.Default == false
# ReadWrite, Identity, and IsMachine are optional fields
- name: Check connection 2 attributes (default)
assert:
that:
- test_conn_2_info.Name == test_connection_2
- test_conn_2_info.URI is defined
- test_conn_2_info.Default == true
# ReadWrite, Identity, and IsMachine are optional fields
- name: Test with unix socket connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "test-unix-info"
destination: "unix:///run/podman/podman.sock"
state: present
- name: Get info about unix socket connection
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "test-unix-info"
register: unix_info_result
- name: Check unix socket connection info
assert:
that:
- unix_info_result.connections | length == 1
- unix_info_result.connections[0].Name == "test-unix-info"
- "'unix://' in unix_info_result.connections[0].URI"
- name: Test with TCP connection
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "test-tcp-info"
destination: "tcp://localhost:8080"
state: present
- name: Get info about TCP connection
containers.podman.podman_system_connection_info:
executable: "{{ test_executable | default('podman') }}"
name: "test-tcp-info"
register: tcp_info_result
- name: Check TCP connection info
assert:
that:
- tcp_info_result.connections | length == 1
- tcp_info_result.connections[0].Name == "test-tcp-info"
- "'tcp://' in tcp_info_result.connections[0].URI"
always:
- name: Clean up test connections
containers.podman.podman_system_connection:
executable: "{{ test_executable | default('podman') }}"
name: "{{ item }}"
state: absent
ignore_errors: true
loop:
- "{{ test_connection_1 }}"
- "{{ test_connection_2 }}"
- "test-unix-info"
- "test-tcp-info"