1
0
Fork 0
mirror of https://github.com/containers/ansible-podman-collections.git synced 2026-02-03 23:01:48 +00:00

Run black -l 120 on all Python files to unify the style (#939)

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
This commit is contained in:
Sergey 2025-06-15 18:25:48 +03:00 committed by GitHub
parent 50c5a2549d
commit 4c682e170c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 3828 additions and 3129 deletions

View file

@ -9,6 +9,6 @@ if len(sys.argv) < 2:
version = sys.argv[1]
with open("galaxy.yml.in") as f:
y = yaml.safe_load(f)
y['version'] = version
y["version"] = version
with open("galaxy.yml", "w") as ff:
yaml.safe_dump(y, ff)

View file

@ -2,7 +2,8 @@
# Copyright (c) 2022 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Written by Janos Gerzson (grzs@backendo.com)
from __future__ import (absolute_import, division, print_function)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
@ -119,7 +120,7 @@ from ansible.plugins.become import BecomeBase
class BecomeModule(BecomeBase):
name = 'containers.podman.podman_unshare'
name = "containers.podman.podman_unshare"
def build_become_command(self, cmd, shell):
super(BecomeModule, self).build_become_command(cmd, shell)
@ -127,18 +128,20 @@ class BecomeModule(BecomeBase):
if not cmd:
return cmd
becomecmd = 'podman unshare'
becomecmd = "podman unshare"
user = self.get_option('become_user') or 'root'
if user != 'root':
cmdlist = [self.get_option('become_exe') or 'sudo']
user = self.get_option("become_user") or "root"
if user != "root":
cmdlist = [self.get_option("become_exe") or "sudo"]
# -i is required, because
# podman unshare should be executed in a login shell to avoid chdir permission errors
cmdlist.append('-iu %s' % user)
if self.get_option('become_pass'):
self.prompt = '[sudo podman unshare via ansible, key=%s] password:' % self._id
cmdlist.append("-iu %s" % user)
if self.get_option("become_pass"):
self.prompt = (
"[sudo podman unshare via ansible, key=%s] password:" % self._id
)
cmdlist.append('-p "%s"' % self.prompt)
cmdlist.append('-- %s' % becomecmd)
becomecmd = ' '.join(cmdlist)
cmdlist.append("-- %s" % becomecmd)
becomecmd = " ".join(cmdlist)
return ' '.join([becomecmd, self._build_success_command(cmd, shell)])
return " ".join([becomecmd, self._build_success_command(cmd, shell)])

View file

@ -7,11 +7,12 @@
#
# Written by: Tomas Tomecek (https://github.com/TomasTomecek)
from __future__ import (absolute_import, division, print_function)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
DOCUMENTATION = """
short_description: Interact with an existing buildah container
description:
- Run commands or put/fetch files to an existing container using buildah tool.
@ -39,7 +40,7 @@ DOCUMENTATION = '''
- name: ansible_user
# keyword:
# - name: remote_user
'''
"""
import os
import shlex
@ -61,7 +62,7 @@ class Connection(ConnectionBase):
"""
# String used to identify this Connection class from other classes
transport = 'containers.podman.buildah'
transport = "containers.podman.buildah"
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
@ -77,7 +78,9 @@ class Connection(ConnectionBase):
display.vvvv("Using buildah connection from collection")
def _set_user(self):
self._buildah(b"config", [b"--user=" + to_bytes(self.user, errors='surrogate_or_strict')])
self._buildah(
b"config", [b"--user=" + to_bytes(self.user, errors="surrogate_or_strict")]
)
def _buildah(self, cmd, cmd_args=None, in_data=None, outfile_stdout=None):
"""
@ -89,17 +92,17 @@ class Connection(ConnectionBase):
:param outfile_stdout: file for writing STDOUT to
:return: return code, stdout, stderr
"""
buildah_exec = 'buildah'
buildah_exec = "buildah"
local_cmd = [buildah_exec]
if isinstance(cmd, str):
local_cmd.append(cmd)
else:
local_cmd.extend(cmd)
if self.user and self.user != 'root':
if cmd == 'run':
if self.user and self.user != "root":
if cmd == "run":
local_cmd.extend(("--user", self.user))
elif cmd == 'copy':
elif cmd == "copy":
local_cmd.extend(("--chown", self.user))
local_cmd.append(self._container_id)
@ -109,23 +112,27 @@ class Connection(ConnectionBase):
else:
local_cmd.extend(cmd_args)
local_cmd = [to_bytes(i, errors='surrogate_or_strict')
for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
display.vvv("RUN %s" % (local_cmd,), host=self._container_id)
if outfile_stdout:
stdout_fd = open(outfile_stdout, "wb")
else:
stdout_fd = subprocess.PIPE
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
stdout=stdout_fd, stderr=subprocess.PIPE)
p = subprocess.Popen(
local_cmd,
shell=False,
stdin=subprocess.PIPE,
stdout=stdout_fd,
stderr=subprocess.PIPE,
)
stdout, stderr = p.communicate(input=in_data)
display.vvvv("STDOUT %s" % to_text(stdout))
display.vvvv("STDERR %s" % to_text(stderr))
display.vvvv("RC CODE %s" % p.returncode)
stdout = to_bytes(stdout, errors='surrogate_or_strict')
stderr = to_bytes(stderr, errors='surrogate_or_strict')
stdout = to_bytes(stdout, errors="surrogate_or_strict")
stderr = to_bytes(stderr, errors="surrogate_or_strict")
return p.returncode, stdout, stderr
def _connect(self):
@ -136,19 +143,26 @@ class Connection(ConnectionBase):
super(Connection, self)._connect()
rc, self._mount_point, stderr = self._buildah("mount")
if rc != 0:
display.v("Failed to mount container %s: %s" % (self._container_id, stderr.strip()))
display.v(
"Failed to mount container %s: %s"
% (self._container_id, stderr.strip())
)
else:
self._mount_point = self._mount_point.strip() + to_bytes(os.path.sep, errors='surrogate_or_strict')
display.vvvv("MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr))
self._mount_point = self._mount_point.strip() + to_bytes(
os.path.sep, errors="surrogate_or_strict"
)
display.vvvv(
"MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr)
)
self._connected = True
@ensure_connect
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run specified command in a running OCI container using buildah """
"""run specified command in a running OCI container using buildah"""
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
# shlex.split has a bug with text strings on Python-2.6 and can only handle text strings on Python-3
cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict'))
cmd_args_list = shlex.split(to_native(cmd, errors="surrogate_or_strict"))
rc, stdout, stderr = self._buildah("run", cmd_args_list, in_data)
@ -156,47 +170,51 @@ class Connection(ConnectionBase):
return rc, stdout, stderr
def put_file(self, in_path, out_path):
""" Place a local file located in 'in_path' inside container at 'out_path' """
"""Place a local file located in 'in_path' inside container at 'out_path'"""
super(Connection, self).put_file(in_path, out_path)
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._container_id)
if not self._mount_point or self.user:
rc, stdout, stderr = self._buildah(
"copy", [in_path, out_path])
rc, stdout, stderr = self._buildah("copy", [in_path, out_path])
if rc != 0:
raise AnsibleError(
"Failed to copy file from %s to %s in container %s\n%s" % (
in_path, out_path, self._container_id, stderr)
"Failed to copy file from %s to %s in container %s\n%s"
% (in_path, out_path, self._container_id, stderr)
)
else:
real_out_path = self._mount_point + to_bytes(out_path, errors='surrogate_or_strict')
real_out_path = self._mount_point + to_bytes(
out_path, errors="surrogate_or_strict"
)
shutil.copyfile(
to_bytes(in_path, errors='surrogate_or_strict'),
to_bytes(real_out_path, errors='surrogate_or_strict')
to_bytes(in_path, errors="surrogate_or_strict"),
to_bytes(real_out_path, errors="surrogate_or_strict"),
)
def fetch_file(self, in_path, out_path):
""" obtain file specified via 'in_path' from the container and place it at 'out_path' """
"""obtain file specified via 'in_path' from the container and place it at 'out_path'"""
super(Connection, self).fetch_file(in_path, out_path)
display.vvv("FETCH %s TO %s" %
(in_path, out_path), host=self._container_id)
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._container_id)
if not self._mount_point:
rc, stdout, stderr = self._buildah(
"run",
["cat", to_bytes(in_path, errors='surrogate_or_strict')],
outfile_stdout=out_path)
["cat", to_bytes(in_path, errors="surrogate_or_strict")],
outfile_stdout=out_path,
)
if rc != 0:
raise AnsibleError("Failed to fetch file from %s to %s from container %s\n%s" % (
in_path, out_path, self._container_id, stderr))
raise AnsibleError(
"Failed to fetch file from %s to %s from container %s\n%s"
% (in_path, out_path, self._container_id, stderr)
)
else:
real_in_path = self._mount_point + \
to_bytes(in_path, errors='surrogate_or_strict')
real_in_path = self._mount_point + to_bytes(
in_path, errors="surrogate_or_strict"
)
shutil.copyfile(
to_bytes(real_in_path, errors='surrogate_or_strict'),
to_bytes(out_path, errors='surrogate_or_strict')
to_bytes(real_in_path, errors="surrogate_or_strict"),
to_bytes(out_path, errors="surrogate_or_strict"),
)
def close(self):
""" unmount container's filesystem """
"""unmount container's filesystem"""
super(Connection, self).close()
rc, stdout, stderr = self._buildah("umount")
display.vvvv("RC %s STDOUT %r STDERR %r" % (rc, stdout, stderr))

View file

@ -7,10 +7,11 @@
#
# Written by: Tomas Tomecek (https://github.com/TomasTomecek)
from __future__ import (absolute_import, division, print_function)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
DOCUMENTATION = """
author: Tomas Tomecek (@TomasTomecek)
name: podman
short_description: Interact with an existing podman container
@ -56,7 +57,7 @@ DOCUMENTATION = '''
- name: ansible_podman_executable
env:
- name: ANSIBLE_PODMAN_EXECUTABLE
'''
"""
import os
import shlex
@ -79,7 +80,7 @@ class Connection(ConnectionBase):
"""
# String used to identify this Connection class from other classes
transport = 'containers.podman.podman'
transport = "containers.podman.podman"
# We know that pipelining does not work with podman. Do not enable it, or
# users will start containers and fail to connect to them.
has_pipelining = False
@ -104,7 +105,7 @@ class Connection(ConnectionBase):
:param use_container_id: whether to append the container ID to the command
:return: return code, stdout, stderr
"""
podman_exec = self.get_option('podman_executable')
podman_exec = self.get_option("podman_executable")
try:
podman_cmd = get_bin_path(podman_exec)
except ValueError:
@ -112,11 +113,12 @@ class Connection(ConnectionBase):
if not podman_cmd:
raise AnsibleError("%s command not found in PATH" % podman_exec)
local_cmd = [podman_cmd]
if self.get_option('podman_extra_args'):
if self.get_option("podman_extra_args"):
local_cmd += shlex.split(
to_native(
self.get_option('podman_extra_args'),
errors='surrogate_or_strict'))
self.get_option("podman_extra_args"), errors="surrogate_or_strict"
)
)
if isinstance(cmd, str):
local_cmd.append(cmd)
else:
@ -126,18 +128,23 @@ class Connection(ConnectionBase):
local_cmd.append(self._container_id)
if cmd_args:
local_cmd += cmd_args
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
display.vvv("RUN %s" % (local_cmd,), host=self._container_id)
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
local_cmd,
shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = p.communicate(input=in_data)
display.vvvvv("STDOUT %s" % stdout)
display.vvvvv("STDERR %s" % stderr)
display.vvvvv("RC CODE %s" % p.returncode)
stdout = to_bytes(stdout, errors='surrogate_or_strict')
stderr = to_bytes(stderr, errors='surrogate_or_strict')
stdout = to_bytes(stdout, errors="surrogate_or_strict")
stderr = to_bytes(stderr, errors="surrogate_or_strict")
return p.returncode, stdout, stderr
def _connect(self):
@ -148,22 +155,30 @@ class Connection(ConnectionBase):
super(Connection, self)._connect()
rc, self._mount_point, stderr = self._podman("mount")
if rc != 0:
display.vvvv("Failed to mount container %s: %s" % (self._container_id, stderr.strip()))
display.vvvv(
"Failed to mount container %s: %s"
% (self._container_id, stderr.strip())
)
elif not os.listdir(self._mount_point.strip()):
display.vvvv("Failed to mount container with CGroups2: empty dir %s" % self._mount_point.strip())
display.vvvv(
"Failed to mount container with CGroups2: empty dir %s"
% self._mount_point.strip()
)
self._mount_point = None
else:
self._mount_point = self._mount_point.strip()
display.vvvvv("MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr))
display.vvvvv(
"MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr)
)
self._connected = True
@ensure_connect
def exec_command(self, cmd, in_data=None, sudoable=False):
""" run specified command in a running OCI container using podman """
"""run specified command in a running OCI container using podman"""
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
# shlex.split has a bug with text strings on Python-2.6 and can only handle text strings on Python-3
cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict'))
cmd_args_list = shlex.split(to_native(cmd, errors="surrogate_or_strict"))
exec_args_list = ["exec"]
if self.user:
exec_args_list.extend(("--user", self.user))
@ -174,56 +189,70 @@ class Connection(ConnectionBase):
return rc, stdout, stderr
def put_file(self, in_path, out_path):
""" Place a local file located in 'in_path' inside container at 'out_path' """
"""Place a local file located in 'in_path' inside container at 'out_path'"""
super(Connection, self).put_file(in_path, out_path)
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._container_id)
if not self._mount_point or self.user:
rc, stdout, stderr = self._podman(
"cp", [in_path, self._container_id + ":" + out_path], use_container_id=False
"cp",
[in_path, self._container_id + ":" + out_path],
use_container_id=False,
)
if rc != 0:
rc, stdout, stderr = self._podman(
"cp", ["--pause=false", in_path, self._container_id + ":" + out_path], use_container_id=False
"cp",
["--pause=false", in_path, self._container_id + ":" + out_path],
use_container_id=False,
)
if rc != 0:
raise AnsibleError(
"Failed to copy file from %s to %s in container %s\n%s" % (
in_path, out_path, self._container_id, stderr)
"Failed to copy file from %s to %s in container %s\n%s"
% (in_path, out_path, self._container_id, stderr)
)
if self.user:
rc, stdout, stderr = self._podman(
"exec", ["chown", self.user, out_path])
"exec", ["chown", self.user, out_path]
)
if rc != 0:
raise AnsibleError(
"Failed to chown file %s for user %s in container %s\n%s" % (
out_path, self.user, self._container_id, stderr)
"Failed to chown file %s for user %s in container %s\n%s"
% (out_path, self.user, self._container_id, stderr)
)
else:
real_out_path = self._mount_point + to_bytes(out_path, errors='surrogate_or_strict')
real_out_path = self._mount_point + to_bytes(
out_path, errors="surrogate_or_strict"
)
shutil.copyfile(
to_bytes(in_path, errors='surrogate_or_strict'),
to_bytes(real_out_path, errors='surrogate_or_strict')
to_bytes(in_path, errors="surrogate_or_strict"),
to_bytes(real_out_path, errors="surrogate_or_strict"),
)
def fetch_file(self, in_path, out_path):
""" obtain file specified via 'in_path' from the container and place it at 'out_path' """
"""obtain file specified via 'in_path' from the container and place it at 'out_path'"""
super(Connection, self).fetch_file(in_path, out_path)
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._container_id)
if not self._mount_point:
rc, stdout, stderr = self._podman(
"cp", [self._container_id + ":" + in_path, out_path], use_container_id=False)
"cp",
[self._container_id + ":" + in_path, out_path],
use_container_id=False,
)
if rc != 0:
raise AnsibleError("Failed to fetch file from %s to %s from container %s\n%s" % (
in_path, out_path, self._container_id, stderr))
raise AnsibleError(
"Failed to fetch file from %s to %s from container %s\n%s"
% (in_path, out_path, self._container_id, stderr)
)
else:
real_in_path = self._mount_point + to_bytes(in_path, errors='surrogate_or_strict')
real_in_path = self._mount_point + to_bytes(
in_path, errors="surrogate_or_strict"
)
shutil.copyfile(
to_bytes(real_in_path, errors='surrogate_or_strict'),
to_bytes(out_path, errors='surrogate_or_strict')
to_bytes(real_in_path, errors="surrogate_or_strict"),
to_bytes(out_path, errors="surrogate_or_strict"),
)
def close(self):
""" unmount container's filesystem """
"""unmount container's filesystem"""
super(Connection, self).close()
# we actually don't need to unmount since the container is mounted anyway
# rc, stdout, stderr = self._podman("umount")

View file

@ -2,6 +2,7 @@
# 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
import json
@ -9,37 +10,45 @@ import os
import shutil
from ansible.module_utils.six import raise_from
try:
from ansible.module_utils.compat.version import LooseVersion # noqa: F401
except ImportError:
try:
from distutils.version import LooseVersion # noqa: F401
except ImportError as exc:
raise_from(ImportError('To use this plugin or module with ansible-core'
' < 2.11, you need to use Python < 3.12 with '
'distutils.version present'), exc)
raise_from(
ImportError(
"To use this plugin or module with ansible-core"
" < 2.11, you need to use Python < 3.12 with "
"distutils.version present"
),
exc,
)
ARGUMENTS_OPTS_DICT = {
'--attach': ['--attach', '-a'],
'--cpu-shares': ['--cpu-shares', '-c'],
'--detach': ['--detach', '-d'],
'--env': ['--env', '-e'],
'--hostname': ['--hostname', '-h'],
'--interactive': ['--interactive', '-i'],
'--label': ['--label', '-l'],
'--memory': ['--memory', '-m'],
'--network': ['--network', '--net'],
'--publish': ['--publish', '-p'],
'--publish-all': ['--publish-all', '-P'],
'--quiet': ['--quiet', '-q'],
'--tty': ['--tty', '-t'],
'--user': ['--user', '-u'],
'--volume': ['--volume', '-v'],
'--workdir': ['--workdir', '-w'],
"--attach": ["--attach", "-a"],
"--cpu-shares": ["--cpu-shares", "-c"],
"--detach": ["--detach", "-d"],
"--env": ["--env", "-e"],
"--hostname": ["--hostname", "-h"],
"--interactive": ["--interactive", "-i"],
"--label": ["--label", "-l"],
"--memory": ["--memory", "-m"],
"--network": ["--network", "--net"],
"--publish": ["--publish", "-p"],
"--publish-all": ["--publish-all", "-P"],
"--quiet": ["--quiet", "-q"],
"--tty": ["--tty", "-t"],
"--user": ["--user", "-u"],
"--volume": ["--volume", "-v"],
"--workdir": ["--workdir", "-w"],
}
def run_podman_command(module, executable='podman', args=None, expected_rc=0, ignore_errors=False):
def run_podman_command(
module, executable="podman", args=None, expected_rc=0, ignore_errors=False
):
if not isinstance(executable, list):
command = [executable]
if args is not None:
@ -47,77 +56,94 @@ def run_podman_command(module, executable='podman', args=None, expected_rc=0, ig
rc, out, err = module.run_command(command)
if not ignore_errors and rc != expected_rc:
module.fail_json(
msg='Failed to run {command} {args}: {err}'.format(
command=command, args=args, err=err))
msg="Failed to run {command} {args}: {err}".format(
command=command, args=args, err=err
)
)
return rc, out, err
def run_generate_systemd_command(module, module_params, name, version):
"""Generate systemd unit file."""
command = [module_params['executable'], 'generate', 'systemd',
name, '--format', 'json']
sysconf = module_params['generate_systemd']
gt4ver = LooseVersion(version) >= LooseVersion('4.0.0')
if sysconf.get('restart_policy'):
if sysconf.get('restart_policy') not in [
"no", "on-success", "on-failure", "on-abnormal", "on-watchdog",
"on-abort", "always"]:
command = [
module_params["executable"],
"generate",
"systemd",
name,
"--format",
"json",
]
sysconf = module_params["generate_systemd"]
gt4ver = LooseVersion(version) >= LooseVersion("4.0.0")
if sysconf.get("restart_policy"):
if sysconf.get("restart_policy") not in [
"no",
"on-success",
"on-failure",
"on-abnormal",
"on-watchdog",
"on-abort",
"always",
]:
module.fail_json(
'Restart policy for systemd unit file is "%s" and must be one of: '
'"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", or "always"' %
sysconf.get('restart_policy'))
command.extend([
'--restart-policy',
sysconf['restart_policy']])
if sysconf.get('restart_sec') is not None:
command.extend(['--restart-sec=%s' % sysconf['restart_sec']])
if (sysconf.get('stop_timeout') is not None) or (sysconf.get('time') is not None):
'"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", or "always"'
% sysconf.get("restart_policy")
)
command.extend(["--restart-policy", sysconf["restart_policy"]])
if sysconf.get("restart_sec") is not None:
command.extend(["--restart-sec=%s" % sysconf["restart_sec"]])
if (sysconf.get("stop_timeout") is not None) or (sysconf.get("time") is not None):
# Select correct parameter name based on version
arg_name = 'stop-timeout' if gt4ver else 'time'
arg_value = sysconf.get('stop_timeout') if sysconf.get('stop_timeout') is not None else sysconf.get('time')
command.extend(['--%s=%s' % (arg_name, arg_value)])
if sysconf.get('start_timeout') is not None:
command.extend(['--start-timeout=%s' % sysconf['start_timeout']])
if sysconf.get('no_header'):
command.extend(['--no-header'])
if sysconf.get('names', True):
command.extend(['--name'])
arg_name = "stop-timeout" if gt4ver else "time"
arg_value = (
sysconf.get("stop_timeout")
if sysconf.get("stop_timeout") is not None
else sysconf.get("time")
)
command.extend(["--%s=%s" % (arg_name, arg_value)])
if sysconf.get("start_timeout") is not None:
command.extend(["--start-timeout=%s" % sysconf["start_timeout"]])
if sysconf.get("no_header"):
command.extend(["--no-header"])
if sysconf.get("names", True):
command.extend(["--name"])
if sysconf.get("new"):
command.extend(["--new"])
if sysconf.get('container_prefix') is not None:
command.extend(['--container-prefix=%s' % sysconf['container_prefix']])
if sysconf.get('pod_prefix') is not None:
command.extend(['--pod-prefix=%s' % sysconf['pod_prefix']])
if sysconf.get('separator') is not None:
command.extend(['--separator=%s' % sysconf['separator']])
if sysconf.get('after') is not None:
if sysconf.get("container_prefix") is not None:
command.extend(["--container-prefix=%s" % sysconf["container_prefix"]])
if sysconf.get("pod_prefix") is not None:
command.extend(["--pod-prefix=%s" % sysconf["pod_prefix"]])
if sysconf.get("separator") is not None:
command.extend(["--separator=%s" % sysconf["separator"]])
if sysconf.get("after") is not None:
sys_after = sysconf['after']
sys_after = sysconf["after"]
if isinstance(sys_after, str):
sys_after = [sys_after]
for after in sys_after:
command.extend(['--after=%s' % after])
if sysconf.get('wants') is not None:
sys_wants = sysconf['wants']
command.extend(["--after=%s" % after])
if sysconf.get("wants") is not None:
sys_wants = sysconf["wants"]
if isinstance(sys_wants, str):
sys_wants = [sys_wants]
for want in sys_wants:
command.extend(['--wants=%s' % want])
if sysconf.get('requires') is not None:
sys_req = sysconf['requires']
command.extend(["--wants=%s" % want])
if sysconf.get("requires") is not None:
sys_req = sysconf["requires"]
if isinstance(sys_req, str):
sys_req = [sys_req]
for require in sys_req:
command.extend(['--requires=%s' % require])
for param in ['after', 'wants', 'requires']:
command.extend(["--requires=%s" % require])
for param in ["after", "wants", "requires"]:
if sysconf.get(param) is not None and not gt4ver:
module.fail_json(msg="Systemd parameter '%s' is supported from "
"podman version 4 only! Current version is %s" % (
param, version))
module.fail_json(
msg="Systemd parameter '%s' is supported from "
"podman version 4 only! Current version is %s" % (param, version)
)
if module.params['debug'] or module_params['debug']:
module.log("PODMAN-CONTAINER-DEBUG: systemd command: %s" %
" ".join(command))
if module.params["debug"] or module_params["debug"]:
module.log("PODMAN-CONTAINER-DEBUG: systemd command: %s" % " ".join(command))
rc, systemd, err = module.run_command(command)
return rc, systemd, err
@ -125,14 +151,16 @@ def run_generate_systemd_command(module, module_params, name, version):
def compare_systemd_file_content(file_path, file_content):
if not os.path.exists(file_path):
# File does not exist, so all lines in file_content are different
return '', file_content
return "", file_content
# Read the file
with open(file_path, 'r') as unit_file:
with open(file_path, "r") as unit_file:
current_unit_file_content = unit_file.read()
# Function to remove comments from file content
def remove_comments(content):
return "\n".join([line for line in content.splitlines() if not line.startswith('#')])
return "\n".join(
[line for line in content.splitlines() if not line.startswith("#")]
)
# Remove comments from both file contents before comparison
current_unit_file_content_nocmnt = remove_comments(current_unit_file_content)
@ -141,111 +169,145 @@ def compare_systemd_file_content(file_path, file_content):
return None
# Get the different lines between the two contents
diff_in_file = [line
for line in unit_content_nocmnt.splitlines()
if line not in current_unit_file_content_nocmnt.splitlines()]
diff_in_string = [line
for line in current_unit_file_content_nocmnt.splitlines()
if line not in unit_content_nocmnt.splitlines()]
diff_in_file = [
line
for line in unit_content_nocmnt.splitlines()
if line not in current_unit_file_content_nocmnt.splitlines()
]
diff_in_string = [
line
for line in current_unit_file_content_nocmnt.splitlines()
if line not in unit_content_nocmnt.splitlines()
]
return diff_in_string, diff_in_file
def generate_systemd(module, module_params, name, version):
result = {
'changed': False,
'systemd': {},
'diff': {},
"changed": False,
"systemd": {},
"diff": {},
}
sysconf = module_params['generate_systemd']
rc, systemd, err = run_generate_systemd_command(module, module_params, name, version)
sysconf = module_params["generate_systemd"]
rc, systemd, err = run_generate_systemd_command(
module, module_params, name, version
)
if rc != 0:
module.log(
"PODMAN-CONTAINER-DEBUG: Error generating systemd: %s" % err)
module.log("PODMAN-CONTAINER-DEBUG: Error generating systemd: %s" % err)
if sysconf:
module.fail_json(msg="Error generating systemd: %s" % err)
return result
else:
try:
data = json.loads(systemd)
result['systemd'] = data
if sysconf.get('path'):
full_path = os.path.expanduser(sysconf['path'])
result["systemd"] = data
if sysconf.get("path"):
full_path = os.path.expanduser(sysconf["path"])
if not os.path.exists(full_path):
os.makedirs(full_path)
result['changed'] = True
result["changed"] = True
if not os.path.isdir(full_path):
module.fail_json("Path %s is not a directory! "
"Can not save systemd unit files there!"
% full_path)
module.fail_json(
"Path %s is not a directory! "
"Can not save systemd unit files there!" % full_path
)
for file_name, file_content in data.items():
file_name += ".service"
if not os.path.exists(os.path.join(full_path, file_name)):
result['changed'] = True
if result['diff'].get('before') is None:
result['diff'] = {'before': {}, 'after': {}}
result['diff']['before'].update(
{'systemd_{file_name}.service'.format(file_name=file_name): ''})
result['diff']['after'].update(
{'systemd_{file_name}.service'.format(file_name=file_name): file_content})
result["changed"] = True
if result["diff"].get("before") is None:
result["diff"] = {"before": {}, "after": {}}
result["diff"]["before"].update(
{
"systemd_{file_name}.service".format(
file_name=file_name
): ""
}
)
result["diff"]["after"].update(
{
"systemd_{file_name}.service".format(
file_name=file_name
): file_content
}
)
else:
diff_ = compare_systemd_file_content(os.path.join(full_path, file_name), file_content)
diff_ = compare_systemd_file_content(
os.path.join(full_path, file_name), file_content
)
if diff_:
result['changed'] = True
if result['diff'].get('before') is None:
result['diff'] = {'before': {}, 'after': {}}
result['diff']['before'].update(
{'systemd_{file_name}.service'.format(file_name=file_name): "\n".join(diff_[0])})
result['diff']['after'].update(
{'systemd_{file_name}.service'.format(file_name=file_name): "\n".join(diff_[1])})
with open(os.path.join(full_path, file_name), 'w') as f:
result["changed"] = True
if result["diff"].get("before") is None:
result["diff"] = {"before": {}, "after": {}}
result["diff"]["before"].update(
{
"systemd_{file_name}.service".format(
file_name=file_name
): "\n".join(diff_[0])
}
)
result["diff"]["after"].update(
{
"systemd_{file_name}.service".format(
file_name=file_name
): "\n".join(diff_[1])
}
)
with open(os.path.join(full_path, file_name), "w") as f:
f.write(file_content)
diff_before = "\n".join(
["{j} - {k}".format(j=j, k=k)
for j, k in result['diff'].get('before', {}).items() if 'PIDFile' not in k]).strip()
[
"{j} - {k}".format(j=j, k=k)
for j, k in result["diff"].get("before", {}).items()
if "PIDFile" not in k
]
).strip()
diff_after = "\n".join(
["{j} - {k}".format(j=j, k=k)
for j, k in result['diff'].get('after', {}).items() if 'PIDFile' not in k]).strip()
[
"{j} - {k}".format(j=j, k=k)
for j, k in result["diff"].get("after", {}).items()
if "PIDFile" not in k
]
).strip()
if diff_before or diff_after:
result['diff']['before'] = diff_before + "\n"
result['diff']['after'] = diff_after + "\n"
result["diff"]["before"] = diff_before + "\n"
result["diff"]["after"] = diff_after + "\n"
else:
result['diff'] = {}
result["diff"] = {}
return result
except Exception as e:
module.log(
"PODMAN-CONTAINER-DEBUG: Error writing systemd: %s" % e)
module.log("PODMAN-CONTAINER-DEBUG: Error writing systemd: %s" % e)
if sysconf:
module.fail_json(msg="Error writing systemd: %s" % e)
return result
def delete_systemd(module, module_params, name, version):
sysconf = module_params['generate_systemd']
if not sysconf.get('path'):
sysconf = module_params["generate_systemd"]
if not sysconf.get("path"):
# We don't know where systemd files are located, nothing to delete
module.log(
"PODMAN-CONTAINER-DEBUG: Not deleting systemd file - no path!")
module.log("PODMAN-CONTAINER-DEBUG: Not deleting systemd file - no path!")
return
rc, systemd, err = run_generate_systemd_command(module, module_params, name, version)
rc, systemd, err = run_generate_systemd_command(
module, module_params, name, version
)
if rc != 0:
module.log(
"PODMAN-CONTAINER-DEBUG: Error generating systemd: %s" % err)
module.log("PODMAN-CONTAINER-DEBUG: Error generating systemd: %s" % err)
return
else:
try:
data = json.loads(systemd)
for file_name in data.keys():
file_name += ".service"
full_dir_path = os.path.expanduser(sysconf['path'])
full_dir_path = os.path.expanduser(sysconf["path"])
file_path = os.path.join(full_dir_path, file_name)
if os.path.exists(file_path):
os.unlink(file_path)
return
except Exception as e:
module.log(
"PODMAN-CONTAINER-DEBUG: Error deleting systemd: %s" % e)
module.log("PODMAN-CONTAINER-DEBUG: Error deleting systemd: %s" % e)
return
@ -306,12 +368,12 @@ _signal_map = {
"VTALRM": 26,
"WINCH": 28,
"XCPU": 24,
"XFSZ": 25
"XFSZ": 25,
}
for i in range(1, _signal_map['RTMAX'] - _signal_map['RTMIN'] + 1):
_signal_map['RTMIN+{0}'.format(i)] = _signal_map['RTMIN'] + i
_signal_map['RTMAX-{0}'.format(i)] = _signal_map['RTMAX'] - i
for i in range(1, _signal_map["RTMAX"] - _signal_map["RTMIN"] + 1):
_signal_map["RTMIN+{0}".format(i)] = _signal_map["RTMIN"] + i
_signal_map["RTMAX-{0}".format(i)] = _signal_map["RTMAX"] - i
def normalize_signal(signal_name_or_number):
@ -320,7 +382,7 @@ def normalize_signal(signal_name_or_number):
return signal_name_or_number
else:
signal_name = signal_name_or_number.upper()
if signal_name.startswith('SIG'):
if signal_name.startswith("SIG"):
signal_name = signal_name[3:]
if signal_name not in _signal_map:
raise RuntimeError("Unknown signal '{0}'".format(signal_name_or_number))
@ -328,13 +390,15 @@ def normalize_signal(signal_name_or_number):
def get_podman_version(module, fail=True):
executable = module.params['executable'] if module.params['executable'] else 'podman'
rc, out, err = module.run_command(
[executable, b'--version'])
executable = (
module.params["executable"] if module.params["executable"] else "podman"
)
rc, out, err = module.run_command([executable, b"--version"])
if rc != 0 or not out or "version" not in out:
if fail:
module.fail_json(msg="'%s --version' run failed! Error: %s" %
(executable, err))
module.fail_json(
msg="'%s --version' run failed! Error: %s" % (executable, err)
)
return None
return out.split("version")[1].strip()
@ -409,16 +473,23 @@ def diff_generic(params, info_config, module_arg, cmd_arg, boolean_type=False):
if before:
before = ",".join(sorted([str(i).lower() for i in before]))
else:
before = ''
before = ""
elif isinstance(after, dict):
if module_arg == "log_opt" and "max_size" in after:
after["max-size"] = after.pop("max_size")
after = ",".join(sorted(
[str(k).lower() + "=" + str(v).lower() for k, v in after.items() if v is not None]))
after = ",".join(
sorted(
[
str(k).lower() + "=" + str(v).lower()
for k, v in after.items()
if v is not None
]
)
)
if before:
before = ",".join(sorted([j.lower() for j in before]))
else:
before = ''
before = ""
elif isinstance(after, bool):
after = str(after).capitalize()
if before is not None:

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,12 +2,15 @@
# 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
import os
import shlex
from ansible_collections.containers.podman.plugins.module_utils.podman.common import compare_systemd_file_content
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
compare_systemd_file_content,
)
QUADLET_ROOT_PATH = "/etc/containers/systemd/"
QUADLET_NON_ROOT_PATH = "~/.config/containers/systemd/"
@ -52,102 +55,107 @@ class Quadlet:
Construct the quadlet content as a string.
"""
custom_user_options = self.custom_params.get("quadlet_options")
custom_text = "\n" + "\n".join(custom_user_options) if custom_user_options else ""
return f"[{self.section}]\n" + "\n".join(
f"{key}={value}" for key, value in self.dict_params
) + custom_text + "\n"
custom_text = (
"\n" + "\n".join(custom_user_options) if custom_user_options else ""
)
return (
f"[{self.section}]\n"
+ "\n".join(f"{key}={value}" for key, value in self.dict_params)
+ custom_text
+ "\n"
)
def write_to_file(self, path: str):
"""
Write the quadlet content to a file at the specified path.
"""
content = self.create_quadlet_content()
with open(path, 'w') as file:
with open(path, "w") as file:
file.write(content)
class ContainerQuadlet(Quadlet):
param_map = {
'cap_add': 'AddCapability',
'device': 'AddDevice',
'annotation': 'Annotation',
'name': 'ContainerName',
"cap_add": "AddCapability",
"device": "AddDevice",
"annotation": "Annotation",
"name": "ContainerName",
# the following are not implemented yet in Podman module
'AutoUpdate': 'AutoUpdate',
'ContainersConfModule': 'ContainersConfModule',
"AutoUpdate": "AutoUpdate",
"ContainersConfModule": "ContainersConfModule",
# end of not implemented yet
'dns': 'DNS',
'dns_option': 'DNSOption',
'dns_search': 'DNSSearch',
'cap_drop': 'DropCapability',
'cgroups': 'CgroupsMode',
'entrypoint': 'Entrypoint',
'env': 'Environment',
'env_file': 'EnvironmentFile',
'env_host': 'EnvironmentHost',
'etc_hosts': 'AddHost',
'command': 'Exec',
'expose': 'ExposeHostPort',
'gidmap': 'GIDMap',
'global_args': 'GlobalArgs',
'group': 'Group', # Does not exist in module parameters
'group_add': 'GroupAdd',
'healthcheck': 'HealthCmd',
'healthcheck_interval': 'HealthInterval',
'healthcheck_failure_action': 'HealthOnFailure',
'healthcheck_retries': 'HealthRetries',
'healthcheck_start_period': 'HealthStartPeriod',
'healthcheck_timeout': 'HealthTimeout',
'health_startup_cmd': 'HealthStartupCmd',
'health_startup_interval': 'HealthStartupInterval',
'health_startup_retries': 'HealthStartupRetries',
'health_startup_success': 'HealthStartupSuccess',
'health_startup_timeout': 'HealthStartupTimeout',
'hostname': 'HostName',
'image': 'Image',
'ip': 'IP',
'ip6': 'IP6',
'label': 'Label',
'log_driver': 'LogDriver',
'log_opt': 'LogOpt',
"dns": "DNS",
"dns_option": "DNSOption",
"dns_search": "DNSSearch",
"cap_drop": "DropCapability",
"cgroups": "CgroupsMode",
"entrypoint": "Entrypoint",
"env": "Environment",
"env_file": "EnvironmentFile",
"env_host": "EnvironmentHost",
"etc_hosts": "AddHost",
"command": "Exec",
"expose": "ExposeHostPort",
"gidmap": "GIDMap",
"global_args": "GlobalArgs",
"group": "Group", # Does not exist in module parameters
"group_add": "GroupAdd",
"healthcheck": "HealthCmd",
"healthcheck_interval": "HealthInterval",
"healthcheck_failure_action": "HealthOnFailure",
"healthcheck_retries": "HealthRetries",
"healthcheck_start_period": "HealthStartPeriod",
"healthcheck_timeout": "HealthTimeout",
"health_startup_cmd": "HealthStartupCmd",
"health_startup_interval": "HealthStartupInterval",
"health_startup_retries": "HealthStartupRetries",
"health_startup_success": "HealthStartupSuccess",
"health_startup_timeout": "HealthStartupTimeout",
"hostname": "HostName",
"image": "Image",
"ip": "IP",
"ip6": "IP6",
"label": "Label",
"log_driver": "LogDriver",
"log_opt": "LogOpt",
"Mask": "Mask", # add it in security_opt
'mount': 'Mount',
'network': 'Network',
'network_aliases': 'NetworkAlias',
'no_new_privileges': 'NoNewPrivileges',
'sdnotify': 'Notify',
'pids_limit': 'PidsLimit',
'pod': 'Pod',
'publish': 'PublishPort',
"mount": "Mount",
"network": "Network",
"network_aliases": "NetworkAlias",
"no_new_privileges": "NoNewPrivileges",
"sdnotify": "Notify",
"pids_limit": "PidsLimit",
"pod": "Pod",
"publish": "PublishPort",
"pull": "Pull",
'read_only': 'ReadOnly',
'read_only_tmpfs': 'ReadOnlyTmpfs',
'rootfs': 'Rootfs',
'init': 'RunInit',
'SeccompProfile': 'SeccompProfile',
'secrets': 'Secret',
"read_only": "ReadOnly",
"read_only_tmpfs": "ReadOnlyTmpfs",
"rootfs": "Rootfs",
"init": "RunInit",
"SeccompProfile": "SeccompProfile",
"secrets": "Secret",
# All these are in security_opt
'SecurityLabelDisable': 'SecurityLabelDisable',
'SecurityLabelFileType': 'SecurityLabelFileType',
'SecurityLabelLevel': 'SecurityLabelLevel',
'SecurityLabelNested': 'SecurityLabelNested',
'SecurityLabelType': 'SecurityLabelType',
'shm_size': 'ShmSize',
'stop_signal': 'StopSignal',
'stop_timeout': 'StopTimeout',
'subgidname': 'SubGIDMap',
'subuidname': 'SubUIDMap',
'sysctl': 'Sysctl',
'timezone': 'Timezone',
'tmpfs': 'Tmpfs',
'uidmap': 'UIDMap',
'ulimit': 'Ulimit',
'Unmask': 'Unmask', # --security-opt unmask=ALL
'user': 'User',
'userns': 'UserNS',
'volume': 'Volume',
'workdir': 'WorkingDir',
'podman_args': 'PodmanArgs',
"SecurityLabelDisable": "SecurityLabelDisable",
"SecurityLabelFileType": "SecurityLabelFileType",
"SecurityLabelLevel": "SecurityLabelLevel",
"SecurityLabelNested": "SecurityLabelNested",
"SecurityLabelType": "SecurityLabelType",
"shm_size": "ShmSize",
"stop_signal": "StopSignal",
"stop_timeout": "StopTimeout",
"subgidname": "SubGIDMap",
"subuidname": "SubUIDMap",
"sysctl": "Sysctl",
"timezone": "Timezone",
"tmpfs": "Tmpfs",
"uidmap": "UIDMap",
"ulimit": "Ulimit",
"Unmask": "Unmask", # --security-opt unmask=ALL
"user": "User",
"userns": "UserNS",
"volume": "Volume",
"workdir": "WorkingDir",
"podman_args": "PodmanArgs",
}
def __init__(self, params: dict):
@ -159,27 +167,36 @@ class ContainerQuadlet(Quadlet):
"""
# Work on params in params_map and convert them to a right form
if params["annotation"]:
params['annotation'] = ["%s=%s" %
(k, v) for k, v in params['annotation'].items()]
params["annotation"] = [
"%s=%s" % (k, v) for k, v in params["annotation"].items()
]
if params["cap_add"]:
params["cap_add"] = " ".join(params["cap_add"])
if params["cap_drop"]:
params["cap_drop"] = " ".join(params["cap_drop"])
if params["command"]:
params["command"] = (" ".join([str(j) for j in params["command"]])
if isinstance(params["command"], list)
else params["command"])
params["command"] = (
" ".join([str(j) for j in params["command"]])
if isinstance(params["command"], list)
else params["command"]
)
if params["label"]:
params["label"] = [shlex.quote("%s=%s" % (k, v)) for k, v in params["label"].items()]
params["label"] = [
shlex.quote("%s=%s" % (k, v)) for k, v in params["label"].items()
]
if params["env"]:
params["env"] = [shlex.quote("%s=%s" % (k, v)) for k, v in params["env"].items()]
params["env"] = [
shlex.quote("%s=%s" % (k, v)) for k, v in params["env"].items()
]
if params["rootfs"]:
params["rootfs"] = params["image"]
params["image"] = None
if params["sysctl"]:
params["sysctl"] = ["%s=%s" % (k, v) for k, v in params["sysctl"].items()]
if params["tmpfs"]:
params["tmpfs"] = ["%s:%s" % (k, v) if v else k for k, v in params["tmpfs"].items()]
params["tmpfs"] = [
"%s:%s" % (k, v) if v else k for k, v in params["tmpfs"].items()
]
# Work on params which are not in the param_map but can be calculated
params["global_args"] = []
@ -208,8 +225,14 @@ class ContainerQuadlet(Quadlet):
if params["blkio_weight"]:
params["podman_args"].append(f"--blkio-weight {params['blkio_weight']}")
if params["blkio_weight_device"]:
params["podman_args"].append(" ".join([
f"--blkio-weight-device {':'.join(blkio)}" for blkio in params["blkio_weight_device"].items()]))
params["podman_args"].append(
" ".join(
[
f"--blkio-weight-device {':'.join(blkio)}"
for blkio in params["blkio_weight_device"].items()
]
)
)
if params["cgroupns"]:
params["podman_args"].append(f"--cgroupns {params['cgroupns']}")
if params["cgroup_conf"]:
@ -242,7 +265,9 @@ class ContainerQuadlet(Quadlet):
if params["decryption_key"]:
params["podman_args"].append(f"--decryption-key {params['decryption_key']}")
if params["device_cgroup_rule"]:
params["podman_args"].append(f"--device-cgroup-rule {params['device_cgroup_rule']}")
params["podman_args"].append(
f"--device-cgroup-rule {params['device_cgroup_rule']}"
)
if params["device_read_bps"]:
for i in params["device_read_bps"]:
params["podman_args"].append(f"--device-read-bps {i}")
@ -256,7 +281,9 @@ class ContainerQuadlet(Quadlet):
for i in params["device_write_iops"]:
params["podman_args"].append(f"--device-write-iops {i}")
if params["etc_hosts"]:
params['etc_hosts'] = ["%s:%s" % (k, v) for k, v in params['etc_hosts'].items()]
params["etc_hosts"] = [
"%s:%s" % (k, v) for k, v in params["etc_hosts"].items()
]
if params["env_merge"]:
for k, v in params["env_merge"].items():
params["podman_args"].append(f"--env {k}={v}")
@ -287,24 +314,32 @@ class ContainerQuadlet(Quadlet):
params["podman_args"].append(f"--label-file {params['label_file']}")
if params["log_opt"]:
params["log_opt"] = [
"%s=%s" % (k.replace('max_size', 'max-size'), v)
for k, v in params['log_opt'].items() if v is not None]
"%s=%s" % (k.replace("max_size", "max-size"), v)
for k, v in params["log_opt"].items()
if v is not None
]
if params["mac_address"]:
params["podman_args"].append(f"--mac-address {params['mac_address']}")
if params["memory"]:
params["podman_args"].append(f"--memory {params['memory']}")
if params["memory_reservation"]:
params["podman_args"].append(f"--memory-reservation {params['memory_reservation']}")
params["podman_args"].append(
f"--memory-reservation {params['memory_reservation']}"
)
if params["memory_swap"]:
params["podman_args"].append(f"--memory-swap {params['memory_swap']}")
if params["memory_swappiness"]:
params["podman_args"].append(f"--memory-swappiness {params['memory_swappiness']}")
params["podman_args"].append(
f"--memory-swappiness {params['memory_swappiness']}"
)
if params["no_healthcheck"]:
params["podman_args"].append("--no-healthcheck")
if params["no_hosts"] is not None:
params["podman_args"].append(f"--no-hosts={params['no_hosts']}")
if params["oom_kill_disable"]:
params["podman_args"].append(f"--oom-kill-disable={params['oom_kill_disable']}")
params["podman_args"].append(
f"--oom-kill-disable={params['oom_kill_disable']}"
)
if params["oom_score_adj"]:
params["podman_args"].append(f"--oom-score-adj {params['oom_score_adj']}")
if params["os"]:
@ -350,7 +385,9 @@ class ContainerQuadlet(Quadlet):
for security_opt in params["security_opt"]:
params["podman_args"].append(f"--security-opt {security_opt}")
if params["shm_size_systemd"]:
params["podman_args"].append(f"--shm-size-systemd {params['shm_size_systemd']}")
params["podman_args"].append(
f"--shm-size-systemd {params['shm_size_systemd']}"
)
if params["sig_proxy"]:
params["podman_args"].append(f"--sig-proxy {params['sig_proxy']}")
if params["systemd"]:
@ -358,7 +395,9 @@ class ContainerQuadlet(Quadlet):
if params["timeout"]:
params["podman_args"].append(f"--timeout {params['timeout']}")
if params["tls_verify"]:
params["podman_args"].append(f"--tls-verify={str(params['tls_verify']).lower()}")
params["podman_args"].append(
f"--tls-verify={str(params['tls_verify']).lower()}"
)
if params["tty"]:
params["podman_args"].append("--tty")
if params["umask"]:
@ -384,17 +423,17 @@ class ContainerQuadlet(Quadlet):
class NetworkQuadlet(Quadlet):
param_map = {
'name': 'NetworkName',
'internal': 'Internal',
'driver': 'Driver',
'gateway': 'Gateway',
'disable_dns': 'DisableDNS',
'subnet': 'Subnet',
'ip_range': 'IPRange',
'ipv6': 'IPv6',
"name": "NetworkName",
"internal": "Internal",
"driver": "Driver",
"gateway": "Gateway",
"disable_dns": "DisableDNS",
"subnet": "Subnet",
"ip_range": "IPRange",
"ipv6": "IPv6",
"opt": "Options",
# Add more parameter mappings specific to networks
'ContainersConfModule': 'ContainersConfModule',
"ContainersConfModule": "ContainersConfModule",
"dns": "DNS",
"ipam_driver": "IPAMDriver",
"Label": "Label",
@ -424,11 +463,11 @@ class NetworkQuadlet(Quadlet):
# This is a inherited class that represents a Quadlet file for the Podman pod
class PodQuadlet(Quadlet):
param_map = {
'name': 'PodName',
"name": "PodName",
"network": "Network",
"publish": "PublishPort",
"volume": "Volume",
'ContainersConfModule': 'ContainersConfModule',
"ContainersConfModule": "ContainersConfModule",
"global_args": "GlobalArgs",
"podman_args": "PodmanArgs",
}
@ -445,15 +484,21 @@ class PodQuadlet(Quadlet):
params["podman_args"] = []
if params["add_host"]:
for host in params['add_host']:
for host in params["add_host"]:
params["podman_args"].append(f"--add-host {host}")
if params["cgroup_parent"]:
params["podman_args"].append(f"--cgroup-parent {params['cgroup_parent']}")
if params["blkio_weight"]:
params["podman_args"].append(f"--blkio-weight {params['blkio_weight']}")
if params["blkio_weight_device"]:
params["podman_args"].append(" ".join([
f"--blkio-weight-device {':'.join(blkio)}" for blkio in params["blkio_weight_device"].items()]))
params["podman_args"].append(
" ".join(
[
f"--blkio-weight-device {':'.join(blkio)}"
for blkio in params["blkio_weight_device"].items()
]
)
)
if params["cpuset_cpus"]:
params["podman_args"].append(f"--cpuset-cpus {params['cpuset_cpus']}")
if params["cpuset_mems"]:
@ -494,7 +539,9 @@ class PodQuadlet(Quadlet):
if params["infra_command"]:
params["podman_args"].append(f"--infra-command {params['infra_command']}")
if params["infra_conmon_pidfile"]:
params["podman_args"].append(f"--infra-conmon-pidfile {params['infra_conmon_pidfile']}")
params["podman_args"].append(
f"--infra-conmon-pidfile {params['infra_conmon_pidfile']}"
)
if params["infra_image"]:
params["podman_args"].append(f"--infra-image {params['infra_image']}")
if params["infra_name"]:
@ -528,11 +575,15 @@ class PodQuadlet(Quadlet):
if params["share"]:
params["podman_args"].append(f"--share {params['share']}")
if params["share_parent"] is not None:
params["podman_args"].append(f"--share-parent={str(params['share_parent']).lower()}")
params["podman_args"].append(
f"--share-parent={str(params['share_parent']).lower()}"
)
if params["shm_size"]:
params["podman_args"].append(f"--shm-size {params['shm_size']}")
if params["shm_size_systemd"]:
params["podman_args"].append(f"--shm-size-systemd {params['shm_size_systemd']}")
params["podman_args"].append(
f"--shm-size-systemd {params['shm_size_systemd']}"
)
if params["subgidname"]:
params["podman_args"].append(f"--subgidname {params['subgidname']}")
if params["subuidname"]:
@ -559,13 +610,13 @@ class PodQuadlet(Quadlet):
# This is a inherited class that represents a Quadlet file for the Podman volume
class VolumeQuadlet(Quadlet):
param_map = {
'name': 'VolumeName',
'driver': 'Driver',
'label': 'Label',
"name": "VolumeName",
"driver": "Driver",
"label": "Label",
# 'opt': 'Options',
'ContainersConfModule': 'ContainersConfModule',
'global_args': 'GlobalArgs',
'podman_args': 'PodmanArgs',
"ContainersConfModule": "ContainersConfModule",
"global_args": "GlobalArgs",
"podman_args": "PodmanArgs",
}
def __init__(self, params: dict):
@ -593,19 +644,19 @@ class VolumeQuadlet(Quadlet):
# This is a inherited class that represents a Quadlet file for the Podman kube
class KubeQuadlet(Quadlet):
param_map = {
'configmap': 'ConfigMap',
'log_driver': 'LogDriver',
'network': 'Network',
'kube_file': 'Yaml',
'userns': 'UserNS',
'AutoUpdate': 'AutoUpdate',
'ExitCodePropagation': 'ExitCodePropagation',
'KubeDownForce': 'KubeDownForce',
'PublishPort': 'PublishPort',
'SetWorkingDirectory': 'SetWorkingDirectory',
'ContainersConfModule': 'ContainersConfModule',
'global_args': 'GlobalArgs',
'podman_args': 'PodmanArgs',
"configmap": "ConfigMap",
"log_driver": "LogDriver",
"network": "Network",
"kube_file": "Yaml",
"userns": "UserNS",
"AutoUpdate": "AutoUpdate",
"ExitCodePropagation": "ExitCodePropagation",
"KubeDownForce": "KubeDownForce",
"PublishPort": "PublishPort",
"SetWorkingDirectory": "SetWorkingDirectory",
"ContainersConfModule": "ContainersConfModule",
"global_args": "GlobalArgs",
"podman_args": "PodmanArgs",
}
def __init__(self, params: dict):
@ -628,20 +679,20 @@ class KubeQuadlet(Quadlet):
# This is a inherited class that represents a Quadlet file for the Podman image
class ImageQuadlet(Quadlet):
param_map = {
'AllTags': 'AllTags',
'arch': 'Arch',
'authfile': 'AuthFile',
'ca_cert_dir': 'CertDir',
'creds': 'Creds',
'DecryptionKey': 'DecryptionKey',
'name': 'Image',
'ImageTag': 'ImageTag',
'OS': 'OS',
'validate_certs': 'TLSVerify',
'Variant': 'Variant',
'ContainersConfModule': 'ContainersConfModule',
'global_args': 'GlobalArgs',
'podman_args': 'PodmanArgs',
"AllTags": "AllTags",
"arch": "Arch",
"authfile": "AuthFile",
"ca_cert_dir": "CertDir",
"creds": "Creds",
"DecryptionKey": "DecryptionKey",
"name": "Image",
"ImageTag": "ImageTag",
"OS": "OS",
"validate_certs": "TLSVerify",
"Variant": "Variant",
"ContainersConfModule": "ContainersConfModule",
"global_args": "GlobalArgs",
"podman_args": "PodmanArgs",
}
def __init__(self, params: dict):
@ -664,20 +715,20 @@ class ImageQuadlet(Quadlet):
def check_quadlet_directory(module, quadlet_dir):
'''Check if the directory exists and is writable. If not, fail the module.'''
"""Check if the directory exists and is writable. If not, fail the module."""
if not os.path.exists(quadlet_dir):
try:
os.makedirs(quadlet_dir)
except Exception as e:
module.fail_json(
msg="Directory for quadlet_file can't be created: %s" % e)
module.fail_json(msg="Directory for quadlet_file can't be created: %s" % e)
if not os.access(quadlet_dir, os.W_OK):
module.fail_json(
msg="Directory for quadlet_file is not writable: %s" % quadlet_dir)
msg="Directory for quadlet_file is not writable: %s" % quadlet_dir
)
def create_quadlet_state(module, issuer):
'''Create a quadlet file for the specified issuer.'''
"""Create a quadlet file for the specified issuer."""
class_map = {
"container": ContainerQuadlet,
"network": NetworkQuadlet,
@ -688,20 +739,22 @@ def create_quadlet_state(module, issuer):
}
# Let's detect which user is running
user = "root" if os.geteuid() == 0 else "user"
quadlet_dir = module.params.get('quadlet_dir')
quadlet_dir = module.params.get("quadlet_dir")
if not quadlet_dir:
if user == "root":
quadlet_dir = QUADLET_ROOT_PATH
else:
quadlet_dir = os.path.expanduser(QUADLET_NON_ROOT_PATH)
# Create a filename based on the issuer
if not module.params.get('name') and not module.params.get('quadlet_filename'):
module.fail_json(msg=f"Filename for {issuer} is required for creating a quadlet file.")
if not module.params.get("name") and not module.params.get("quadlet_filename"):
module.fail_json(
msg=f"Filename for {issuer} is required for creating a quadlet file."
)
if issuer == "image":
name = module.params['name'].split("/")[-1].split(":")[0]
name = module.params["name"].split("/")[-1].split(":")[0]
else:
name = module.params.get('name')
quad_file_name = module.params['quadlet_filename']
name = module.params.get("name")
quad_file_name = module.params["quadlet_filename"]
if quad_file_name and not quad_file_name.endswith(f".{issuer}"):
quad_file_name = f"{quad_file_name}.{issuer}"
filename = quad_file_name or f"{name}.{issuer}"
@ -710,10 +763,10 @@ def create_quadlet_state(module, issuer):
if not module.check_mode:
check_quadlet_directory(module, quadlet_dir)
# Specify file permissions
mode = module.params.get('quadlet_file_mode', None)
mode = module.params.get("quadlet_file_mode", None)
if mode is None and not os.path.exists(quadlet_file_path):
# default mode for new quadlet file only
mode = '0640'
mode = "0640"
# Check if file already exists and if it's different
quadlet = class_map[issuer](module.params)
quadlet_content = quadlet.create_quadlet_content()
@ -724,22 +777,31 @@ def create_quadlet_state(module, issuer):
if mode is not None:
module.set_mode_if_different(quadlet_file_path, mode, False)
results_update = {
'changed': True,
"changed": True,
"diff": {
"before": "\n".join(file_diff[0]) if isinstance(file_diff[0], list) else file_diff[0] + "\n",
"after": "\n".join(file_diff[1]) if isinstance(file_diff[1], list) else file_diff[1] + "\n",
}}
"before": (
"\n".join(file_diff[0])
if isinstance(file_diff[0], list)
else file_diff[0] + "\n"
),
"after": (
"\n".join(file_diff[1])
if isinstance(file_diff[1], list)
else file_diff[1] + "\n"
),
},
}
else:
# adjust file permissions
diff = {}
if mode is not None and module.set_mode_if_different(quadlet_file_path, mode, False, diff):
results_update = {
'changed': True,
'diff': diff
}
if mode is not None and module.set_mode_if_different(
quadlet_file_path, mode, False, diff
):
results_update = {"changed": True, "diff": diff}
else:
results_update = {}
return results_update
# Check with following command:
# QUADLET_UNIT_DIRS=<Directory> /usr/lib/systemd/system-generators/podman-system-generator {--user} --dryrun

View file

@ -1522,28 +1522,31 @@ container:
from ansible.module_utils.basic import AnsibleModule # noqa: F402
from ..module_utils.podman.podman_container_lib import PodmanManager # noqa: F402
from ..module_utils.podman.podman_container_lib import ARGUMENTS_SPEC_CONTAINER # noqa: F402
from ..module_utils.podman.podman_container_lib import (
ARGUMENTS_SPEC_CONTAINER,
) # noqa: F402
def main():
module = AnsibleModule(
argument_spec=ARGUMENTS_SPEC_CONTAINER,
mutually_exclusive=(
['no_hosts', 'etc_hosts'],
),
mutually_exclusive=(["no_hosts", "etc_hosts"],),
supports_check_mode=True,
)
# work on input vars
if (module.params['state'] in ['present', 'created']
and not module.params['force_restart']
and not module.params['image']):
module.fail_json(msg="State '%s' required image to be configured!" %
module.params['state'])
if (
module.params["state"] in ["present", "created"]
and not module.params["force_restart"]
and not module.params["image"]
):
module.fail_json(
msg="State '%s' required image to be configured!" % module.params["state"]
)
results = PodmanManager(module, module.params).execute()
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_container_copy
author:
- Alessandro Rossi (@kubealex)
@ -54,7 +55,7 @@ options:
required: False
default: False
type: bool
'''
"""
EXAMPLES = r"""
- name: Copy file "test.yml" on the host to the "apache" container's root folder
@ -73,22 +74,26 @@ EXAMPLES = r"""
from ansible.module_utils.basic import AnsibleModule
def copy_file(module, executable, src, dest, container, from_container, archive, overwrite):
def copy_file(
module, executable, src, dest, container, from_container, archive, overwrite
):
if from_container:
command = [executable, 'cp', '{0}:{1}'.format(container, src), dest]
command = [executable, "cp", "{0}:{1}".format(container, src), dest]
else:
command = [executable, 'cp', src, '{0}:{1}'.format(container, dest)]
command = [executable, "cp", src, "{0}:{1}".format(container, dest)]
if not archive:
command.append('--archive=False')
command.append("--archive=False")
if overwrite:
command.append('--overwrite')
command.append("--overwrite")
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg='Unable to copy file to/from container - {out}'.format(out=err))
module.fail_json(
msg="Unable to copy file to/from container - {out}".format(out=err)
)
else:
changed = True
return changed, out, err
@ -97,35 +102,35 @@ def copy_file(module, executable, src, dest, container, from_container, archive,
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
src=dict(type='str', required=True),
dest=dict(type='str', required=True),
container=dict(type='str', required=True),
from_container=dict(type='bool', required=False, default=False),
archive=dict(type='bool', required=False, default=True),
overwrite=dict(type='bool', required=False, default=False)
executable=dict(type="str", default="podman"),
src=dict(type="str", required=True),
dest=dict(type="str", required=True),
container=dict(type="str", required=True),
from_container=dict(type="bool", required=False, default=False),
archive=dict(type="bool", required=False, default=True),
overwrite=dict(type="bool", required=False, default=False),
),
supports_check_mode=False,
)
executable = module.params['executable']
src = module.params['src']
dest = module.params['dest']
container = module.params['container']
from_container = module.params['from_container']
archive = module.params['archive']
overwrite = module.params['overwrite']
executable = module.params["executable"]
src = module.params["src"]
dest = module.params["dest"]
container = module.params["container"]
from_container = module.params["from_container"]
archive = module.params["archive"]
overwrite = module.params["overwrite"]
executable = module.get_bin_path(executable, required=True)
changed, out, err = copy_file(module, executable, src, dest, container, from_container, archive, overwrite)
results = dict(
changed=changed
changed, out, err = copy_file(
module, executable, src, dest, container, from_container, archive, overwrite
)
results = dict(changed=changed)
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -4,9 +4,10 @@
# Copyright (c) 2023, Takuya Nishimura <@nishipy>
# 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'''
DOCUMENTATION = r"""
module: podman_container_exec
author:
- Takuya Nishimura (@nishipy)
@ -68,9 +69,9 @@ requirements:
- podman
notes:
- See L(the Podman documentation,https://docs.podman.io/en/latest/markdown/podman-exec.1.html) for details of podman-exec(1).
'''
"""
EXAMPLES = r'''
EXAMPLES = r"""
- name: Execute a command with workdir
containers.podman.podman_container_exec:
name: ubi8
@ -93,9 +94,9 @@ EXAMPLES = r'''
name: detach_container
command: "cat redhat-release"
detach: true
'''
"""
RETURN = r'''
RETURN = r"""
stdout:
type: str
returned: success
@ -118,65 +119,65 @@ exec_id:
sample: f99002e34c1087fd1aa08d5027e455bf7c2d6b74f019069acf6462a96ddf2a47
description:
- The ID of the exec session.
'''
"""
import shlex
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import run_podman_command
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
run_podman_command,
)
def run_container_exec(module: AnsibleModule) -> dict:
'''
"""
Execute podman-container-exec for the given options
'''
exec_with_args = ['container', 'exec']
"""
exec_with_args = ["container", "exec"]
# podman_container_exec always returns changed=true
changed = True
exec_options = []
name = module.params['name']
argv = module.params['argv']
command = module.params['command']
detach = module.params['detach']
env = module.params['env']
privileged = module.params['privileged']
tty = module.params['tty']
user = module.params['user']
workdir = module.params['workdir']
executable = module.params['executable']
name = module.params["name"]
argv = module.params["argv"]
command = module.params["command"]
detach = module.params["detach"]
env = module.params["env"]
privileged = module.params["privileged"]
tty = module.params["tty"]
user = module.params["user"]
workdir = module.params["workdir"]
executable = module.params["executable"]
if command is not None:
argv = shlex.split(command)
if detach:
exec_options.append('--detach')
exec_options.append("--detach")
if env is not None:
for key, value in env.items():
if not isinstance(value, string_types):
module.fail_json(
msg="Specify string value %s on the env field" % (value))
msg="Specify string value %s on the env field" % (value)
)
to_text(value, errors='surrogate_or_strict')
exec_options += ['--env',
'%s=%s' % (key, value)]
to_text(value, errors="surrogate_or_strict")
exec_options += ["--env", "%s=%s" % (key, value)]
if privileged:
exec_options.append('--privileged')
exec_options.append("--privileged")
if tty:
exec_options.append('--tty')
exec_options.append("--tty")
if user is not None:
exec_options += ['--user',
to_text(user, errors='surrogate_or_strict')]
exec_options += ["--user", to_text(user, errors="surrogate_or_strict")]
if workdir is not None:
exec_options += ['--workdir',
to_text(workdir, errors='surrogate_or_strict')]
exec_options += ["--workdir", to_text(workdir, errors="surrogate_or_strict")]
exec_options.append(name)
exec_options.extend(argv)
@ -184,71 +185,72 @@ def run_container_exec(module: AnsibleModule) -> dict:
exec_with_args.extend(exec_options)
rc, stdout, stderr = run_podman_command(
module=module, executable=executable, args=exec_with_args, ignore_errors=True)
module=module, executable=executable, args=exec_with_args, ignore_errors=True
)
result = {
'changed': changed,
'podman_command': exec_options,
'rc': rc,
'stdout': stdout,
'stderr': stderr,
"changed": changed,
"podman_command": exec_options,
"rc": rc,
"stdout": stdout,
"stderr": stderr,
}
if detach:
result['exec_id'] = stdout.replace('\n', '')
result["exec_id"] = stdout.replace("\n", "")
return result
def main():
argument_spec = {
'name': {
'type': 'str',
'required': True,
"name": {
"type": "str",
"required": True,
},
'command': {
'type': 'str',
"command": {
"type": "str",
},
'argv': {
'type': 'list',
'elements': 'str',
"argv": {
"type": "list",
"elements": "str",
},
'detach': {
'type': 'bool',
'default': False,
"detach": {
"type": "bool",
"default": False,
},
'executable': {
'type': 'str',
'default': 'podman',
"executable": {
"type": "str",
"default": "podman",
},
'env': {
'type': 'dict',
"env": {
"type": "dict",
},
'privileged': {
'type': 'bool',
'default': False,
"privileged": {
"type": "bool",
"default": False,
},
'tty': {
'type': 'bool',
'default': False,
"tty": {
"type": "bool",
"default": False,
},
'user': {
'type': 'str',
"user": {
"type": "str",
},
'workdir': {
'type': 'str',
"workdir": {
"type": "str",
},
}
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=[('argv', 'command')],
required_one_of=[("argv", "command")],
)
result = run_container_exec(module)
module.exit_json(**result)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_container_info
author:
- Sagi Shnaidman (@podman)
@ -31,7 +32,7 @@ options:
machine running C(podman)
default: 'podman'
type: str
'''
"""
EXAMPLES = r"""
- name: Gather facts for all containers
@ -338,7 +339,7 @@ def get_containers_facts(module, executable, name):
retry = 0
retry_limit = 4
if not name:
all_names = [executable, 'container', 'ls', '-q', '-a']
all_names = [executable, "container", "ls", "-q", "-a"]
rc, out, err = module.run_command(all_names)
# This should not fail in regular circumstances, so retry again
# https://github.com/containers/podman/issues/10225
@ -348,12 +349,14 @@ def get_containers_facts(module, executable, name):
retry += 1
rc, out, err = module.run_command(all_names)
if rc != 0:
module.fail_json(msg="Unable to get list of containers during"
" %s retries" % retry_limit)
module.fail_json(
msg="Unable to get list of containers during"
" %s retries" % retry_limit
)
name = out.split()
if not name:
return [], out, err
command = [executable, 'container', 'inspect']
command = [executable, "container", "inspect"]
command.extend(name)
rc, out, err = module.run_command(command)
if rc == 0:
@ -361,7 +364,7 @@ def get_containers_facts(module, executable, name):
if json_out is None:
return [], out, err
return json_out, out, err
if rc != 0 and 'no such ' in err:
if rc != 0 and "no such " in err:
if len(name) < 2:
return [], out, err
return cycle_over(module, executable, name)
@ -382,9 +385,9 @@ def cycle_over(module, executable, name):
inspection = []
stderrs = []
for container in name:
command = [executable, 'container', 'inspect', container]
command = [executable, "container", "inspect", container]
rc, out, err = module.run_command(command)
if rc != 0 and 'no such ' not in err:
if rc != 0 and "no such " not in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (container, err))
if rc == 0 and out:
json_out = json.loads(out)
@ -397,25 +400,21 @@ def cycle_over(module, executable, name):
def main():
module = AnsibleModule(
argument_spec={
'executable': {'type': 'str', 'default': 'podman'},
'name': {'type': 'list', 'elements': 'str'},
"executable": {"type": "str", "default": "podman"},
"name": {"type": "list", "elements": "str"},
},
supports_check_mode=True,
)
name = module.params['name']
executable = module.get_bin_path(module.params['executable'], required=True)
name = module.params["name"]
executable = module.get_bin_path(module.params["executable"], required=True)
# pylint: disable=unused-variable
inspect_results, out, err = get_containers_facts(module, executable, name)
results = {
"changed": False,
"containers": inspect_results,
"stderr": err
}
results = {"changed": False, "containers": inspect_results, "stderr": err}
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -6,7 +6,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
DOCUMENTATION = """
---
module: podman_containers
author:
@ -29,9 +29,9 @@ options:
- Return additional information which can be helpful for investigations.
type: bool
default: False
'''
"""
EXAMPLES = '''
EXAMPLES = """
- name: Run three containers at once
podman_containers:
containers:
@ -43,7 +43,7 @@ EXAMPLES = '''
- name: test
image: python:3.10-alpine
command: python -V
'''
"""
from ansible.module_utils.basic import AnsibleModule # noqa: F402
from ..module_utils.podman.podman_container_lib import PodmanManager # noqa: F402
@ -51,82 +51,81 @@ from ..module_utils.podman.podman_container_lib import set_container_opts # noq
def combine(results):
changed = any(i.get('changed', False) for i in results)
failed = any(i.get('failed', False) for i in results)
changed = any(i.get("changed", False) for i in results)
failed = any(i.get("failed", False) for i in results)
actions = []
podman_actions = []
containers = []
podman_version = ''
podman_version = ""
diffs = {}
stderr = ''
stdout = ''
stderr = ""
stdout = ""
for i in results:
if 'actions' in i and i['actions']:
actions += i['actions']
if 'podman_actions' in i and i['podman_actions']:
podman_actions += i['podman_actions']
if 'container' in i and i['container']:
containers.append(i['container'])
if 'podman_version' in i:
podman_version = i['podman_version']
if 'diff' in i:
diffs[i['container']['Name']] = i['diff']
if 'stderr' in i:
stderr += i['stderr']
if 'stdout' in i:
stdout += i['stdout']
if "actions" in i and i["actions"]:
actions += i["actions"]
if "podman_actions" in i and i["podman_actions"]:
podman_actions += i["podman_actions"]
if "container" in i and i["container"]:
containers.append(i["container"])
if "podman_version" in i:
podman_version = i["podman_version"]
if "diff" in i:
diffs[i["container"]["Name"]] = i["diff"]
if "stderr" in i:
stderr += i["stderr"]
if "stdout" in i:
stdout += i["stdout"]
total = {
'changed': changed,
'failed': failed,
'actions': actions,
'podman_actions': podman_actions,
'containers': containers,
'stdout': stdout,
'stderr': stderr,
"changed": changed,
"failed": failed,
"actions": actions,
"podman_actions": podman_actions,
"containers": containers,
"stdout": stdout,
"stderr": stderr,
}
if podman_version:
total['podman_version'] = podman_version
total["podman_version"] = podman_version
if diffs:
before = after = ''
before = after = ""
for k, v in diffs.items():
before += "".join([str(k), ": ", str(v['before']), "\n"])
after += "".join([str(k), ": ", str(v['after']), "\n"])
total['diff'] = {
'before': before,
'after': after
}
before += "".join([str(k), ": ", str(v["before"]), "\n"])
after += "".join([str(k), ": ", str(v["after"]), "\n"])
total["diff"] = {"before": before, "after": after}
return total
def check_input_strict(container):
if container['state'] in ['started', 'present'] and not container['image']:
return "State '%s' required image to be configured!" % container['state']
if container["state"] in ["started", "present"] and not container["image"]:
return "State '%s' required image to be configured!" % container["state"]
def main():
module = AnsibleModule(
argument_spec=dict(
containers=dict(type='list', elements='dict', required=True),
debug=dict(type='bool', default=False),
containers=dict(type="list", elements="dict", required=True),
debug=dict(type="bool", default=False),
),
supports_check_mode=True,
)
# work on input vars
results = []
for container in module.params['containers']:
for container in module.params["containers"]:
options_dict = set_container_opts(container)
options_dict['debug'] = module.params['debug'] or options_dict['debug']
options_dict["debug"] = module.params["debug"] or options_dict["debug"]
test_input = check_input_strict(options_dict)
if test_input:
module.fail_json(
msg="Failed to run container %s because: %s" % (options_dict['name'], test_input))
msg="Failed to run container %s because: %s"
% (options_dict["name"], test_input)
)
res = PodmanManager(module, options_dict).execute()
results.append(res)
total_results = combine(results)
module.exit_json(**total_results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_export
short_description: Export a podman container
author: Sagi Shnaidman (@sshnaidm)
@ -41,12 +41,12 @@ options:
type: str
requirements:
- "Podman installed on host"
'''
"""
RETURN = '''
'''
RETURN = """
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- containers.podman.podman_export:
dest: /path/to/tar/file
@ -54,7 +54,7 @@ EXAMPLES = '''
- containers.podman.podman_export:
dest: /path/to/tar/file
volume: volume-name
'''
"""
import os # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
@ -63,56 +63,58 @@ from ..module_utils.podman.common import remove_file_or_dir # noqa: E402
def export(module, executable):
changed = False
export_type = ''
export_type = ""
command = []
if module.params['container']:
export_type = 'container'
command = [executable, 'export']
if module.params["container"]:
export_type = "container"
command = [executable, "export"]
else:
export_type = 'volume'
command = [executable, 'volume', 'export']
export_type = "volume"
command = [executable, "volume", "export"]
command += ['-o=%s' % module.params['dest'], module.params[export_type]]
if module.params['force']:
dest = module.params['dest']
command += ["-o=%s" % module.params["dest"], module.params[export_type]]
if module.params["force"]:
dest = module.params["dest"]
if os.path.exists(dest):
changed = True
if module.check_mode:
return changed, '', ''
return changed, "", ""
try:
remove_file_or_dir(dest)
except Exception as e:
module.fail_json(msg="Error deleting %s path: %s" % (dest, e))
else:
changed = not os.path.exists(module.params['dest'])
changed = not os.path.exists(module.params["dest"])
if module.check_mode:
return changed, '', ''
return changed, "", ""
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Error exporting %s %s: %s" % (export_type,
module.params['container'], err))
module.fail_json(
msg="Error exporting %s %s: %s"
% (export_type, module.params["container"], err)
)
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(type='str', required=True),
container=dict(type='str'),
volume=dict(type='str'),
force=dict(type='bool', default=True),
executable=dict(type='str', default='podman')
dest=dict(type="str", required=True),
container=dict(type="str"),
volume=dict(type="str"),
force=dict(type="bool", default=True),
executable=dict(type="str", default="podman"),
),
supports_check_mode=True,
mutually_exclusive=[
('container', 'volume'),
("container", "volume"),
],
required_one_of=[
('container', 'volume'),
("container", "volume"),
],
)
executable = module.get_bin_path(module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
changed, out, err = export(module, executable)
results = {
@ -123,5 +125,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,11 +3,12 @@
# 2022, Sébastien Gendre <seb@k-7.ch>
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
DOCUMENTATION = """
module: podman_generate_systemd
author:
- Sébastien Gendre (@CyberFox001)
@ -140,9 +141,9 @@ notes:
systemd will not see the container or pod as started
- Stop your container or pod before you do a C(systemctl daemon-reload),
then you can start them with C(systemctl start my_container.service)
'''
"""
EXAMPLES = '''
EXAMPLES = """
# Example of creating a container and systemd unit file.
# When using podman_generate_systemd with new:true then
# the container needs rm:true for idempotence.
@ -204,9 +205,9 @@ EXAMPLES = '''
POSTGRES_USER: my_app
POSTGRES_PASSWORD: example
register: postgres_local_systemd_unit
'''
"""
RETURN = '''
RETURN = """
systemd_units:
description: A copy of the generated systemd .service unit(s)
returned: always
@ -220,27 +221,29 @@ podman_command:
returned: always
type: str
sample: "podman generate systemd my_webapp"
'''
"""
import os
from ansible.module_utils.basic import AnsibleModule
import json
from ansible_collections.containers.podman.plugins.module_utils.podman.common import compare_systemd_file_content
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
compare_systemd_file_content,
)
RESTART_POLICY_CHOICES = [
'no-restart',
'on-success',
'on-failure',
'on-abnormal',
'on-watchdog',
'on-abort',
'always',
"no-restart",
"on-success",
"on-failure",
"on-abnormal",
"on-watchdog",
"on-abort",
"always",
]
def generate_systemd(module):
'''Generate systemd .service unit file from a pod or container.
"""Generate systemd .service unit file from a pod or container.
Parameter:
- module (AnsibleModule): An AnsibleModule object
@ -249,7 +252,7 @@ def generate_systemd(module):
- A boolean which indicate whether the targeted systemd state is modified
- A copy of the generated systemd .service units content
- A copy of the command, as a string
'''
"""
# Flag which indicate whether the targeted system state is modified
changed = False
@ -257,115 +260,115 @@ def generate_systemd(module):
command_options = []
# New option
if module.params['new']:
command_options.append('--new')
if module.params["new"]:
command_options.append("--new")
# Restart policy option
restart_policy = module.params['restart_policy']
restart_policy = module.params["restart_policy"]
if restart_policy:
# add the restart policy to options
if restart_policy == 'no-restart':
restart_policy = 'no'
if restart_policy == "no-restart":
restart_policy = "no"
command_options.append(
'--restart-policy={restart_policy}'.format(
"--restart-policy={restart_policy}".format(
restart_policy=restart_policy,
),
)
# Restart-sec option (only for Podman 4.0.0 and above)
restart_sec = module.params['restart_sec']
restart_sec = module.params["restart_sec"]
if restart_sec:
command_options.append(
'--restart-sec={restart_sec}'.format(
"--restart-sec={restart_sec}".format(
restart_sec=restart_sec,
),
)
# Start-timeout option (only for Podman 4.0.0 and above)
start_timeout = module.params['start_timeout']
start_timeout = module.params["start_timeout"]
if start_timeout:
command_options.append(
'--start-timeout={start_timeout}'.format(
"--start-timeout={start_timeout}".format(
start_timeout=start_timeout,
),
)
# Stop-timeout option
stop_timeout = module.params['stop_timeout']
stop_timeout = module.params["stop_timeout"]
if stop_timeout:
command_options.append(
'--stop-timeout={stop_timeout}'.format(
"--stop-timeout={stop_timeout}".format(
stop_timeout=stop_timeout,
),
)
# Use container name(s) option
if module.params['use_names']:
command_options.append('--name')
if module.params["use_names"]:
command_options.append("--name")
# Container-prefix option
container_prefix = module.params['container_prefix']
container_prefix = module.params["container_prefix"]
if container_prefix is not None:
command_options.append(
'--container-prefix={container_prefix}'.format(
"--container-prefix={container_prefix}".format(
container_prefix=container_prefix,
),
)
# Pod-prefix option
pod_prefix = module.params['pod_prefix']
pod_prefix = module.params["pod_prefix"]
if pod_prefix is not None:
command_options.append(
'--pod-prefix={pod_prefix}'.format(
"--pod-prefix={pod_prefix}".format(
pod_prefix=pod_prefix,
),
)
# Separator option
separator = module.params['separator']
separator = module.params["separator"]
if separator is not None:
command_options.append(
'--separator={separator}'.format(
"--separator={separator}".format(
separator=separator,
),
)
# No-header option
if module.params['no_header']:
command_options.append('--no-header')
if module.params["no_header"]:
command_options.append("--no-header")
# After option (only for Podman 4.0.0 and above)
after = module.params['after']
after = module.params["after"]
if after:
for item in after:
command_options.append(
'--after={item}'.format(
"--after={item}".format(
item=item,
),
)
# Wants option (only for Podman 4.0.0 and above)
wants = module.params['wants']
wants = module.params["wants"]
if wants:
for item in wants:
command_options.append(
'--wants={item}'.format(
"--wants={item}".format(
item=item,
)
)
# Requires option (only for Podman 4.0.0 and above)
requires = module.params['requires']
requires = module.params["requires"]
if requires:
for item in requires:
command_options.append(
'--requires={item}'.format(
"--requires={item}".format(
item=item,
),
)
# Environment variables (only for Podman 4.3.0 and above)
environment_variables = module.params['env']
environment_variables = module.params["env"]
if environment_variables:
for env_var_name, env_var_value in environment_variables.items():
command_options.append(
@ -376,19 +379,21 @@ def generate_systemd(module):
)
# Set output format, of podman command, to json
command_options.extend(['--format', 'json'])
command_options.extend(["--format", "json"])
# Full command build, with option included
# Base of the command
command = [
module.params['executable'], 'generate', 'systemd',
module.params["executable"],
"generate",
"systemd",
]
# Add the options to the commande
command.extend(command_options)
# Add pod or container name to the command
command.append(module.params['name'])
command.append(module.params["name"])
# Build the string version of the command, only for module return
command_str = ' '.join(command)
command_str = " ".join(command)
# Run the podman command to generated systemd .service unit(s) content
return_code, stdout, stderr = module.run_command(command)
@ -396,10 +401,10 @@ def generate_systemd(module):
# In case of error in running the command
if return_code != 0:
# Print information about the error and return and empty dictionary
message = 'Error generating systemd .service unit(s).'
message += ' Command executed: {command_str}'
message += ' Command returned with code: {return_code}.'
message += ' Error message: {stderr}.'
message = "Error generating systemd .service unit(s)."
message += " Command executed: {command_str}"
message += " Command returned with code: {return_code}."
message += " Error message: {stderr}."
module.fail_json(
msg=message.format(
command_str=command_str,
@ -421,9 +426,9 @@ def generate_systemd(module):
# Write the systemd .service unit(s) content to file(s), if
# requested
if module.params['dest']:
if module.params["dest"]:
try:
systemd_units_dest = module.params['dest']
systemd_units_dest = module.params["dest"]
# If destination don't exist
if not os.path.exists(systemd_units_dest):
# If not in check mode, make it
@ -447,23 +452,24 @@ def generate_systemd(module):
# Write each systemd unit, if needed
for unit_name, unit_content in systemd_units.items():
# Build full path to unit file
unit_file_name = unit_name + '.service'
unit_file_name = unit_name + ".service"
unit_file_full_path = os.path.join(
systemd_units_dest,
unit_file_name,
)
if module.params['force']:
if module.params["force"]:
# Force to replace the existing unit file
need_to_write_file = True
else:
# See if we need to write the unit file, default yes
need_to_write_file = bool(compare_systemd_file_content(
unit_file_full_path, unit_content))
need_to_write_file = bool(
compare_systemd_file_content(unit_file_full_path, unit_content)
)
# Write the file, if needed
if need_to_write_file:
with open(unit_file_full_path, 'w') as unit_file:
with open(unit_file_full_path, "w") as unit_file:
# If not in check mode, write the file
if not module.check_mode:
unit_file.write(unit_content)
@ -471,133 +477,128 @@ def generate_systemd(module):
except Exception as exception:
# When exception occurs while trying to write units file
message = 'PODMAN-GENERATE-SYSTEMD-DEBUG: '
message += 'Error writing systemd units files: '
message += '{exception}'
message = "PODMAN-GENERATE-SYSTEMD-DEBUG: "
message += "Error writing systemd units files: "
message += "{exception}"
module.log(
message.format(
exception=exception
),
message.format(exception=exception),
)
# Return the systemd .service unit(s) content
return changed, systemd_units, command_str
def run_module():
'''Run the module on the target'''
"""Run the module on the target"""
# Build the list of parameters user can use
module_parameters = {
'name': {
'type': 'str',
'required': True,
"name": {
"type": "str",
"required": True,
},
'dest': {
'type': 'path',
'required': False,
"dest": {
"type": "path",
"required": False,
},
'new': {
'type': 'bool',
'required': False,
'default': False,
"new": {
"type": "bool",
"required": False,
"default": False,
},
'force': {
'type': 'bool',
'required': False,
'default': False,
"force": {
"type": "bool",
"required": False,
"default": False,
},
'restart_policy': {
'type': 'str',
'required': False,
'choices': RESTART_POLICY_CHOICES,
"restart_policy": {
"type": "str",
"required": False,
"choices": RESTART_POLICY_CHOICES,
},
'restart_sec': {
'type': 'int',
'required': False,
"restart_sec": {
"type": "int",
"required": False,
},
'start_timeout': {
'type': 'int',
'required': False,
"start_timeout": {
"type": "int",
"required": False,
},
'stop_timeout': {
'type': 'int',
'required': False,
"stop_timeout": {
"type": "int",
"required": False,
},
'env': {
'type': 'dict',
'required': False,
"env": {
"type": "dict",
"required": False,
},
'use_names': {
'type': 'bool',
'required': False,
'default': True,
"use_names": {
"type": "bool",
"required": False,
"default": True,
},
'container_prefix': {
'type': 'str',
'required': False,
"container_prefix": {
"type": "str",
"required": False,
},
'pod_prefix': {
'type': 'str',
'required': False,
"pod_prefix": {
"type": "str",
"required": False,
},
'separator': {
'type': 'str',
'required': False,
"separator": {
"type": "str",
"required": False,
},
'no_header': {
'type': 'bool',
'required': False,
'default': False,
"no_header": {
"type": "bool",
"required": False,
"default": False,
},
'after': {
'type': 'list',
'elements': 'str',
'required': False,
"after": {
"type": "list",
"elements": "str",
"required": False,
},
'wants': {
'type': 'list',
'elements': 'str',
'required': False,
"wants": {
"type": "list",
"elements": "str",
"required": False,
},
'requires': {
'type': 'list',
'elements': 'str',
'required': False,
"requires": {
"type": "list",
"elements": "str",
"required": False,
},
'executable': {
'type': 'str',
'required': False,
'default': 'podman',
"executable": {
"type": "str",
"required": False,
"default": "podman",
},
}
# Build result dictionary
result = {
'changed': False,
'systemd_units': {},
'podman_command': '',
"changed": False,
"systemd_units": {},
"podman_command": "",
}
# Build the Ansible Module
module = AnsibleModule(
argument_spec=module_parameters,
supports_check_mode=True
)
module = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True)
# Generate the systemd units
state_changed, systemd_units, podman_command = generate_systemd(module)
result['changed'] = state_changed
result['systemd_units'] = systemd_units
result['podman_command'] = podman_command
result["changed"] = state_changed
result["systemd_units"] = systemd_units
result["podman_command"] = podman_command
# Return the result
module.exit_json(**result)
def main():
'''Main function of this script.'''
"""Main function of this script."""
run_module()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_image
author:
- Sam Doran (@samdoran)
@ -225,7 +226,7 @@ DOCUMENTATION = r'''
type: list
elements: str
required: false
'''
"""
EXAMPLES = r"""
- name: Pull an image
@ -436,8 +437,12 @@ import sys # noqa: E402
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import run_podman_command
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import create_quadlet_state
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
run_podman_command,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import (
create_quadlet_state,
)
class PodmanImageManager(object):
@ -448,57 +453,68 @@ class PodmanImageManager(object):
self.module = module
self.results = results
self.name = self.module.params.get('name')
self.executable = self.module.get_bin_path(module.params.get('executable'), required=True)
self.tag = self.module.params.get('tag')
self.pull = self.module.params.get('pull')
self.pull_extra_args = self.module.params.get('pull_extra_args')
self.push = self.module.params.get('push')
self.path = self.module.params.get('path')
self.force = self.module.params.get('force')
self.state = self.module.params.get('state')
self.validate_certs = self.module.params.get('validate_certs')
self.auth_file = self.module.params.get('auth_file')
self.username = self.module.params.get('username')
self.password = self.module.params.get('password')
self.ca_cert_dir = self.module.params.get('ca_cert_dir')
self.build = self.module.params.get('build')
self.push_args = self.module.params.get('push_args')
self.arch = self.module.params.get('arch')
self.name = self.module.params.get("name")
self.executable = self.module.get_bin_path(
module.params.get("executable"), required=True
)
self.tag = self.module.params.get("tag")
self.pull = self.module.params.get("pull")
self.pull_extra_args = self.module.params.get("pull_extra_args")
self.push = self.module.params.get("push")
self.path = self.module.params.get("path")
self.force = self.module.params.get("force")
self.state = self.module.params.get("state")
self.validate_certs = self.module.params.get("validate_certs")
self.auth_file = self.module.params.get("auth_file")
self.username = self.module.params.get("username")
self.password = self.module.params.get("password")
self.ca_cert_dir = self.module.params.get("ca_cert_dir")
self.build = self.module.params.get("build")
self.push_args = self.module.params.get("push_args")
self.arch = self.module.params.get("arch")
repo, repo_tag = parse_repository_tag(self.name)
if repo_tag:
self.name = repo
self.tag = repo_tag
delimiter = ':' if "sha256" not in self.tag else '@'
self.image_name = '{name}{d}{tag}'.format(name=self.name, d=delimiter, tag=self.tag)
delimiter = ":" if "sha256" not in self.tag else "@"
self.image_name = "{name}{d}{tag}".format(
name=self.name, d=delimiter, tag=self.tag
)
if self.state in ['present', 'build']:
if self.state in ["present", "build"]:
self.present()
if self.state in ['absent']:
if self.state in ["absent"]:
self.absent()
if self.state == 'quadlet':
if self.state == "quadlet":
self.make_quadlet()
def _run(self, args, expected_rc=0, ignore_errors=False):
cmd = " ".join([self.executable]
+ [to_native(i) for i in args])
cmd = " ".join([self.executable] + [to_native(i) for i in args])
self.module.log("PODMAN-IMAGE-DEBUG: %s" % cmd)
self.results['podman_actions'].append(cmd)
self.results["podman_actions"].append(cmd)
return run_podman_command(
module=self.module,
executable=self.executable,
args=args,
expected_rc=expected_rc,
ignore_errors=ignore_errors)
ignore_errors=ignore_errors,
)
def _get_id_from_output(self, lines, startswith=None, contains=None, split_on=' ', maxsplit=1):
def _get_id_from_output(
self, lines, startswith=None, contains=None, split_on=" ", maxsplit=1
):
layer_ids = []
for line in lines.splitlines():
if startswith and line.startswith(startswith) or contains and contains in line:
if (
startswith
and line.startswith(startswith)
or contains
and contains in line
):
splitline = line.rsplit(split_on, maxsplit)
layer_ids.append(splitline[1])
@ -515,7 +531,9 @@ class PodmanImageManager(object):
"""
containerfile_path = None
for filename in [os.path.join(self.path, fname) for fname in ["Containerfile", "Dockerfile"]]:
for filename in [
os.path.join(self.path, fname) for fname in ["Containerfile", "Dockerfile"]
]:
if os.path.exists(filename):
containerfile_path = filename
break
@ -534,8 +552,10 @@ class PodmanImageManager(object):
locations, return 'None'.
"""
build_file_arg = self.build.get('file') if self.build else None
containerfile_contents = self.build.get('container_file') if self.build else None
build_file_arg = self.build.get("file") if self.build else None
containerfile_contents = (
self.build.get("container_file") if self.build else None
)
container_filename = None
if build_file_arg:
@ -564,8 +584,7 @@ class PodmanImageManager(object):
).hexdigest()
else:
return hashlib.sha256(
containerfile_contents.encode(),
usedforsecurity=False
containerfile_contents.encode(), usedforsecurity=False
).hexdigest()
def _get_args_containerfile_hash(self):
@ -578,12 +597,14 @@ class PodmanImageManager(object):
args_containerfile_hash = None
context_has_containerfile = self.path and self._find_containerfile_from_context()
context_has_containerfile = (
self.path and self._find_containerfile_from_context()
)
should_hash_args_containerfile = (
context_has_containerfile or
self.build.get('file') is not None or
self.build.get('container_file') is not None
context_has_containerfile
or self.build.get("file") is not None
or self.build.get("container_file") is not None
)
if should_hash_args_containerfile:
@ -599,68 +620,86 @@ class PodmanImageManager(object):
args_containerfile_hash = self._get_args_containerfile_hash()
if image:
digest_before = image[0].get('Digest', image[0].get('digest'))
labels = image[0].get('Labels') or {}
digest_before = image[0].get("Digest", image[0].get("digest"))
labels = image[0].get("Labels") or {}
if "containerfile.hash" in labels:
existing_image_containerfile_hash = labels["containerfile.hash"]
else:
digest_before = None
both_hashes_exist_and_differ = (args_containerfile_hash and existing_image_containerfile_hash and
args_containerfile_hash != existing_image_containerfile_hash
)
both_hashes_exist_and_differ = (
args_containerfile_hash
and existing_image_containerfile_hash
and args_containerfile_hash != existing_image_containerfile_hash
)
if not image or self.force or both_hashes_exist_and_differ:
if self.state == 'build' or self.path:
if self.state == "build" or self.path:
# Build the image
build_file = self.build.get('file') if self.build else None
container_file_txt = self.build.get('container_file') if self.build else None
build_file = self.build.get("file") if self.build else None
container_file_txt = (
self.build.get("container_file") if self.build else None
)
if build_file and container_file_txt:
self.module.fail_json(msg='Cannot specify both build file and container file content!')
self.module.fail_json(
msg="Cannot specify both build file and container file content!"
)
if not self.path and build_file:
self.path = os.path.dirname(build_file)
elif not self.path and not build_file and not container_file_txt:
self.module.fail_json(msg='Path to build context or file is required when building an image')
self.results['actions'].append('Built image {image_name} from {path}'.format(
image_name=self.image_name, path=self.path or 'default context'))
self.module.fail_json(
msg="Path to build context or file is required when building an image"
)
self.results["actions"].append(
"Built image {image_name} from {path}".format(
image_name=self.image_name, path=self.path or "default context"
)
)
if not self.module.check_mode:
self.results['image'], self.results['stdout'] = self.build_image(args_containerfile_hash)
image = self.results['image']
self.results["image"], self.results["stdout"] = self.build_image(
args_containerfile_hash
)
image = self.results["image"]
else:
# Pull the image
self.results['actions'].append('Pulled image {image_name}'.format(image_name=self.image_name))
self.results["actions"].append(
"Pulled image {image_name}".format(image_name=self.image_name)
)
if not self.module.check_mode:
image = self.results['image'] = self.pull_image()
image = self.results["image"] = self.pull_image()
if not image:
image = self.find_image()
if not self.module.check_mode:
digest_after = image[0].get('Digest', image[0].get('digest'))
self.results['changed'] = digest_before != digest_after
digest_after = image[0].get("Digest", image[0].get("digest"))
self.results["changed"] = digest_before != digest_after
else:
self.results['changed'] = True
self.results["changed"] = True
if self.push:
self.results['image'], output = self.push_image()
self.results['stdout'] += "\n" + output
if image and not self.results.get('image'):
self.results['image'] = image
self.results["image"], output = self.push_image()
self.results["stdout"] += "\n" + output
if image and not self.results.get("image"):
self.results["image"] = image
def absent(self):
image = self.find_image()
image_id = self.find_image_id()
if image:
self.results['actions'].append('Removed image {name}'.format(name=self.name))
self.results['changed'] = True
self.results['image']['state'] = 'Deleted'
self.results["actions"].append(
"Removed image {name}".format(name=self.name)
)
self.results["changed"] = True
self.results["image"]["state"] = "Deleted"
if not self.module.check_mode:
self.remove_image()
elif image_id:
self.results['actions'].append(
'Removed image with id {id}'.format(id=self.image_name))
self.results['changed'] = True
self.results['image']['state'] = 'Deleted'
self.results["actions"].append(
"Removed image with id {id}".format(id=self.image_name)
)
self.results["changed"] = True
self.results["image"]["state"] = "Deleted"
if not self.module.check_mode:
self.remove_image_id()
@ -673,17 +712,21 @@ class PodmanImageManager(object):
if image_name is None:
image_name = self.image_name
# Let's find out if image exists
rc, out, err = self._run(['image', 'exists', image_name], ignore_errors=True)
rc, out, err = self._run(["image", "exists", image_name], ignore_errors=True)
if rc == 0:
inspect_json = self.inspect_image(image_name)
else:
return None
args = ['image', 'ls', image_name, '--format', 'json']
args = ["image", "ls", image_name, "--format", "json"]
rc, images, err = self._run(args, ignore_errors=True)
try:
images = json.loads(images)
except json.decoder.JSONDecodeError:
self.module.fail_json(msg='Failed to parse JSON output from podman image ls: {out}'.format(out=images))
self.module.fail_json(
msg="Failed to parse JSON output from podman image ls: {out}".format(
out=images
)
)
if len(images) == 0:
return None
inspect_json = self.inspect_image(image_name)
@ -692,16 +735,15 @@ class PodmanImageManager(object):
return None
def _is_target_arch(self, inspect_json=None, arch=None):
return arch and inspect_json[0]['Architecture'] == arch
return arch and inspect_json[0]["Architecture"] == arch
def find_image_id(self, image_id=None):
if image_id is None:
# If image id is set as image_name, remove tag
image_id = re.sub(':.*$', '', self.image_name)
args = ['image', 'ls', '--quiet', '--no-trunc']
image_id = re.sub(":.*$", "", self.image_name)
args = ["image", "ls", "--quiet", "--no-trunc"]
rc, candidates, err = self._run(args, ignore_errors=True)
candidates = [re.sub('^sha256:', '', c)
for c in str.splitlines(candidates)]
candidates = [re.sub("^sha256:", "", c) for c in str.splitlines(candidates)]
for c in candidates:
if c.startswith(image_id):
return image_id
@ -710,12 +752,16 @@ class PodmanImageManager(object):
def inspect_image(self, image_name=None):
if image_name is None:
image_name = self.image_name
args = ['inspect', image_name, '--format', 'json']
args = ["inspect", image_name, "--format", "json"]
rc, image_data, err = self._run(args)
try:
image_data = json.loads(image_data)
except json.decoder.JSONDecodeError:
self.module.fail_json(msg='Failed to parse JSON output from podman inspect: {out}'.format(out=image_data))
self.module.fail_json(
msg="Failed to parse JSON output from podman inspect: {out}".format(
out=image_data
)
)
if len(image_data) > 0:
return image_data
else:
@ -725,26 +771,28 @@ class PodmanImageManager(object):
if image_name is None:
image_name = self.image_name
args = ['pull', image_name, '-q']
args = ["pull", image_name, "-q"]
if self.arch:
args.extend(['--arch', self.arch])
args.extend(["--arch", self.arch])
if self.auth_file:
args.extend(['--authfile', self.auth_file])
args.extend(["--authfile", self.auth_file])
if self.username and self.password:
cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
args.extend(['--creds', cred_string])
cred_string = "{user}:{password}".format(
user=self.username, password=self.password
)
args.extend(["--creds", cred_string])
if self.validate_certs is not None:
if self.validate_certs:
args.append('--tls-verify')
args.append("--tls-verify")
else:
args.append('--tls-verify=false')
args.append("--tls-verify=false")
if self.ca_cert_dir:
args.extend(['--cert-dir', self.ca_cert_dir])
args.extend(["--cert-dir", self.ca_cert_dir])
if self.pull_extra_args:
args.extend(shlex.split(self.pull_extra_args))
@ -753,177 +801,212 @@ class PodmanImageManager(object):
if rc != 0:
if not self.pull:
self.module.fail_json(
msg='Failed to find image {image_name} locally, image pull set to {pull_bool}'.format(
pull_bool=self.pull, image_name=image_name))
msg="Failed to find image {image_name} locally, image pull set to {pull_bool}".format(
pull_bool=self.pull, image_name=image_name
)
)
else:
self.module.fail_json(
msg='Failed to pull image {image_name}'.format(image_name=image_name))
msg="Failed to pull image {image_name}".format(
image_name=image_name
)
)
return self.inspect_image(out.strip())
def build_image(self, containerfile_hash):
args = ['build']
args.extend(['-t', self.image_name])
args = ["build"]
args.extend(["-t", self.image_name])
if self.validate_certs is not None:
if self.validate_certs:
args.append('--tls-verify')
args.append("--tls-verify")
else:
args.append('--tls-verify=false')
args.append("--tls-verify=false")
annotation = self.build.get('annotation')
annotation = self.build.get("annotation")
if annotation:
for k, v in annotation.items():
args.extend(['--annotation', '{k}={v}'.format(k=k, v=v)])
args.extend(["--annotation", "{k}={v}".format(k=k, v=v)])
if self.ca_cert_dir:
args.extend(['--cert-dir', self.ca_cert_dir])
args.extend(["--cert-dir", self.ca_cert_dir])
if self.build.get('force_rm'):
args.append('--force-rm')
if self.build.get("force_rm"):
args.append("--force-rm")
image_format = self.build.get('format')
image_format = self.build.get("format")
if image_format:
args.extend(['--format', image_format])
args.extend(["--format", image_format])
if self.arch:
args.extend(['--arch', self.arch])
args.extend(["--arch", self.arch])
if not self.build.get('cache'):
args.append('--no-cache')
if not self.build.get("cache"):
args.append("--no-cache")
if self.build.get('rm'):
args.append('--rm')
if self.build.get("rm"):
args.append("--rm")
containerfile = self.build.get('file')
containerfile = self.build.get("file")
if containerfile:
args.extend(['--file', containerfile])
container_file_txt = self.build.get('container_file')
args.extend(["--file", containerfile])
container_file_txt = self.build.get("container_file")
if container_file_txt:
# create a temporarly file with the content of the Containerfile
if self.path:
container_file_path = os.path.join(self.path, 'Containerfile.generated_by_ansible_%s' % time.time())
container_file_path = os.path.join(
self.path, "Containerfile.generated_by_ansible_%s" % time.time()
)
else:
container_file_path = os.path.join(
tempfile.gettempdir(), 'Containerfile.generated_by_ansible_%s' % time.time())
with open(container_file_path, 'w') as f:
tempfile.gettempdir(),
"Containerfile.generated_by_ansible_%s" % time.time(),
)
with open(container_file_path, "w") as f:
f.write(container_file_txt)
args.extend(['--file', container_file_path])
args.extend(["--file", container_file_path])
if containerfile_hash:
args.extend(['--label', f"containerfile.hash={containerfile_hash}"])
args.extend(["--label", f"containerfile.hash={containerfile_hash}"])
volume = self.build.get('volume')
volume = self.build.get("volume")
if volume:
for v in volume:
if v:
args.extend(['--volume', v])
args.extend(["--volume", v])
if self.auth_file:
args.extend(['--authfile', self.auth_file])
args.extend(["--authfile", self.auth_file])
if self.username and self.password:
cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
args.extend(['--creds', cred_string])
cred_string = "{user}:{password}".format(
user=self.username, password=self.password
)
args.extend(["--creds", cred_string])
extra_args = self.build.get('extra_args')
extra_args = self.build.get("extra_args")
if extra_args:
args.extend(shlex.split(extra_args))
target = self.build.get('target')
target = self.build.get("target")
if target:
args.extend(['--target', target])
args.extend(["--target", target])
if self.path:
args.append(self.path)
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(
image=self.image_name, out=out, err=err))
self.module.fail_json(
msg="Failed to build image {image}: {out} {err}".format(
image=self.image_name, out=out, err=err
)
)
# remove the temporary file if it was created
if container_file_txt:
os.remove(container_file_path)
last_id = self._get_id_from_output(out, startswith='-->')
last_id = self._get_id_from_output(out, startswith="-->")
return self.inspect_image(last_id), out + err
def push_image(self):
args = ['push']
args = ["push"]
if self.validate_certs is not None:
if self.validate_certs:
args.append('--tls-verify')
args.append("--tls-verify")
else:
args.append('--tls-verify=false')
args.append("--tls-verify=false")
if self.ca_cert_dir:
args.extend(['--cert-dir', self.ca_cert_dir])
args.extend(["--cert-dir", self.ca_cert_dir])
if self.username and self.password:
cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
args.extend(['--creds', cred_string])
cred_string = "{user}:{password}".format(
user=self.username, password=self.password
)
args.extend(["--creds", cred_string])
if self.auth_file:
args.extend(['--authfile', self.auth_file])
args.extend(["--authfile", self.auth_file])
if self.push_args.get('compress'):
args.append('--compress')
if self.push_args.get("compress"):
args.append("--compress")
push_format = self.push_args.get('format')
push_format = self.push_args.get("format")
if push_format:
args.extend(['--format', push_format])
args.extend(["--format", push_format])
if self.push_args.get('remove_signatures'):
args.append('--remove-signatures')
if self.push_args.get("remove_signatures"):
args.append("--remove-signatures")
sign_by_key = self.push_args.get('sign_by')
sign_by_key = self.push_args.get("sign_by")
if sign_by_key:
args.extend(['--sign-by', sign_by_key])
args.extend(["--sign-by", sign_by_key])
push_extra_args = self.push_args.get('extra_args')
push_extra_args = self.push_args.get("extra_args")
if push_extra_args:
args.extend(shlex.split(push_extra_args))
args.append(self.image_name)
# Build the destination argument
dest = self.push_args.get('dest')
transport = self.push_args.get('transport')
dest = self.push_args.get("dest")
transport = self.push_args.get("transport")
if dest is None:
dest = self.image_name
if transport:
if transport == 'docker':
dest_format_string = '{transport}://{dest}'
elif transport == 'ostree':
dest_format_string = '{transport}:{name}@{dest}'
if transport == "docker":
dest_format_string = "{transport}://{dest}"
elif transport == "ostree":
dest_format_string = "{transport}:{name}@{dest}"
else:
dest_format_string = '{transport}:{dest}'
if transport == 'docker-daemon' and ":" not in dest:
dest_format_string = '{transport}:{dest}:latest'
dest_string = dest_format_string.format(transport=transport, name=self.name, dest=dest)
dest_format_string = "{transport}:{dest}"
if transport == "docker-daemon" and ":" not in dest:
dest_format_string = "{transport}:{dest}:latest"
dest_string = dest_format_string.format(
transport=transport, name=self.name, dest=dest
)
else:
dest_string = dest
# In case of dest as a repository with org name only, append image name to it
if ":" not in dest and "@" not in dest and len(dest.rstrip("/").split("/")) == 2:
if (
":" not in dest
and "@" not in dest
and len(dest.rstrip("/").split("/")) == 2
):
dest_string = dest.rstrip("/") + "/" + self.image_name
if "/" not in dest_string and "@" not in dest_string and "docker-daemon" not in dest_string:
self.module.fail_json(msg="Destination must be a full URL or path to a directory with image name and tag.")
if (
"/" not in dest_string
and "@" not in dest_string
and "docker-daemon" not in dest_string
):
self.module.fail_json(
msg="Destination must be a full URL or path to a directory with image name and tag."
)
args.append(dest_string)
self.module.log("PODMAN-IMAGE-DEBUG: Pushing image {image_name} to {dest_string}".format(
image_name=self.image_name, dest_string=dest_string))
self.results['actions'].append(" ".join(args))
self.results['changed'] = True
out, err = '', ''
self.module.log(
"PODMAN-IMAGE-DEBUG: Pushing image {image_name} to {dest_string}".format(
image_name=self.image_name, dest_string=dest_string
)
)
self.results["actions"].append(" ".join(args))
self.results["changed"] = True
out, err = "", ""
if not self.module.check_mode:
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
self.module.fail_json(msg="Failed to push image {image_name}".format(
image_name=self.image_name),
stdout=out, stderr=err,
actions=self.results['actions'],
podman_actions=self.results['podman_actions'])
self.module.fail_json(
msg="Failed to push image {image_name}".format(
image_name=self.image_name
),
stdout=out,
stderr=err,
actions=self.results["actions"],
podman_actions=self.results["podman_actions"],
)
return self.inspect_image(self.image_name), out + err
@ -931,35 +1014,41 @@ class PodmanImageManager(object):
if image_name is None:
image_name = self.image_name
args = ['rmi', image_name]
args = ["rmi", image_name]
if self.force:
args.append('--force')
args.append("--force")
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(
image_name=image_name, err=err))
self.module.fail_json(
msg="Failed to remove image {image_name}. {err}".format(
image_name=image_name, err=err
)
)
return out
def remove_image_id(self, image_id=None):
if image_id is None:
image_id = re.sub(':.*$', '', self.image_name)
image_id = re.sub(":.*$", "", self.image_name)
args = ['rmi', image_id]
args = ["rmi", image_id]
if self.force:
args.append('--force')
args.append("--force")
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
self.module.fail_json(msg='Failed to remove image with id {image_id}. {err}'.format(
image_id=image_id, err=err))
self.module.fail_json(
msg="Failed to remove image with id {image_id}. {err}".format(
image_id=image_id, err=err
)
)
return out
def parse_repository_tag(repo_name):
parts = repo_name.rsplit('@', 1)
parts = repo_name.rsplit("@", 1)
if len(parts) == 2:
return tuple(parts)
parts = repo_name.rsplit(':', 1)
if len(parts) == 2 and '/' not in parts[1]:
parts = repo_name.rsplit(":", 1)
if len(parts) == 2 and "/" not in parts[1]:
return tuple(parts)
return repo_name, None
@ -967,77 +1056,78 @@ def parse_repository_tag(repo_name):
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=True),
arch=dict(type='str'),
tag=dict(type='str', default='latest'),
pull=dict(type='bool', default=True),
pull_extra_args=dict(type='str'),
push=dict(type='bool', default=False),
path=dict(type='str'),
force=dict(type='bool', default=False),
state=dict(type='str', default='present', choices=['absent', 'present', 'build', 'quadlet']),
validate_certs=dict(type='bool', aliases=['tlsverify', 'tls_verify']),
executable=dict(type='str', default='podman'),
auth_file=dict(type='path', aliases=['authfile']),
username=dict(type='str'),
password=dict(type='str', no_log=True),
ca_cert_dir=dict(type='path'),
quadlet_dir=dict(type='path', required=False),
quadlet_filename=dict(type='str'),
quadlet_file_mode=dict(type='raw', required=False),
quadlet_options=dict(type='list', elements='str', required=False),
name=dict(type="str", required=True),
arch=dict(type="str"),
tag=dict(type="str", default="latest"),
pull=dict(type="bool", default=True),
pull_extra_args=dict(type="str"),
push=dict(type="bool", default=False),
path=dict(type="str"),
force=dict(type="bool", default=False),
state=dict(
type="str",
default="present",
choices=["absent", "present", "build", "quadlet"],
),
validate_certs=dict(type="bool", aliases=["tlsverify", "tls_verify"]),
executable=dict(type="str", default="podman"),
auth_file=dict(type="path", aliases=["authfile"]),
username=dict(type="str"),
password=dict(type="str", no_log=True),
ca_cert_dir=dict(type="path"),
quadlet_dir=dict(type="path", required=False),
quadlet_filename=dict(type="str"),
quadlet_file_mode=dict(type="raw", required=False),
quadlet_options=dict(type="list", elements="str", required=False),
build=dict(
type='dict',
aliases=['build_args', 'buildargs'],
type="dict",
aliases=["build_args", "buildargs"],
default={},
options=dict(
annotation=dict(type='dict'),
force_rm=dict(type='bool', default=False),
file=dict(type='path'),
container_file=dict(type='str'),
format=dict(
type='str',
choices=['oci', 'docker'],
default='oci'
),
cache=dict(type='bool', default=True),
rm=dict(type='bool', default=True),
volume=dict(type='list', elements='str'),
extra_args=dict(type='str'),
target=dict(type='str'),
annotation=dict(type="dict"),
force_rm=dict(type="bool", default=False),
file=dict(type="path"),
container_file=dict(type="str"),
format=dict(type="str", choices=["oci", "docker"], default="oci"),
cache=dict(type="bool", default=True),
rm=dict(type="bool", default=True),
volume=dict(type="list", elements="str"),
extra_args=dict(type="str"),
target=dict(type="str"),
),
),
push_args=dict(
type='dict',
type="dict",
default={},
options=dict(
compress=dict(type='bool'),
format=dict(type='str', choices=['oci', 'v2s1', 'v2s2']),
remove_signatures=dict(type='bool'),
sign_by=dict(type='str'),
dest=dict(type='str', aliases=['destination'],),
extra_args=dict(type='str'),
compress=dict(type="bool"),
format=dict(type="str", choices=["oci", "v2s1", "v2s2"]),
remove_signatures=dict(type="bool"),
sign_by=dict(type="str"),
dest=dict(
type="str",
aliases=["destination"],
),
extra_args=dict(type="str"),
transport=dict(
type='str',
type="str",
choices=[
'dir',
'docker-archive',
'docker-daemon',
'oci-archive',
'ostree',
'docker'
]
"dir",
"docker-archive",
"docker-daemon",
"oci-archive",
"ostree",
"docker",
],
),
),
),
),
supports_check_mode=True,
required_together=(
['username', 'password'],
),
required_together=(["username", "password"],),
mutually_exclusive=(
['auth_file', 'username'],
['auth_file', 'password'],
["auth_file", "username"],
["auth_file", "password"],
),
)
@ -1046,12 +1136,12 @@ def main():
actions=[],
podman_actions=[],
image={},
stdout='',
stdout="",
)
PodmanImageManager(module, results)
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_image_info
author:
- Sam Doran (@samdoran)
@ -27,7 +28,7 @@ options:
type: list
elements: str
'''
"""
EXAMPLES = r"""
- name: Gather info for all images
@ -126,13 +127,13 @@ from ansible.module_utils.basic import AnsibleModule
def image_exists(module, executable, name):
command = [executable, 'image', 'exists', name]
command = [executable, "image", "exists", name]
rc, out, err = module.run_command(command)
if rc == 1:
return False
elif 'Command "exists" not found' in err:
# The 'exists' test is available in podman >= 0.12.1
command = [executable, 'image', 'ls', '-q', name]
command = [executable, "image", "ls", "-q", name]
rc2, out2, err2 = module.run_command(command)
if rc2 != 0:
return False
@ -158,12 +159,14 @@ def get_image_info(module, executable, name):
names = [name]
if len(names) > 0:
command = [executable, 'image', 'inspect']
command = [executable, "image", "inspect"]
command.extend(names)
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Unable to gather info for '{0}': {1}".format(', '.join(names), err))
module.fail_json(
msg="Unable to gather info for '{0}': {1}".format(", ".join(names), err)
)
return out
else:
@ -171,11 +174,11 @@ def get_image_info(module, executable, name):
def get_all_image_info(module, executable):
command = [executable, 'image', 'ls', '-q']
command = [executable, "image", "ls", "-q"]
rc, out, err = module.run_command(command)
out = out.strip()
if out:
name = out.split('\n')
name = out.split("\n")
res = get_image_info(module, executable, name)
return res
return json.dumps([])
@ -184,14 +187,14 @@ def get_all_image_info(module, executable):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='list', elements='str')
executable=dict(type="str", default="podman"),
name=dict(type="list", elements="str"),
),
supports_check_mode=True,
)
executable = module.params['executable']
name = module.params.get('name')
executable = module.params["executable"]
name = module.params.get("name")
executable = module.get_bin_path(executable, required=True)
if name:
@ -200,13 +203,10 @@ def main():
else:
results = json.loads(get_all_image_info(module, executable))
results = dict(
changed=False,
images=results
)
results = dict(changed=False, images=results)
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_import
short_description: Import Podman container from a tar file.
author: Sagi Shnaidman (@sshnaidm)
@ -41,9 +41,9 @@ options:
type: str
requirements:
- "Podman installed on host"
'''
"""
RETURN = '''
RETURN = """
image:
description: info from loaded image
returned: always
@ -89,9 +89,9 @@ image:
],
"NamesHistory": null
}
'''
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- containers.podman.podman_import:
src: /path/to/tar/file
@ -102,7 +102,7 @@ EXAMPLES = '''
- containers.podman.podman_import:
src: /path/to/tar/file
volume: myvolume
'''
"""
import json # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
@ -110,22 +110,22 @@ from ansible.module_utils.basic import AnsibleModule # noqa: E402
def load(module, executable):
changed = False
command = [executable, 'import']
if module.params['commit_message']:
command.extend(['--message', module.params['commit_message']])
if module.params['change']:
for change in module.params['change']:
command += ['--change', "=".join(list(change.items())[0])]
command += [module.params['src']]
command = [executable, "import"]
if module.params["commit_message"]:
command.extend(["--message", module.params["commit_message"]])
if module.params["change"]:
for change in module.params["change"]:
command += ["--change", "=".join(list(change.items())[0])]
command += [module.params["src"]]
changed = True
if module.check_mode:
return changed, '', '', '', command
return changed, "", "", "", command
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Image loading failed: %s" % (err))
image_name_line = [i for i in out.splitlines() if 'sha256' in i][0]
image_name_line = [i for i in out.splitlines() if "sha256" in i][0]
image_name = image_name_line.split(":", maxsplit=1)[1].strip()
rc, out2, err2 = module.run_command([executable, 'image', 'inspect', image_name])
rc, out2, err2 = module.run_command([executable, "image", "inspect", image_name])
if rc != 0:
module.fail_json(msg="Image %s inspection failed: %s" % (image_name, err2))
try:
@ -137,43 +137,55 @@ def load(module, executable):
def volume_load(module, executable):
changed = True
command = [executable, 'volume', 'import', module.params['volume'], module.params['src']]
src = module.params['src']
command = [
executable,
"volume",
"import",
module.params["volume"],
module.params["src"],
]
src = module.params["src"]
if module.check_mode:
return changed, '', '', '', command
return changed, "", "", "", command
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Error importing volume %s: %s" % (src, err))
rc, out2, err2 = module.run_command([executable, 'volume', 'inspect', module.params['volume']])
rc, out2, err2 = module.run_command(
[executable, "volume", "inspect", module.params["volume"]]
)
if rc != 0:
module.fail_json(msg="Volume %s inspection failed: %s" % (module.params['volume'], err2))
module.fail_json(
msg="Volume %s inspection failed: %s" % (module.params["volume"], err2)
)
try:
info = json.loads(out2)[0]
except Exception as e:
module.fail_json(msg="Could not parse JSON from volume %s: %s" % (module.params['volume'], e))
module.fail_json(
msg="Could not parse JSON from volume %s: %s" % (module.params["volume"], e)
)
return changed, out, err, info, command
def main():
module = AnsibleModule(
argument_spec=dict(
src=dict(type='str', required=True),
commit_message=dict(type='str'),
change=dict(type='list', elements='dict'),
executable=dict(type='str', default='podman'),
volume=dict(type='str', required=False),
src=dict(type="str", required=True),
commit_message=dict(type="str"),
change=dict(type="list", elements="dict"),
executable=dict(type="str", default="podman"),
volume=dict(type="str", required=False),
),
mutually_exclusive=[
('volume', 'commit_message'),
('volume', 'change'),
("volume", "commit_message"),
("volume", "change"),
],
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
volume_info = ''
image_info = ''
if module.params['volume']:
executable = module.get_bin_path(module.params["executable"], required=True)
volume_info = ""
image_info = ""
if module.params["volume"]:
changed, out, err, volume_info, command = volume_load(module, executable)
else:
changed, out, err, image_info, command = load(module, executable)
@ -184,11 +196,11 @@ def main():
"stderr": err,
"image": image_info,
"volume": volume_info,
"podman_command": " ".join(command)
"podman_command": " ".join(command),
}
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_load
short_description: Load image from a tar file.
author: Sagi Shnaidman (@sshnaidm)
@ -32,9 +32,9 @@ options:
type: str
requirements:
- "Podman installed on host"
'''
"""
RETURN = '''
RETURN = """
image:
description: info from loaded image
returned: always
@ -132,13 +132,13 @@ c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/merged",
"VirtualSize": 569919342
}
]
'''
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- containers.podman.podman_load:
input: /path/to/tar/file
'''
"""
import json # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
@ -146,24 +146,24 @@ from ansible.module_utils.basic import AnsibleModule # noqa: E402
def load(module, executable):
changed = False
command = [executable, 'load', '--input']
command.append(module.params['input'])
command = [executable, "load", "--input"]
command.append(module.params["input"])
changed = True
if module.check_mode:
return changed, '', '', ''
return changed, "", "", ""
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Image loading failed: %s" % (err))
image_name_line = [i for i in out.splitlines() if 'Loaded image' in i][0]
image_name_line = [i for i in out.splitlines() if "Loaded image" in i][0]
# For Podman < 4.x
if 'Loaded image(s):' in image_name_line:
image_name = image_name_line.split("Loaded image(s): ")[1].split(',')[0].strip()
if "Loaded image(s):" in image_name_line:
image_name = image_name_line.split("Loaded image(s): ")[1].split(",")[0].strip()
# For Podman > 4.x
elif 'Loaded image:' in image_name_line:
elif "Loaded image:" in image_name_line:
image_name = image_name_line.split("Loaded image: ")[1].strip()
else:
module.fail_json(msg="Not found images in %s" % image_name_line)
rc, out2, err2 = module.run_command([executable, 'image', 'inspect', image_name])
rc, out2, err2 = module.run_command([executable, "image", "inspect", image_name])
if rc != 0:
module.fail_json(msg="Image %s inspection failed: %s" % (image_name, err2))
try:
@ -176,13 +176,13 @@ def load(module, executable):
def main():
module = AnsibleModule(
argument_spec=dict(
input=dict(type='str', required=True, aliases=['path']),
executable=dict(type='str', default='podman')
input=dict(type="str", required=True, aliases=["path"]),
executable=dict(type="str", default="podman"),
),
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
changed, out, err, image_info = load(module, executable)
results = {
@ -195,5 +195,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -2,9 +2,10 @@
# 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'''
DOCUMENTATION = r"""
module: podman_login
author:
- "Jason Hiatt (@jthiatt)"
@ -70,7 +71,7 @@ options:
- Name of an existing C(podman) secret to use for authentication
to target registry
type: str
'''
"""
EXAMPLES = r"""
- name: Login to default registry and create ${XDG_RUNTIME_DIR}/containers/auth.json
@ -96,57 +97,70 @@ EXAMPLES = r"""
import hashlib
import os
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion
from ansible_collections.containers.podman.plugins.module_utils.podman.common import get_podman_version
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
LooseVersion,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
get_podman_version,
)
def login(module, executable, registry, authfile,
certdir, tlsverify, username, password, secret):
def login(
module,
executable,
registry,
authfile,
certdir,
tlsverify,
username,
password,
secret,
):
command = [executable, 'login']
command = [executable, "login"]
changed = False
if username:
command.extend(['--username', username])
command.extend(["--username", username])
if password:
command.extend(['--password', password])
command.extend(["--password", password])
if secret:
command.extend(['--secret', secret])
command.extend(["--secret", secret])
if authfile:
command.extend(['--authfile', authfile])
command.extend(["--authfile", authfile])
authfile = os.path.expandvars(authfile)
else:
authfile = os.getenv('XDG_RUNTIME_DIR', '') + '/containers/auth.json'
authfile = os.getenv("XDG_RUNTIME_DIR", "") + "/containers/auth.json"
if registry:
command.append(registry)
if certdir:
command.extend(['--cert-dir', certdir])
command.extend(["--cert-dir", certdir])
if tlsverify is not None:
if tlsverify:
command.append('--tls-verify')
command.append("--tls-verify")
else:
command.append('--tls-verify=False')
command.append("--tls-verify=False")
# Use a checksum to check if the auth JSON has changed
checksum = None
docker_authfile = os.path.expandvars('$HOME/.docker/config.json')
docker_authfile = os.path.expandvars("$HOME/.docker/config.json")
# podman falls back to ~/.docker/config.json if the default authfile doesn't exist
check_file = authfile if os.path.exists(authfile) else docker_authfile
if os.path.exists(check_file):
content = open(check_file, 'rb').read()
content = open(check_file, "rb").read()
checksum = hashlib.sha256(content).hexdigest()
rc, out, err = module.run_command(command)
if rc != 0:
if 'Error: Not logged into' not in err:
if "Error: Not logged into" not in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err))
else:
# If the command is successful, we managed to login
changed = True
if 'Existing credentials are valid' in out:
if "Existing credentials are valid" in out:
changed = False
# If we have managed to calculate a checksum before, check if it has changed
# due to the login
if checksum:
content = open(check_file, 'rb').read()
content = open(check_file, "rb").read()
new_checksum = hashlib.sha256(content).hexdigest()
if new_checksum == checksum:
changed = False
@ -156,47 +170,56 @@ def login(module, executable, registry, authfile,
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
registry=dict(type='str'),
authfile=dict(type='path'),
username=dict(type='str'),
password=dict(type='str', no_log=True),
certdir=dict(type='path'),
tlsverify=dict(type='bool'),
secret=dict(type='str', no_log=False),
executable=dict(type="str", default="podman"),
registry=dict(type="str"),
authfile=dict(type="path"),
username=dict(type="str"),
password=dict(type="str", no_log=True),
certdir=dict(type="path"),
tlsverify=dict(type="bool"),
secret=dict(type="str", no_log=False),
),
supports_check_mode=False,
required_by={
'password': 'username',
"password": "username",
},
mutually_exclusive=[
['password', 'secret'],
["password", "secret"],
],
)
registry = module.params['registry']
authfile = module.params['authfile']
username = module.params['username']
password = module.params['password']
certdir = module.params['certdir']
tlsverify = module.params['tlsverify']
secret = module.params['secret']
executable = module.get_bin_path(module.params['executable'], required=True)
registry = module.params["registry"]
authfile = module.params["authfile"]
username = module.params["username"]
password = module.params["password"]
certdir = module.params["certdir"]
tlsverify = module.params["tlsverify"]
secret = module.params["secret"]
executable = module.get_bin_path(module.params["executable"], required=True)
podman_version = get_podman_version(module, fail=False)
if (
(podman_version is not None) and
(LooseVersion(podman_version) < LooseVersion('4.7.0')) and
secret
(podman_version is not None)
and (LooseVersion(podman_version) < LooseVersion("4.7.0"))
and secret
):
module.fail_json(msg="secret option may not be used with podman < 4.7.0")
if username and ((not password) and (not secret)):
module.fail_json(msg="Must pass either password or secret with username")
changed, out, err = login(module, executable, registry, authfile,
certdir, tlsverify, username, password, secret)
changed, out, err = login(
module,
executable,
registry,
authfile,
certdir,
tlsverify,
username,
password,
secret,
)
results = {
"changed": changed,
@ -207,5 +230,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -2,6 +2,7 @@
# 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
@ -64,23 +65,23 @@ from ansible.module_utils.basic import AnsibleModule
def get_login_info(module, executable, authfile, registry):
command = [executable, 'login', '--get-login']
command = [executable, "login", "--get-login"]
result = dict(
registry=registry,
username='',
username="",
logged_in=False,
)
if authfile:
command.extend(['--authfile', authfile])
command.extend(["--authfile", authfile])
if registry:
command.append(registry)
rc, out, err = module.run_command(command)
if rc != 0:
if 'Error: not logged into' in err:
if "Error: not logged into" in err:
# The error message is e.g. 'Error: not logged into docker.io'
# Therefore get last word to extract registry name
result["registry"] = err.split()[-1]
err = ''
err = ""
return result
module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err))
result["username"] = out.strip()
@ -91,16 +92,16 @@ def get_login_info(module, executable, authfile, registry):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
authfile=dict(type='path'),
registry=dict(type='str', required=True)
executable=dict(type="str", default="podman"),
authfile=dict(type="path"),
registry=dict(type="str", required=True),
),
supports_check_mode=True,
)
registry = module.params['registry']
authfile = module.params['authfile']
executable = module.get_bin_path(module.params['executable'], required=True)
registry = module.params["registry"]
authfile = module.params["authfile"]
executable = module.get_bin_path(module.params["executable"], required=True)
inspect_results = get_login_info(module, executable, authfile, registry)
@ -112,5 +113,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -2,9 +2,10 @@
# 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'''
DOCUMENTATION = r"""
module: podman_logout
author:
- "Clemens Lange (@clelange)"
@ -63,7 +64,7 @@ options:
machine running C(podman)
default: 'podman'
type: str
'''
"""
EXAMPLES = r"""
- name: Log out of default registry
@ -87,29 +88,31 @@ EXAMPLES = r"""
from ansible.module_utils.basic import AnsibleModule
def logout(module, executable, registry, authfile, all_registries, ignore_docker_credentials):
command = [executable, 'logout']
def logout(
module, executable, registry, authfile, all_registries, ignore_docker_credentials
):
command = [executable, "logout"]
changed = False
if authfile:
command.extend(['--authfile', authfile])
command.extend(["--authfile", authfile])
if registry:
command.append(registry)
if all_registries:
command.append("--all")
rc, out, err = module.run_command(command)
if rc != 0:
if 'Error: Not logged into' not in err:
if "Error: Not logged into" not in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err))
else:
# If the command is successful, we managed to log out
# Mind: This also applied if --all flag is used, while in this case
# there is no check whether one has been logged into any registry
changed = True
if 'Existing credentials were established via' in out:
if "Existing credentials were established via" in out:
# The command will return successfully but not log out the user if the
# credentials were initially created using docker. Catch this behaviour:
if not ignore_docker_credentials:
module.fail_json(msg="Unable to log out %s: %s" % (registry or '', out))
module.fail_json(msg="Unable to log out %s: %s" % (registry or "", out))
else:
changed = False
return changed, out, err
@ -118,27 +121,33 @@ def logout(module, executable, registry, authfile, all_registries, ignore_docker
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
registry=dict(type='str'),
authfile=dict(type='path'),
all=dict(type='bool'),
ignore_docker_credentials=dict(type='bool'),
executable=dict(type="str", default="podman"),
registry=dict(type="str"),
authfile=dict(type="path"),
all=dict(type="bool"),
ignore_docker_credentials=dict(type="bool"),
),
supports_check_mode=True,
mutually_exclusive=(
['registry', 'all'],
['ignore_docker_credentials', 'all'],
["registry", "all"],
["ignore_docker_credentials", "all"],
),
)
registry = module.params['registry']
authfile = module.params['authfile']
all_registries = module.params['all']
ignore_docker_credentials = module.params['ignore_docker_credentials']
executable = module.get_bin_path(module.params['executable'], required=True)
registry = module.params["registry"]
authfile = module.params["authfile"]
all_registries = module.params["all"]
ignore_docker_credentials = module.params["ignore_docker_credentials"]
executable = module.get_bin_path(module.params["executable"], required=True)
changed, out, err = logout(module, executable, registry, authfile,
all_registries, ignore_docker_credentials)
changed, out, err = logout(
module,
executable,
registry,
authfile,
all_registries,
ignore_docker_credentials,
)
results = {
"changed": changed,
@ -149,5 +158,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,6 +3,7 @@
# 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
@ -295,27 +296,35 @@ network:
"""
import json
try:
import ipaddress
HAS_IP_ADDRESS_MODULE = True
except ImportError:
HAS_IP_ADDRESS_MODULE = False
from ansible.module_utils.basic import AnsibleModule # noqa: F402
from ansible.module_utils._text import to_bytes, to_native # noqa: F402
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion
from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import create_quadlet_state
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
LooseVersion,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
lower_keys,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import (
create_quadlet_state,
)
class PodmanNetworkModuleParams:
"""Creates list of arguments for podman CLI command.
Arguments:
action {str} -- action type from 'create', 'delete'
params {dict} -- dictionary of module parameters
Arguments:
action {str} -- action type from 'create', 'delete'
params {dict} -- dictionary of module parameters
"""
"""
def __init__(self, action, params, podman_version, module):
self.params = params
@ -329,99 +338,104 @@ class PodmanNetworkModuleParams:
Returns:
list -- list of byte strings for Popen command
"""
if self.action in ['delete']:
if self.action in ["delete"]:
return self._delete_action()
if self.action in ['create']:
if self.action in ["create"]:
return self._create_action()
def _delete_action(self):
cmd = ['rm', self.params['name']]
if self.params['force']:
cmd += ['--force']
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
cmd = ["rm", self.params["name"]]
if self.params["force"]:
cmd += ["--force"]
return [to_bytes(i, errors="surrogate_or_strict") for i in cmd]
def _create_action(self):
cmd = [self.action, self.params['name']]
all_param_methods = [func for func in dir(self)
if callable(getattr(self, func))
and func.startswith("addparam")]
cmd = [self.action, self.params["name"]]
all_param_methods = [
func
for func in dir(self)
if callable(getattr(self, func)) and func.startswith("addparam")
]
params_set = (i for i in self.params if self.params[i] is not None)
for param in params_set:
func_name = "_".join(["addparam", param])
if func_name in all_param_methods:
cmd = getattr(self, func_name)(cmd)
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
return [to_bytes(i, errors="surrogate_or_strict") for i in cmd]
def check_version(self, param, minv=None, maxv=None):
if minv and LooseVersion(minv) > LooseVersion(
self.podman_version):
self.module.fail_json(msg="Parameter %s is supported from podman "
"version %s only! Current version is %s" % (
param, minv, self.podman_version))
if maxv and LooseVersion(maxv) < LooseVersion(
self.podman_version):
self.module.fail_json(msg="Parameter %s is supported till podman "
"version %s only! Current version is %s" % (
param, minv, self.podman_version))
if minv and LooseVersion(minv) > LooseVersion(self.podman_version):
self.module.fail_json(
msg="Parameter %s is supported from podman "
"version %s only! Current version is %s"
% (param, minv, self.podman_version)
)
if maxv and LooseVersion(maxv) < LooseVersion(self.podman_version):
self.module.fail_json(
msg="Parameter %s is supported till podman "
"version %s only! Current version is %s"
% (param, minv, self.podman_version)
)
def addparam_gateway(self, c):
return c + ['--gateway', self.params['gateway']]
return c + ["--gateway", self.params["gateway"]]
def addparam_dns(self, c):
for dns in self.params['dns']:
c += ['--dns', dns]
for dns in self.params["dns"]:
c += ["--dns", dns]
return c
def addparam_driver(self, c):
return c + ['--driver', self.params['driver']]
return c + ["--driver", self.params["driver"]]
def addparam_subnet(self, c):
return c + ['--subnet', self.params['subnet']]
return c + ["--subnet", self.params["subnet"]]
def addparam_ip_range(self, c):
return c + ['--ip-range', self.params['ip_range']]
return c + ["--ip-range", self.params["ip_range"]]
def addparam_ipv6(self, c):
return c + ['--ipv6=%s' % self.params['ipv6']]
return c + ["--ipv6=%s" % self.params["ipv6"]]
def addparam_macvlan(self, c):
return c + ['--macvlan', self.params['macvlan']]
return c + ["--macvlan", self.params["macvlan"]]
def addparam_net_config(self, c):
for net in self.params['net_config']:
for kw in ('subnet', 'gateway', 'ip_range'):
for net in self.params["net_config"]:
for kw in ("subnet", "gateway", "ip_range"):
if kw in net and net[kw]:
c += ['--%s=%s' % (kw.replace('_', '-'), net[kw])]
c += ["--%s=%s" % (kw.replace("_", "-"), net[kw])]
return c
def addparam_interface_name(self, c):
return c + ['--interface-name', self.params['interface_name']]
return c + ["--interface-name", self.params["interface_name"]]
def addparam_internal(self, c):
return c + ['--internal=%s' % self.params['internal']]
return c + ["--internal=%s" % self.params["internal"]]
def addparam_opt(self, c):
for opt in self.params['opt'].items():
for opt in self.params["opt"].items():
if opt[1] is not None:
if opt[0] == 'bridge_name':
opt = ('com.docker.network.bridge.name', opt[1])
if opt[0] == 'driver_mtu':
opt = ('com.docker.network.driver.mtu', opt[1])
c += ['--opt',
b"=".join([to_bytes(k, errors='surrogate_or_strict')
for k in opt])]
if opt[0] == "bridge_name":
opt = ("com.docker.network.bridge.name", opt[1])
if opt[0] == "driver_mtu":
opt = ("com.docker.network.driver.mtu", opt[1])
c += [
"--opt",
b"=".join([to_bytes(k, errors="surrogate_or_strict") for k in opt]),
]
return c
def addparam_route(self, c):
for route in self.params['route']:
c += ['--route', route]
for route in self.params["route"]:
c += ["--route", route]
return c
def addparam_ipam_driver(self, c):
return c + ['--ipam-driver=%s' % self.params['ipam_driver']]
return c + ["--ipam-driver=%s" % self.params["ipam_driver"]]
def addparam_disable_dns(self, c):
return c + ['--disable-dns=%s' % self.params['disable_dns']]
return c + ["--disable-dns=%s" % self.params["disable_dns"]]
class PodmanNetworkDefaults:
@ -429,8 +443,8 @@ class PodmanNetworkDefaults:
self.module = module
self.version = podman_version
self.defaults = {
'driver': 'bridge',
'internal': False,
"driver": "bridge",
"internal": False,
}
def default_dict(self):
@ -445,13 +459,14 @@ class PodmanNetworkDiff:
self.default_dict = None
self.info = lower_keys(info)
self.params = self.defaultize()
self.diff = {'before': {}, 'after': {}}
self.diff = {"before": {}, "after": {}}
self.non_idempotent = {}
def defaultize(self):
params_with_defaults = {}
self.default_dict = PodmanNetworkDefaults(
self.module, self.version).default_dict()
self.module, self.version
).default_dict()
for p in self.module.params:
if self.module.params[p] is None and p in self.default_dict:
params_with_defaults[p] = self.default_dict[p]
@ -461,171 +476,189 @@ class PodmanNetworkDiff:
def _diff_update_and_compare(self, param_name, before, after):
if before != after:
self.diff['before'].update({param_name: before})
self.diff['after'].update({param_name: after})
self.diff["before"].update({param_name: before})
self.diff["after"].update({param_name: after})
return True
return False
def diffparam_disable_dns(self):
# For v3 it's impossible to find out DNS settings.
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
before = not self.info.get('dns_enabled', True)
after = self.params['disable_dns']
if LooseVersion(self.version) >= LooseVersion("4.0.0"):
before = not self.info.get("dns_enabled", True)
after = self.params["disable_dns"]
# compare only if set explicitly
if self.params['disable_dns'] is None:
if self.params["disable_dns"] is None:
after = before
return self._diff_update_and_compare('disable_dns', before, after)
before = after = self.params['disable_dns']
return self._diff_update_and_compare('disable_dns', before, after)
return self._diff_update_and_compare("disable_dns", before, after)
before = after = self.params["disable_dns"]
return self._diff_update_and_compare("disable_dns", before, after)
def diffparam_dns(self):
before = self.info.get('network_dns_servers', [])
after = self.params['dns'] or []
return self._diff_update_and_compare('dns', sorted(before), sorted(after))
before = self.info.get("network_dns_servers", [])
after = self.params["dns"] or []
return self._diff_update_and_compare("dns", sorted(before), sorted(after))
def diffparam_driver(self):
# Currently only bridge is supported
before = after = 'bridge'
return self._diff_update_and_compare('driver', before, after)
before = after = "bridge"
return self._diff_update_and_compare("driver", before, after)
def diffparam_ipv6(self):
# We don't support dual stack because it generates subnets randomly
return self._diff_update_and_compare('ipv6', '', '')
return self._diff_update_and_compare("ipv6", "", "")
def diffparam_gateway(self):
# Disable idempotency of subnet for v4, subnets are added automatically
# TODO(sshnaidm): check if it's still the issue in v5
if LooseVersion(self.version) < LooseVersion('4.0.0'):
if LooseVersion(self.version) < LooseVersion("4.0.0"):
try:
before = self.info['plugins'][0]['ipam']['ranges'][0][0]['gateway']
before = self.info["plugins"][0]["ipam"]["ranges"][0][0]["gateway"]
except (IndexError, KeyError):
before = ''
before = ""
after = before
if self.params['gateway'] is not None:
after = self.params['gateway']
return self._diff_update_and_compare('gateway', before, after)
if self.params["gateway"] is not None:
after = self.params["gateway"]
return self._diff_update_and_compare("gateway", before, after)
else:
before_subs = self.info.get('subnets')
after = self.params['gateway']
before_subs = self.info.get("subnets")
after = self.params["gateway"]
if not before_subs:
before = None
if before_subs:
if len(before_subs) > 1 and after:
return self._diff_update_and_compare(
'gateway', ",".join([i['gateway'] for i in before_subs]), after)
before = [i.get('gateway') for i in before_subs][0]
"gateway", ",".join([i["gateway"] for i in before_subs]), after
)
before = [i.get("gateway") for i in before_subs][0]
if not after:
after = before
return self._diff_update_and_compare('gateway', before, after)
return self._diff_update_and_compare("gateway", before, after)
def diffparam_internal(self):
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
before = self.info.get('internal', False)
after = self.params['internal']
return self._diff_update_and_compare('internal', before, after)
if LooseVersion(self.version) >= LooseVersion("4.0.0"):
before = self.info.get("internal", False)
after = self.params["internal"]
return self._diff_update_and_compare("internal", before, after)
try:
before = not self.info['plugins'][0]['isgateway']
before = not self.info["plugins"][0]["isgateway"]
except (IndexError, KeyError):
before = False
after = self.params['internal']
return self._diff_update_and_compare('internal', before, after)
after = self.params["internal"]
return self._diff_update_and_compare("internal", before, after)
def diffparam_ip_range(self):
# TODO(sshnaidm): implement IP to CIDR convert and vice versa
before = after = ''
return self._diff_update_and_compare('ip_range', before, after)
before = after = ""
return self._diff_update_and_compare("ip_range", before, after)
def diffparam_ipam_driver(self):
before = self.info.get("ipam_options", {}).get("driver", "")
after = self.params['ipam_driver']
after = self.params["ipam_driver"]
if not after:
after = before
return self._diff_update_and_compare('ipam_driver', before, after)
return self._diff_update_and_compare("ipam_driver", before, after)
def diffparam_net_config(self):
after = self.params['net_config']
after = self.params["net_config"]
if not after:
return self._diff_update_and_compare('net_config', '', '')
before_subs = self.info.get('subnets', [])
return self._diff_update_and_compare("net_config", "", "")
before_subs = self.info.get("subnets", [])
if before_subs:
before = ":".join(sorted([",".join([i['subnet'], i['gateway']]).rstrip(",") for i in before_subs]))
before = ":".join(
sorted(
[
",".join([i["subnet"], i["gateway"]]).rstrip(",")
for i in before_subs
]
)
)
else:
before = ''
after = ":".join(sorted([",".join([i['subnet'], i['gateway']]).rstrip(",") for i in after]))
return self._diff_update_and_compare('net_config', before, after)
before = ""
after = ":".join(
sorted([",".join([i["subnet"], i["gateway"]]).rstrip(",") for i in after])
)
return self._diff_update_and_compare("net_config", before, after)
def diffparam_route(self):
routes = self.info.get('routes', [])
routes = self.info.get("routes", [])
if routes:
before = [",".join([
r['destination'], r['gateway'], str(r.get('metric', ''))]).rstrip(",") for r in routes]
before = [
",".join(
[r["destination"], r["gateway"], str(r.get("metric", ""))]
).rstrip(",")
for r in routes
]
else:
before = []
after = self.params['route'] or []
return self._diff_update_and_compare('route', sorted(before), sorted(after))
after = self.params["route"] or []
return self._diff_update_and_compare("route", sorted(before), sorted(after))
def diffparam_subnet(self):
# Disable idempotency of subnet for v3 and below
if LooseVersion(self.version) < LooseVersion('4.0.0'):
if LooseVersion(self.version) < LooseVersion("4.0.0"):
try:
before = self.info['plugins'][0]['ipam']['ranges'][0][0]['subnet']
before = self.info["plugins"][0]["ipam"]["ranges"][0][0]["subnet"]
except (IndexError, KeyError):
before = ''
before = ""
after = before
if self.params['subnet'] is not None:
after = self.params['subnet']
if self.params["subnet"] is not None:
after = self.params["subnet"]
if HAS_IP_ADDRESS_MODULE:
after = ipaddress.ip_network(after).compressed
return self._diff_update_and_compare('subnet', before, after)
return self._diff_update_and_compare("subnet", before, after)
else:
if self.params['ipv6'] is not None:
if self.params["ipv6"] is not None:
# We can't support dual stack, it generates subnets randomly
return self._diff_update_and_compare('subnet', '', '')
after = self.params['subnet']
return self._diff_update_and_compare("subnet", "", "")
after = self.params["subnet"]
if after is None:
# We can't guess what subnet was used before by default
return self._diff_update_and_compare('subnet', '', '')
before = self.info.get('subnets')
return self._diff_update_and_compare("subnet", "", "")
before = self.info.get("subnets")
if before:
if len(before) > 1 and after:
return self._diff_update_and_compare('subnet', ",".join([i['subnet'] for i in before]), after)
before = [i['subnet'] for i in before][0]
return self._diff_update_and_compare('subnet', before, after)
return self._diff_update_and_compare(
"subnet", ",".join([i["subnet"] for i in before]), after
)
before = [i["subnet"] for i in before][0]
return self._diff_update_and_compare("subnet", before, after)
def diffparam_macvlan(self):
before = after = ''
return self._diff_update_and_compare('macvlan', before, after)
before = after = ""
return self._diff_update_and_compare("macvlan", before, after)
def diffparam_opt(self):
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
vlan_before = self.info.get('options', {}).get('vlan')
if LooseVersion(self.version) >= LooseVersion("4.0.0"):
vlan_before = self.info.get("options", {}).get("vlan")
else:
try:
vlan_before = self.info['plugins'][0].get('vlan')
vlan_before = self.info["plugins"][0].get("vlan")
except (IndexError, KeyError):
vlan_before = None
vlan_after = self.params['opt'].get('vlan') if self.params['opt'] else None
vlan_after = self.params["opt"].get("vlan") if self.params["opt"] else None
if vlan_before or vlan_after:
before, after = {'vlan': str(vlan_before)}, {'vlan': str(vlan_after)}
before, after = {"vlan": str(vlan_before)}, {"vlan": str(vlan_after)}
else:
before, after = {}, {}
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
mtu_before = self.info.get('options', {}).get('mtu')
if LooseVersion(self.version) >= LooseVersion("4.0.0"):
mtu_before = self.info.get("options", {}).get("mtu")
else:
try:
mtu_before = self.info['plugins'][0].get('mtu')
mtu_before = self.info["plugins"][0].get("mtu")
except (IndexError, KeyError):
mtu_before = None
mtu_after = self.params['opt'].get('mtu') if self.params['opt'] else None
mtu_after = self.params["opt"].get("mtu") if self.params["opt"] else None
if mtu_before or mtu_after:
before.update({'mtu': str(mtu_before)})
after.update({'mtu': str(mtu_after)})
return self._diff_update_and_compare('opt', before, after)
before.update({"mtu": str(mtu_before)})
after.update({"mtu": str(mtu_after)})
return self._diff_update_and_compare("opt", before, after)
def is_different(self):
diff_func_list = [func for func in dir(self)
if callable(getattr(self, func)) and func.startswith(
"diffparam")]
diff_func_list = [
func
for func in dir(self)
if callable(getattr(self, func)) and func.startswith("diffparam")
]
fail_fast = not bool(self.module._diff)
different = False
for func_name in diff_func_list:
@ -636,7 +669,11 @@ class PodmanNetworkDiff:
different = True
# Check non idempotent parameters
for p in self.non_idempotent:
if self.module.params[p] is not None and self.module.params[p] not in [{}, [], '']:
if self.module.params[p] is not None and self.module.params[p] not in [
{},
[],
"",
]:
different = True
return different
@ -658,7 +695,7 @@ class PodmanNetwork:
super(PodmanNetwork, self).__init__()
self.module = module
self.name = name
self.stdout, self.stderr = '', ''
self.stdout, self.stderr = "", ""
self.info = self.get_info()
self.version = self._get_podman_version()
self.diff = {}
@ -672,35 +709,41 @@ class PodmanNetwork:
@property
def different(self):
"""Check if network is different."""
diffcheck = PodmanNetworkDiff(
self.module,
self.info,
self.version)
diffcheck = PodmanNetworkDiff(self.module, self.info, self.version)
is_different = diffcheck.is_different()
diffs = diffcheck.diff
if self.module._diff and is_different and diffs['before'] and diffs['after']:
self.diff['before'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['before'].items())]) + "\n"
self.diff['after'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['after'].items())]) + "\n"
if self.module._diff and is_different and diffs["before"] and diffs["after"]:
self.diff["before"] = (
"\n".join(
["%s - %s" % (k, v) for k, v in sorted(diffs["before"].items())]
)
+ "\n"
)
self.diff["after"] = (
"\n".join(
["%s - %s" % (k, v) for k, v in sorted(diffs["after"].items())]
)
+ "\n"
)
return is_different
def get_info(self):
"""Inspect network and gather info about it."""
# pylint: disable=unused-variable
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'network', b'inspect', self.name])
[self.module.params["executable"], b"network", b"inspect", self.name]
)
return json.loads(out)[0] if rc == 0 else {}
def _get_podman_version(self):
# pylint: disable=unused-variable
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'--version'])
[self.module.params["executable"], b"--version"]
)
if rc != 0 or not out or "version" not in out:
self.module.fail_json(msg="%s run failed!" %
self.module.params['executable'])
self.module.fail_json(
msg="%s run failed!" % self.module.params["executable"]
)
return out.split("version")[1].strip()
def _perform_action(self, action):
@ -709,33 +752,39 @@ class PodmanNetwork:
Arguments:
action {str} -- action to perform - create, stop, delete
"""
b_command = PodmanNetworkModuleParams(action,
self.module.params,
self.version,
self.module,
).construct_command_from_params()
full_cmd = " ".join([self.module.params['executable'], 'network']
+ [to_native(i) for i in b_command])
b_command = PodmanNetworkModuleParams(
action,
self.module.params,
self.version,
self.module,
).construct_command_from_params()
full_cmd = " ".join(
[self.module.params["executable"], "network"]
+ [to_native(i) for i in b_command]
)
self.module.log("PODMAN-NETWORK-DEBUG: %s" % full_cmd)
self.actions.append(full_cmd)
if not self.module.check_mode:
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'network'] + b_command,
expand_user_and_vars=False)
[self.module.params["executable"], b"network"] + b_command,
expand_user_and_vars=False,
)
self.stdout = out
self.stderr = err
if rc != 0:
self.module.fail_json(
msg="Can't %s network %s" % (action, self.name),
stdout=out, stderr=err)
stdout=out,
stderr=err,
)
def delete(self):
"""Delete the network."""
self._perform_action('delete')
self._perform_action("delete")
def create(self):
"""Create the network."""
self._perform_action('create')
self._perform_action("create")
def recreate(self):
"""Recreate the network."""
@ -760,16 +809,16 @@ class PodmanNetworkManager:
self.module = module
self.results = {
'changed': False,
'actions': [],
'network': {},
"changed": False,
"actions": [],
"network": {},
}
self.name = self.module.params['name']
self.executable = \
self.module.get_bin_path(self.module.params['executable'],
required=True)
self.state = self.module.params['state']
self.recreate = self.module.params['recreate']
self.name = self.module.params["name"]
self.executable = self.module.get_bin_path(
self.module.params["executable"], required=True
)
self.state = self.module.params["state"]
self.recreate = self.module.params["recreate"]
self.network = PodmanNetwork(self.module, self.name)
def update_network_result(self, changed=True):
@ -781,37 +830,43 @@ class PodmanNetworkManager:
"""
facts = self.network.get_info() if changed else self.network.info
out, err = self.network.stdout, self.network.stderr
self.results.update({'changed': changed, 'network': facts,
'podman_actions': self.network.actions},
stdout=out, stderr=err)
self.results.update(
{
"changed": changed,
"network": facts,
"podman_actions": self.network.actions,
},
stdout=out,
stderr=err,
)
if self.network.diff:
self.results.update({'diff': self.network.diff})
if self.module.params['debug']:
self.results.update({'podman_version': self.network.version})
self.results.update({"diff": self.network.diff})
if self.module.params["debug"]:
self.results.update({"podman_version": self.network.version})
self.module.exit_json(**self.results)
def execute(self):
"""Execute the desired action according to map of actions & states."""
states_map = {
'present': self.make_present,
'absent': self.make_absent,
'quadlet': self.make_quadlet,
"present": self.make_present,
"absent": self.make_absent,
"quadlet": self.make_quadlet,
}
process_action = states_map[self.state]
process_action()
self.module.fail_json(msg="Unexpected logic error happened, "
"please contact maintainers ASAP!")
self.module.fail_json(
msg="Unexpected logic error happened, " "please contact maintainers ASAP!"
)
def make_present(self):
"""Run actions if desired state is 'started'."""
if not self.network.exists:
self.network.create()
self.results['actions'].append('created %s' % self.network.name)
self.results["actions"].append("created %s" % self.network.name)
self.update_network_result()
elif self.recreate or self.network.different:
self.network.recreate()
self.results['actions'].append('recreated %s' %
self.network.name)
self.results["actions"].append("recreated %s" % self.network.name)
self.update_network_result()
else:
self.update_network_result(changed=False)
@ -819,13 +874,12 @@ class PodmanNetworkManager:
def make_absent(self):
"""Run actions if desired state is 'absent'."""
if not self.network.exists:
self.results.update({'changed': False})
self.results.update({"changed": False})
elif self.network.exists:
self.network.delete()
self.results['actions'].append('deleted %s' % self.network.name)
self.results.update({'changed': True})
self.results.update({'network': {},
'podman_actions': self.network.actions})
self.results["actions"].append("deleted %s" % self.network.name)
self.results.update({"changed": True})
self.results.update({"network": {}, "podman_actions": self.network.actions})
self.module.exit_json(**self.results)
def make_quadlet(self):
@ -837,60 +891,70 @@ class PodmanNetworkManager:
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', default="present",
choices=['present', 'absent', 'quadlet']),
name=dict(type='str', required=True),
disable_dns=dict(type='bool', required=False),
dns=dict(type='list', elements='str', required=False),
driver=dict(type='str', required=False),
force=dict(type='bool', default=False),
gateway=dict(type='str', required=False),
interface_name=dict(type='str', required=False),
internal=dict(type='bool', required=False),
ip_range=dict(type='str', required=False),
ipam_driver=dict(type='str', required=False,
choices=['host-local', 'dhcp', 'none']),
ipv6=dict(type='bool', required=False),
subnet=dict(type='str', required=False),
macvlan=dict(type='str', required=False),
opt=dict(type='dict', required=False,
options=dict(
isolate=dict(type='bool', required=False),
mtu=dict(type='int', required=False),
metric=dict(type='int', required=False),
mode=dict(type='str', required=False),
parent=dict(type='str', required=False),
vlan=dict(type='int', required=False),
bclim=dict(type='int', required=False),
no_default_route=dict(type='str', required=False),
vrf=dict(type='str', required=False),
bridge_name=dict(type='str', required=False),
driver_mtu=dict(type='str', required=False),
)),
executable=dict(type='str', required=False, default='podman'),
debug=dict(type='bool', default=False),
recreate=dict(type='bool', default=False),
route=dict(type='list', elements='str', required=False),
quadlet_dir=dict(type='path', required=False),
quadlet_filename=dict(type='str', required=False),
quadlet_file_mode=dict(type='raw', required=False),
quadlet_options=dict(type='list', elements='str', required=False),
net_config=dict(type='list', required=False, elements='dict',
options=dict(
subnet=dict(type='str', required=True),
gateway=dict(type='str', required=True),
ip_range=dict(type='str', required=False),
)),
state=dict(
type="str", default="present", choices=["present", "absent", "quadlet"]
),
name=dict(type="str", required=True),
disable_dns=dict(type="bool", required=False),
dns=dict(type="list", elements="str", required=False),
driver=dict(type="str", required=False),
force=dict(type="bool", default=False),
gateway=dict(type="str", required=False),
interface_name=dict(type="str", required=False),
internal=dict(type="bool", required=False),
ip_range=dict(type="str", required=False),
ipam_driver=dict(
type="str", required=False, choices=["host-local", "dhcp", "none"]
),
ipv6=dict(type="bool", required=False),
subnet=dict(type="str", required=False),
macvlan=dict(type="str", required=False),
opt=dict(
type="dict",
required=False,
options=dict(
isolate=dict(type="bool", required=False),
mtu=dict(type="int", required=False),
metric=dict(type="int", required=False),
mode=dict(type="str", required=False),
parent=dict(type="str", required=False),
vlan=dict(type="int", required=False),
bclim=dict(type="int", required=False),
no_default_route=dict(type="str", required=False),
vrf=dict(type="str", required=False),
bridge_name=dict(type="str", required=False),
driver_mtu=dict(type="str", required=False),
),
),
executable=dict(type="str", required=False, default="podman"),
debug=dict(type="bool", default=False),
recreate=dict(type="bool", default=False),
route=dict(type="list", elements="str", required=False),
quadlet_dir=dict(type="path", required=False),
quadlet_filename=dict(type="str", required=False),
quadlet_file_mode=dict(type="raw", required=False),
quadlet_options=dict(type="list", elements="str", required=False),
net_config=dict(
type="list",
required=False,
elements="dict",
options=dict(
subnet=dict(type="str", required=True),
gateway=dict(type="str", required=True),
ip_range=dict(type="str", required=False),
),
),
),
required_by=dict( # for IP range and GW to set 'subnet' is required
ip_range=('subnet'),
gateway=('subnet'),
ip_range=("subnet"),
gateway=("subnet"),
),
# define or subnet or net config
mutually_exclusive=[['subnet', 'net_config']])
mutually_exclusive=[["subnet", "net_config"]],
)
PodmanNetworkManager(module).execute()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,6 +3,7 @@
# 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
@ -82,9 +83,9 @@ from ansible.module_utils.basic import AnsibleModule
def get_network_info(module, executable, name):
command = [executable, 'network', 'inspect']
command = [executable, "network", "inspect"]
if not name:
all_names = [executable, 'network', 'ls', '-q']
all_names = [executable, "network", "ls", "-q"]
rc, out, err = module.run_command(all_names)
if rc != 0:
module.fail_json(msg="Unable to get list of networks: %s" % err)
@ -95,7 +96,7 @@ def get_network_info(module, executable, name):
else:
command.append(name)
rc, out, err = module.run_command(command)
if rc != 0 or 'unable to find network configuration' in err:
if rc != 0 or "unable to find network configuration" in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (name, err))
if not out or json.loads(out) is None:
return [], out, err
@ -105,25 +106,20 @@ def get_network_info(module, executable, name):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='str')
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)
name = module.params["name"]
executable = module.get_bin_path(module.params["executable"], required=True)
inspect_results, out, err = get_network_info(module, executable, name)
results = {
"changed": False,
"networks": inspect_results,
"stderr": err
}
results = {"changed": False, "networks": inspect_results, "stderr": err}
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_play
author:
- "Sagi Shnaidman (@sshnaidm)"
@ -195,9 +195,9 @@ options:
type: list
elements: str
required: false
'''
"""
EXAMPLES = '''
EXAMPLES = """
- name: Play kube file
containers.podman.podman_play:
kube_file: ~/kube.yaml
@ -229,17 +229,24 @@ EXAMPLES = '''
quadlet_options:
- "SetWorkingDirectory=yaml"
- "ExitCodePropagation=any"
'''
"""
import re # noqa: F402
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
from ansible.module_utils.basic import AnsibleModule # noqa: F402
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion, get_podman_version
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import create_quadlet_state # noqa: F402
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
LooseVersion,
get_podman_version,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import (
create_quadlet_state,
) # noqa: F402
class PodmanKubeManagement:
@ -248,115 +255,127 @@ class PodmanKubeManagement:
self.module = module
self.actions = []
self.executable = executable
self.command = [self.executable, 'play', 'kube']
self.command = [self.executable, "play", "kube"]
self.version = get_podman_version(module)
creds = []
# pod_name = extract_pod_name(module.params['kube_file'])
if self.module.params['annotation']:
for k, v in self.module.params['annotation'].items():
self.command.extend(['--annotation', '{k}={v}'.format(k=k, v=v)])
if self.module.params['username']:
creds += [self.module.params['username']]
if self.module.params['password']:
creds += [self.module.params['password']]
if self.module.params["annotation"]:
for k, v in self.module.params["annotation"].items():
self.command.extend(["--annotation", "{k}={v}".format(k=k, v=v)])
if self.module.params["username"]:
creds += [self.module.params["username"]]
if self.module.params["password"]:
creds += [self.module.params["password"]]
creds = ":".join(creds)
self.command.extend(['--creds=%s' % creds])
if self.module.params['network']:
networks = ",".join(self.module.params['network'])
self.command.extend(['--network=%s' % networks])
if self.module.params['configmap']:
configmaps = ",".join(self.module.params['configmap'])
self.command.extend(['--configmap=%s' % configmaps])
if self.module.params['log_opt']:
for k, v in self.module.params['log_opt'].items():
self.command.extend(['--log-opt', '{k}={v}'.format(k=k.replace('_', '-'), v=v)])
start = self.module.params['state'] == 'started'
self.command.extend(['--start=%s' % str(start).lower()])
self.command.extend(["--creds=%s" % creds])
if self.module.params["network"]:
networks = ",".join(self.module.params["network"])
self.command.extend(["--network=%s" % networks])
if self.module.params["configmap"]:
configmaps = ",".join(self.module.params["configmap"])
self.command.extend(["--configmap=%s" % configmaps])
if self.module.params["log_opt"]:
for k, v in self.module.params["log_opt"].items():
self.command.extend(
["--log-opt", "{k}={v}".format(k=k.replace("_", "-"), v=v)]
)
start = self.module.params["state"] == "started"
self.command.extend(["--start=%s" % str(start).lower()])
for arg, param in {
'--authfile': 'authfile',
'--build': 'build',
'--cert-dir': 'cert_dir',
'--context-dir': 'context_dir',
'--log-driver': 'log_driver',
'--seccomp-profile-root': 'seccomp_profile_root',
'--tls-verify': 'tls_verify',
'--log-level': 'log_level',
'--userns': 'userns',
'--quiet': 'quiet',
"--authfile": "authfile",
"--build": "build",
"--cert-dir": "cert_dir",
"--context-dir": "context_dir",
"--log-driver": "log_driver",
"--seccomp-profile-root": "seccomp_profile_root",
"--tls-verify": "tls_verify",
"--log-level": "log_level",
"--userns": "userns",
"--quiet": "quiet",
}.items():
if self.module.params[param] is not None:
self.command += ["%s=%s" % (arg, self.module.params[param])]
if self.module.params['kube_file']:
self.command += [self.module.params['kube_file']]
elif self.module.params['kube_file_content']:
self.command += ['-']
if self.module.params["kube_file"]:
self.command += [self.module.params["kube_file"]]
elif self.module.params["kube_file_content"]:
self.command += ["-"]
def _command_run(self, cmd):
if self.module.params['kube_file_content']:
rc, out, err = self.module.run_command(cmd, data=self.module.params['kube_file_content'])
if self.module.params["kube_file_content"]:
rc, out, err = self.module.run_command(
cmd, data=self.module.params["kube_file_content"]
)
else:
rc, out, err = self.module.run_command(cmd)
self.actions.append(" ".join(cmd))
if self.module.params['debug']:
self.module.log('PODMAN-PLAY-KUBE command: %s' % " ".join(cmd))
self.module.log('PODMAN-PLAY-KUBE stdout: %s' % out)
self.module.log('PODMAN-PLAY-KUBE stderr: %s' % err)
self.module.log('PODMAN-PLAY-KUBE rc: %s' % rc)
if self.module.params["debug"]:
self.module.log("PODMAN-PLAY-KUBE command: %s" % " ".join(cmd))
self.module.log("PODMAN-PLAY-KUBE stdout: %s" % out)
self.module.log("PODMAN-PLAY-KUBE stderr: %s" % err)
self.module.log("PODMAN-PLAY-KUBE rc: %s" % rc)
return rc, out, err
def tear_down_pods(self):
'''
"""
Tear down the pod and contaiers by using --down option in kube play
which is supported since Podman 3.4.0
'''
"""
changed = False
kube_file = self.module.params['kube_file']
kube_file_content = self.module.params['kube_file_content']
kube_file = self.module.params["kube_file"]
kube_file_content = self.module.params["kube_file_content"]
if kube_file:
rc, out, err = self._command_run([self.executable, "kube", "play", "--down", kube_file])
rc, out, err = self._command_run(
[self.executable, "kube", "play", "--down", kube_file]
)
elif kube_file_content:
rc, out, err = self._command_run([self.executable, "kube", "play", "--down", "-"])
rc, out, err = self._command_run(
[self.executable, "kube", "play", "--down", "-"]
)
if rc != 0 and "no such pod" in err:
changed = False
return changed, out, err
if rc != 0:
self.module.fail_json(msg="Failed to delete Pod with %s: %s %s" % (
kube_file if kube_file else "YAML content", out, err))
self.module.fail_json(
msg="Failed to delete Pod with %s: %s %s"
% (kube_file if kube_file else "YAML content", out, err)
)
# hack to check if no resources are deleted
for line in out.splitlines():
if line and not line.endswith(':'):
if line and not line.endswith(":"):
changed = True
break
return changed, out, err
def discover_pods(self):
pod_name = ''
if self.module.params['kube_file']:
pod_name = ""
if self.module.params["kube_file"]:
if HAS_YAML:
with open(self.module.params['kube_file']) as f:
with open(self.module.params["kube_file"]) as f:
pods = list(yaml.safe_load_all(f))
for pod in pods:
if 'metadata' in pod and pod['kind'] in ['Deployment', 'Pod']:
pod_name = pod['metadata'].get('name')
if "metadata" in pod and pod["kind"] in ["Deployment", "Pod"]:
pod_name = pod["metadata"].get("name")
else:
with open(self.module.params['kube_file']) as text:
with open(self.module.params["kube_file"]) as text:
# the following formats are matched for a kube name:
# should match name field within metadata (2 or 4 spaces in front of name)
# the name can be written without quotes, in single or double quotes
# the name can contain -_
re_pod_name = re.compile(r'^\s{2,4}name: ["|\']?(?P<pod_name>[\w|\-|\_]+)["|\']?', re.MULTILINE)
re_pod_name = re.compile(
r'^\s{2,4}name: ["|\']?(?P<pod_name>[\w|\-|\_]+)["|\']?',
re.MULTILINE,
)
re_pod = re_pod_name.search(text.read())
if re_pod:
pod_name = re_pod.group(1)
if not pod_name:
self.module.fail_json("This Kube file doesn't have Pod or Deployment!")
# Find all pods
all_pods = ''
all_pods = ""
# In case of one pod or replicasets
for name in ("name=%s$", "name=%s-pod-*"):
cmd = [self.executable,
"pod", "ps", "-q", "--filter", name % pod_name]
cmd = [self.executable, "pod", "ps", "-q", "--filter", name % pod_name]
rc, out, err = self._command_run(cmd)
all_pods += out
ids = list(set([i for i in all_pods.splitlines() if i]))
@ -364,11 +383,12 @@ class PodmanKubeManagement:
def remove_associated_pods(self, pods):
changed = False
out_all, err_all = '', ''
out_all, err_all = "", ""
# Delete all pods
for pod_id in pods:
rc, out, err = self._command_run(
[self.executable, "pod", "rm", "-f", pod_id])
[self.executable, "pod", "rm", "-f", pod_id]
)
if rc != 0:
self.module.fail_json("Can NOT delete Pod %s" % pod_id)
else:
@ -378,7 +398,9 @@ class PodmanKubeManagement:
return changed, out_all, err_all
def pod_recreate(self):
if self.version is not None and LooseVersion(self.version) >= LooseVersion('3.4.0'):
if self.version is not None and LooseVersion(self.version) >= LooseVersion(
"3.4.0"
):
self.tear_down_pods()
else:
pods = self.discover_pods()
@ -392,14 +414,15 @@ class PodmanKubeManagement:
def play(self):
rc, out, err = self._command_run(self.command)
if rc != 0 and 'pod already exists' in err:
if self.module.params['recreate']:
if rc != 0 and "pod already exists" in err:
if self.module.params["recreate"]:
out, err = self.pod_recreate()
changed = True
else:
changed = False
err = "\n".join([
i for i in err.splitlines() if 'pod already exists' not in i])
err = "\n".join(
[i for i in err.splitlines() if "pod already exists" not in i]
)
elif rc != 0:
self.module.fail_json(msg="Output: %s\nError=%s" % (out, err))
else:
@ -416,63 +439,73 @@ class PodmanKubeManagement:
def main():
module = AnsibleModule(
argument_spec=dict(
annotation=dict(type='dict', aliases=['annotations']),
executable=dict(type='str', default='podman'),
kube_file=dict(type='path'),
kube_file_content=dict(type='str'),
authfile=dict(type='path'),
build=dict(type='bool'),
cert_dir=dict(type='path'),
configmap=dict(type='list', elements='path'),
context_dir=dict(type='path'),
seccomp_profile_root=dict(type='path'),
username=dict(type='str'),
password=dict(type='str', no_log=True),
log_driver=dict(type='str'),
log_opt=dict(type='dict', aliases=['log_options'], options=dict(
path=dict(type='str'),
max_size=dict(type='str'),
tag=dict(type='str'))),
network=dict(type='list', elements='str'),
annotation=dict(type="dict", aliases=["annotations"]),
executable=dict(type="str", default="podman"),
kube_file=dict(type="path"),
kube_file_content=dict(type="str"),
authfile=dict(type="path"),
build=dict(type="bool"),
cert_dir=dict(type="path"),
configmap=dict(type="list", elements="path"),
context_dir=dict(type="path"),
seccomp_profile_root=dict(type="path"),
username=dict(type="str"),
password=dict(type="str", no_log=True),
log_driver=dict(type="str"),
log_opt=dict(
type="dict",
aliases=["log_options"],
options=dict(
path=dict(type="str"),
max_size=dict(type="str"),
tag=dict(type="str"),
),
),
network=dict(type="list", elements="str"),
state=dict(
type='str',
choices=['started', 'created', 'absent', 'quadlet'],
required=True),
tls_verify=dict(type='bool'),
debug=dict(type='bool'),
quiet=dict(type='bool'),
recreate=dict(type='bool'),
userns=dict(type='str'),
type="str",
choices=["started", "created", "absent", "quadlet"],
required=True,
),
tls_verify=dict(type="bool"),
debug=dict(type="bool"),
quiet=dict(type="bool"),
recreate=dict(type="bool"),
userns=dict(type="str"),
log_level=dict(
type='str',
choices=["debug", "info", "warn", "error", "fatal", "panic"]),
quadlet_dir=dict(type='path', required=False),
quadlet_filename=dict(type='str', required=False),
quadlet_file_mode=dict(type='raw', required=False),
quadlet_options=dict(type='list', elements='str', required=False),
type="str", choices=["debug", "info", "warn", "error", "fatal", "panic"]
),
quadlet_dir=dict(type="path", required=False),
quadlet_filename=dict(type="str", required=False),
quadlet_file_mode=dict(type="raw", required=False),
quadlet_options=dict(type="list", elements="str", required=False),
),
supports_check_mode=True,
required_if=[
('state', 'quadlet', ['quadlet_filename']),
("state", "quadlet", ["quadlet_filename"]),
],
required_one_of=[
('kube_file', 'kube_file_content'),
("kube_file", "kube_file_content"),
],
)
executable = module.get_bin_path(
module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
manage = PodmanKubeManagement(module, executable)
changed = False
out = err = ''
if module.params['state'] == 'absent':
if manage.version is not None and LooseVersion(manage.version) > LooseVersion('3.4.0'):
manage.module.log(msg="version: %s, kube file %s" % (manage.version, manage.module.params['kube_file']))
out = err = ""
if module.params["state"] == "absent":
if manage.version is not None and LooseVersion(manage.version) > LooseVersion(
"3.4.0"
):
manage.module.log(
msg="version: %s, kube file %s"
% (manage.version, manage.module.params["kube_file"])
)
changed, out, err = manage.tear_down_pods()
else:
pods = manage.discover_pods()
changed, out, err = manage.remove_associated_pods(pods)
elif module.params['state'] == 'quadlet':
elif module.params["state"] == "quadlet":
manage.make_quadlet()
else:
changed, out, err = manage.play()
@ -480,10 +513,10 @@ def main():
"changed": changed,
"stdout": out,
"stderr": err,
"actions": manage.actions
"actions": manage.actions,
}
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -4,9 +4,10 @@
# flake8: noqa: E501
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
DOCUMENTATION = """
---
module: podman_pod
short_description: Manage Podman pods
@ -500,9 +501,9 @@ options:
requirements:
- "podman"
'''
"""
RETURN = r'''
RETURN = r"""
pod:
description: Pod inspection results for the given pod
built.
@ -611,9 +612,9 @@ pod:
],
"LockNumber": 1
}
'''
"""
EXAMPLES = r'''
EXAMPLES = r"""
# What modules does for example
- containers.podman.podman_pod:
name: pod1
@ -693,7 +694,7 @@ EXAMPLES = r'''
volume:
- /var/run/docker.sock:/var/run/docker.sock
quadlet_dir: /custom/dir
'''
"""
from ansible.module_utils.basic import AnsibleModule # noqa: F402
from ..module_utils.podman.podman_pod_lib import PodmanPodManager # noqa: F402
from ..module_utils.podman.podman_pod_lib import ARGUMENTS_SPEC_POD # noqa: F402
@ -705,5 +706,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,6 +3,7 @@
# 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
@ -155,13 +156,13 @@ from ansible.module_utils.basic import AnsibleModule
def get_pod_info(module, executable, name):
command = [executable, 'pod', 'inspect']
command = [executable, "pod", "inspect"]
pods = [name]
result = []
errs = []
rcs = []
if not name:
all_names = [executable, 'pod', 'ls', '-q']
all_names = [executable, "pod", "ls", "-q"]
rc, out, err = module.run_command(all_names)
if rc != 0:
module.fail_json(msg="Unable to get list of pods: %s" % err)
@ -185,14 +186,13 @@ def get_pod_info(module, executable, name):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='str')
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)
name = module.params["name"]
executable = module.get_bin_path(module.params["executable"], required=True)
inspect_results, errs, rcs = get_pod_info(module, executable, name)
@ -208,5 +208,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -5,9 +5,10 @@
# Copyright (c) 2023, Roberto Alfieri <ralfieri@redhat.com>
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_prune
author:
- 'Roberto Alfieri (@rebtoor)'
@ -91,9 +92,9 @@ options:
https://docs.podman.io/en/latest/markdown/podman-volume-prune.1.html#filter)
for more information on possible filters.
type: dict
'''
"""
EXAMPLES = r'''
EXAMPLES = r"""
- name: Prune containers older than 24h
containers.podman.podman_prune:
containers: true
@ -110,9 +111,9 @@ EXAMPLES = r'''
system: true
system_all: true
system_volumes: true
'''
"""
RETURN = r'''
RETURN = r"""
# containers
containers:
description:
@ -157,7 +158,7 @@ system:
type: list
elements: str
sample: []
'''
"""
from ansible.module_utils.basic import AnsibleModule
@ -165,7 +166,7 @@ from ansible.module_utils.basic import AnsibleModule
def filtersPrepare(target, filters):
filter_out = []
if target == 'system':
if target == "system":
for system_filter in filters:
filter_out.append(filters[system_filter])
else:
@ -173,23 +174,36 @@ def filtersPrepare(target, filters):
if isinstance(filters[common_filter], dict):
dict_filters = filters[common_filter]
for single_filter in dict_filters:
filter_out.append('--filter={label}={key}={value}'.format(label=common_filter, key=single_filter,
value=dict_filters[single_filter]))
filter_out.append(
"--filter={label}={key}={value}".format(
label=common_filter,
key=single_filter,
value=dict_filters[single_filter],
)
)
else:
if target == 'image' and (common_filter in ('dangling_only', 'external')):
if common_filter == 'dangling_only' and not filters['dangling_only']:
filter_out.append('-a')
if common_filter == 'external' and filters['external']:
filter_out.append('--external')
if target == "image" and (
common_filter in ("dangling_only", "external")
):
if (
common_filter == "dangling_only"
and not filters["dangling_only"]
):
filter_out.append("-a")
if common_filter == "external" and filters["external"]:
filter_out.append("--external")
else:
filter_out.append('--filter={label}={value}'.format(label=common_filter,
value=filters[common_filter]))
filter_out.append(
"--filter={label}={value}".format(
label=common_filter, value=filters[common_filter]
)
)
return filter_out
def podmanExec(module, target, filters, executable):
command = [executable, target, 'prune', '--force']
command = [executable, target, "prune", "--force"]
if filters is not None:
command.extend(filtersPrepare(target, filters))
rc, out, err = module.run_command(command)
@ -197,56 +211,61 @@ def podmanExec(module, target, filters, executable):
if rc != 0:
module.fail_json(
msg='Error executing prune on {target}: {err}'.format(target=target, err=err))
msg="Error executing prune on {target}: {err}".format(
target=target, err=err
)
)
return {
"changed": changed,
target: list(filter(None, out.split('\n'))),
"errors": err
target: list(filter(None, out.split("\n"))),
"errors": err,
}
def main():
results = dict()
module_args = dict(
container=dict(type='bool', default=False),
container_filters=dict(type='dict'),
image=dict(type='bool', default=False),
image_filters=dict(type='dict'),
network=dict(type='bool', default=False),
network_filters=dict(type='dict'),
volume=dict(type='bool', default=False),
volume_filters=dict(type='dict'),
system=dict(type='bool', default=False),
system_all=dict(type='bool', default=False),
system_volumes=dict(type='bool', default=False),
executable=dict(type='str', default='podman')
container=dict(type="bool", default=False),
container_filters=dict(type="dict"),
image=dict(type="bool", default=False),
image_filters=dict(type="dict"),
network=dict(type="bool", default=False),
network_filters=dict(type="dict"),
volume=dict(type="bool", default=False),
volume_filters=dict(type="dict"),
system=dict(type="bool", default=False),
system_all=dict(type="bool", default=False),
system_volumes=dict(type="bool", default=False),
executable=dict(type="str", default="podman"),
)
module = AnsibleModule(
argument_spec=module_args
)
module = AnsibleModule(argument_spec=module_args)
executable = module.get_bin_path(
module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
for target, filters in (
('container', 'container_filters'), ('image', 'image_filters'), ('network', 'network_filters'),
('volume', 'volume_filters')):
("container", "container_filters"),
("image", "image_filters"),
("network", "network_filters"),
("volume", "volume_filters"),
):
if module.params[target]:
results[target] = podmanExec(module, target, module.params[filters], executable)
results[target] = podmanExec(
module, target, module.params[filters], executable
)
if module.params['system']:
target = 'system'
if module.params["system"]:
target = "system"
system_filters = {}
if module.params['system_all']:
system_filters['system_all'] = '--all'
if module.params['system_volumes']:
system_filters['system_volumes'] = '--volumes'
if module.params["system_all"]:
system_filters["system_all"] = "--all"
if module.params["system_volumes"]:
system_filters["system_volumes"] = "--volumes"
results[target] = podmanExec(module, target, system_filters, executable)
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_runlabel
short_description: Run given label from given image
author: Pavel Dostal (@pdostal)
@ -32,55 +32,53 @@ options:
type: str
requirements:
- "Podman installed on host"
'''
"""
RETURN = '''
'''
RETURN = """
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- containers.podman.podman_runlabel:
image: docker.io/continuumio/miniconda3
label: INSTALL
'''
"""
from ansible.module_utils.basic import AnsibleModule # noqa: E402
def runlabel(module, executable):
changed = False
command = [executable, 'container', 'runlabel']
command.append(module.params['label'])
command.append(module.params['image'])
command = [executable, "container", "runlabel"]
command.append(module.params["label"])
command.append(module.params["image"])
rc, out, err = module.run_command(command)
if rc == 0:
changed = True
else:
module.fail_json(msg="Error running the runlabel from image %s: %s" % (
module.params['image'], err))
module.fail_json(
msg="Error running the runlabel from image %s: %s"
% (module.params["image"], err)
)
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
image=dict(type='str', required=True),
label=dict(type='str', required=True),
executable=dict(type='str', default='podman')
image=dict(type="str", required=True),
label=dict(type="str", required=True),
executable=dict(type="str", default="podman"),
),
supports_check_mode=False,
)
executable = module.get_bin_path(module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
changed, out, err = runlabel(module, executable)
results = {
"changed": changed,
"stdout": out,
"stderr": err
}
results = {"changed": changed, "stdout": out, "stderr": err}
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_save
short_description: Saves podman image to tar file
author: Sagi Shnaidman (@sshnaidm)
@ -63,12 +63,12 @@ options:
type: str
requirements:
- "Podman installed on host"
'''
"""
RETURN = '''
'''
RETURN = """
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- containers.podman.podman_save:
image: nginx
@ -79,7 +79,7 @@ EXAMPLES = '''
- fedora
dest: /tmp/file456.tar
multi_image_archive: true
'''
"""
import os # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
@ -88,32 +88,32 @@ from ..module_utils.podman.common import remove_file_or_dir # noqa: E402
def save(module, executable):
changed = False
command = [executable, 'save']
command = [executable, "save"]
cmd_args = {
'compress': ['--compress'],
'dest': ['-o=%s' % module.params['dest']],
'format': ['--format=%s' % module.params['format']],
'multi_image_archive': ['--multi-image-archive'],
"compress": ["--compress"],
"dest": ["-o=%s" % module.params["dest"]],
"format": ["--format=%s" % module.params["format"]],
"multi_image_archive": ["--multi-image-archive"],
}
for param in module.params:
if module.params[param] is not None and param in cmd_args:
command += cmd_args[param]
for img in module.params['image']:
for img in module.params["image"]:
command.append(img)
if module.params['force']:
if module.params["force"]:
changed = True
dest = module.params['dest']
dest = module.params["dest"]
if os.path.exists(dest):
if module.check_mode:
return changed, '', ''
return changed, "", ""
try:
remove_file_or_dir(dest)
except Exception as e:
module.fail_json(msg="Error deleting %s path: %s" % (dest, e))
else:
changed = not os.path.exists(module.params['dest'])
changed = not os.path.exists(module.params["dest"])
if module.check_mode:
return changed, '', ''
return changed, "", ""
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Error: %s" % (err))
@ -123,20 +123,28 @@ def save(module, executable):
def main():
module = AnsibleModule(
argument_spec=dict(
image=dict(type='list', elements='str', required=True),
compress=dict(type='bool'),
dest=dict(type='str', required=True, aliases=['path']),
format=dict(type='str', choices=['docker-archive', 'oci-archive', 'oci-dir', 'docker-dir']),
multi_image_archive=dict(type='bool'),
force=dict(type='bool', default=True),
executable=dict(type='str', default='podman')
image=dict(type="list", elements="str", required=True),
compress=dict(type="bool"),
dest=dict(type="str", required=True, aliases=["path"]),
format=dict(
type="str",
choices=["docker-archive", "oci-archive", "oci-dir", "docker-dir"],
),
multi_image_archive=dict(type="bool"),
force=dict(type="bool", default=True),
executable=dict(type="str", default="podman"),
),
supports_check_mode=True,
)
if module.params['compress'] and module.params['format'] not in ['oci-dir', 'docker-dir']:
module.fail_json(msg="Compression is only supported for oci-dir and docker-dir format")
if module.params["compress"] and module.params["format"] not in [
"oci-dir",
"docker-dir",
]:
module.fail_json(
msg="Compression is only supported for oci-dir and docker-dir format"
)
executable = module.get_bin_path(module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
changed, out, err = save(module, executable)
results = {
@ -148,5 +156,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_search
author:
- Derek Waters (@derekwaters)
@ -39,7 +40,7 @@ options:
default: False
type: bool
'''
"""
EXAMPLES = r"""
- name: Search for any rhel images
@ -81,10 +82,10 @@ from ansible.module_utils.basic import AnsibleModule
def search_images(module, executable, term, limit, list_tags):
command = [executable, 'search', term, '--format', 'json']
command.extend(['--limit', "{0}".format(limit)])
command = [executable, "search", term, "--format", "json"]
command.extend(["--limit", "{0}".format(limit)])
if list_tags:
command.extend(['--list-tags'])
command.extend(["--list-tags"])
rc, out, err = module.run_command(command)
if rc != 0 and list_tags and out == "" and "fetching tags list" in err:
@ -97,18 +98,18 @@ def search_images(module, executable, term, limit, list_tags):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
term=dict(type='str', required=True),
limit=dict(type='int', required=False, default=25),
list_tags=dict(type='bool', required=False, default=False)
executable=dict(type="str", default="podman"),
term=dict(type="str", required=True),
limit=dict(type="int", required=False, default=25),
list_tags=dict(type="bool", required=False, default=False),
),
supports_check_mode=True,
)
executable = module.params['executable']
term = module.params.get('term')
limit = module.params.get('limit')
list_tags = module.params.get('list_tags')
executable = module.params["executable"]
term = module.params.get("term")
limit = module.params.get("limit")
list_tags = module.params.get("list_tags")
executable = module.get_bin_path(executable, required=True)
result_str, errors = search_images(module, executable, term, limit, list_tags)
@ -118,7 +119,11 @@ def main():
try:
results = json.loads(result_str)
except json.decoder.JSONDecodeError:
module.fail_json(msg='Failed to parse JSON output from podman search: {out}'.format(out=result_str))
module.fail_json(
msg="Failed to parse JSON output from podman search: {out}".format(
out=result_str
)
)
results = dict(
changed=False,
@ -129,5 +134,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -2,10 +2,11 @@
# 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'''
DOCUMENTATION = r"""
---
module: podman_secret
author:
@ -81,7 +82,7 @@ options:
- Enable debug mode for module. It prints secrets diff.
type: bool
default: False
'''
"""
EXAMPLES = r"""
- name: Create secret
@ -113,24 +114,30 @@ EXAMPLES = r"""
import os
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion
from ansible_collections.containers.podman.plugins.module_utils.podman.common import get_podman_version
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
LooseVersion,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
get_podman_version,
)
diff = {"before": '', "after": ''}
diff = {"before": "", "after": ""}
def podman_secret_exists(module, executable, name, version):
if version is None or LooseVersion(version) < LooseVersion('4.5.0'):
if version is None or LooseVersion(version) < LooseVersion("4.5.0"):
rc, out, err = module.run_command(
[executable, 'secret', 'ls', "--format", "{{.Name}}"])
[executable, "secret", "ls", "--format", "{{.Name}}"]
)
return name in [i.strip() for i in out.splitlines()]
rc, out, err = module.run_command(
[executable, 'secret', 'exists', name])
rc, out, err = module.run_command([executable, "secret", "exists", name])
return rc == 0
def need_update(module, executable, name, data, path, env, skip, driver, driver_opts, debug, labels):
cmd = [executable, 'secret', 'inspect', '--showsecret', name]
def need_update(
module, executable, name, data, path, env, skip, driver, driver_opts, debug, labels
):
cmd = [executable, "secret", "inspect", "--showsecret", name]
rc, out, err = module.run_command(cmd)
if rc != 0:
if debug:
@ -141,64 +148,90 @@ def need_update(module, executable, name, data, path, env, skip, driver, driver_
try:
secret = module.from_json(out)[0]
if data:
if secret['SecretData'] != data:
if secret["SecretData"] != data:
if debug:
diff['after'] = data
diff['before'] = secret['SecretData']
diff["after"] = data
diff["before"] = secret["SecretData"]
else:
diff['after'] = "<different-secret>"
diff['before'] = "<secret>"
diff["after"] = "<different-secret>"
diff["before"] = "<secret>"
return True
if path:
with open(path, 'rb') as f:
text = f.read().decode('utf-8')
if secret['SecretData'] != text:
with open(path, "rb") as f:
text = f.read().decode("utf-8")
if secret["SecretData"] != text:
if debug:
diff['after'] = text
diff['before'] = secret['SecretData']
diff["after"] = text
diff["before"] = secret["SecretData"]
else:
diff['after'] = "<different-secret>"
diff['before'] = "<secret>"
diff["after"] = "<different-secret>"
diff["before"] = "<secret>"
return True
if env:
env_data = os.environ.get(env)
if secret['SecretData'] != env_data:
if secret["SecretData"] != env_data:
if debug:
diff['after'] = env_data
diff['before'] = secret['SecretData']
diff["after"] = env_data
diff["before"] = secret["SecretData"]
else:
diff['after'] = "<different-secret>"
diff['before'] = "<secret>"
diff["after"] = "<different-secret>"
diff["before"] = "<secret>"
return True
if driver:
if secret['Spec']['Driver']['Name'] != driver:
diff['after'] = driver
diff['before'] = secret['Spec']['Driver']['Name']
if secret["Spec"]["Driver"]["Name"] != driver:
diff["after"] = driver
diff["before"] = secret["Spec"]["Driver"]["Name"]
return True
if driver_opts:
for k, v in driver_opts.items():
if secret['Spec']['Driver']['Options'].get(k) != v:
diff['after'] = "=".join([k, v])
diff['before'] = "=".join(
[k, secret['Spec']['Driver']['Options'].get(k)])
if secret["Spec"]["Driver"]["Options"].get(k) != v:
diff["after"] = "=".join([k, v])
diff["before"] = "=".join(
[k, secret["Spec"]["Driver"]["Options"].get(k)]
)
return True
if labels:
for k, v in labels.items():
if secret['Spec']['Labels'].get(k) != v:
diff['after'] = "=".join([k, v])
diff['before'] = "=".join(
[k, secret['Spec']['Labels'].get(k)])
if secret["Spec"]["Labels"].get(k) != v:
diff["after"] = "=".join([k, v])
diff["before"] = "=".join([k, secret["Spec"]["Labels"].get(k)])
return True
except Exception:
return True
return False
def podman_secret_create(module, executable, name, data, path, env, force, skip,
driver, driver_opts, debug, labels):
def podman_secret_create(
module,
executable,
name,
data,
path,
env,
force,
skip,
driver,
driver_opts,
debug,
labels,
):
podman_version = get_podman_version(module, fail=False)
if podman_version is not None and LooseVersion(podman_version) >= LooseVersion('4.7.0'):
if need_update(module, executable, name, data, path, env, skip, driver, driver_opts, debug, labels):
if podman_version is not None and LooseVersion(podman_version) >= LooseVersion(
"4.7.0"
):
if need_update(
module,
executable,
name,
data,
path,
env,
skip,
driver,
driver_opts,
debug,
labels,
):
podman_secret_remove(module, executable, name)
else:
return {"changed": False}
@ -208,20 +241,20 @@ def podman_secret_create(module, executable, name, data, path, env, force, skip,
if skip and podman_secret_exists(module, executable, name, podman_version):
return {"changed": False}
cmd = [executable, 'secret', 'create']
cmd = [executable, "secret", "create"]
if driver:
cmd.append('--driver')
cmd.append("--driver")
cmd.append(driver)
if driver_opts:
cmd.append('--driver-opts')
cmd.append("--driver-opts")
cmd.append(",".join("=".join(i) for i in driver_opts.items()))
if labels:
for k, v in labels.items():
cmd.append('--label')
cmd.append("--label")
cmd.append("=".join([k, v]))
cmd.append(name)
if data:
cmd.append('-')
cmd.append("-")
elif path:
cmd.append(path)
elif env:
@ -240,18 +273,18 @@ def podman_secret_create(module, executable, name, data, path, env, force, skip,
return {
"changed": True,
"diff": {
"before": diff['before'] + "\n",
"after": diff['after'] + "\n",
"before": diff["before"] + "\n",
"after": diff["after"] + "\n",
},
}
def podman_secret_remove(module, executable, name):
changed = False
rc, out, err = module.run_command([executable, 'secret', 'rm', name])
rc, out, err = module.run_command([executable, "secret", "rm", name])
if rc == 0:
changed = True
elif 'no such secret' in err:
elif "no such secret" in err:
pass
else:
module.fail_json(msg="Unable to remove secret: %s" % err)
@ -264,45 +297,56 @@ def podman_secret_remove(module, executable, name):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
state=dict(type='str', default='present', choices=['absent', 'present']),
name=dict(type='str', required=True),
data=dict(type='str', no_log=True),
env=dict(type='str'),
path=dict(type='path'),
force=dict(type='bool', default=False),
skip_existing=dict(type='bool', default=False),
driver=dict(type='str'),
driver_opts=dict(type='dict'),
labels=dict(type='dict'),
debug=dict(type='bool', default=False),
executable=dict(type="str", default="podman"),
state=dict(type="str", default="present", choices=["absent", "present"]),
name=dict(type="str", required=True),
data=dict(type="str", no_log=True),
env=dict(type="str"),
path=dict(type="path"),
force=dict(type="bool", default=False),
skip_existing=dict(type="bool", default=False),
driver=dict(type="str"),
driver_opts=dict(type="dict"),
labels=dict(type="dict"),
debug=dict(type="bool", default=False),
),
required_if=[('state', 'present', ['path', 'env', 'data'], True)],
mutually_exclusive=[['path', 'env', 'data']],
required_if=[("state", "present", ["path", "env", "data"], True)],
mutually_exclusive=[["path", "env", "data"]],
)
state = module.params['state']
name = module.params['name']
executable = module.get_bin_path(module.params['executable'], required=True)
state = module.params["state"]
name = module.params["name"]
executable = module.get_bin_path(module.params["executable"], required=True)
if state == 'present':
data = module.params['data']
force = module.params['force']
skip = module.params['skip_existing']
driver = module.params['driver']
driver_opts = module.params['driver_opts']
debug = module.params['debug']
labels = module.params['labels']
path = module.params['path']
env = module.params['env']
results = podman_secret_create(module, executable,
name, data, path, env, force, skip,
driver, driver_opts, debug, labels)
if state == "present":
data = module.params["data"]
force = module.params["force"]
skip = module.params["skip_existing"]
driver = module.params["driver"]
driver_opts = module.params["driver_opts"]
debug = module.params["debug"]
labels = module.params["labels"]
path = module.params["path"]
env = module.params["env"]
results = podman_secret_create(
module,
executable,
name,
data,
path,
env,
force,
skip,
driver,
driver_opts,
debug,
labels,
)
else:
results = podman_secret_remove(module, executable, name)
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_secret_info
author:
- "Sagi Shnaidman (@sshnaidm)"
@ -32,7 +33,7 @@ options:
machine running C(podman)
default: 'podman'
type: str
'''
"""
EXAMPLES = r"""
- name: Gather info about all present secrets
@ -72,21 +73,23 @@ from ansible.module_utils.basic import AnsibleModule
def get_secret_info(module, executable, show, name):
command = [executable, 'secret', 'inspect']
command = [executable, "secret", "inspect"]
if show:
command.append('--showsecret')
command.append("--showsecret")
if name:
command.append(name)
else:
all_names = [executable, 'secret', 'ls', '-q']
all_names = [executable, "secret", "ls", "-q"]
rc, out, err = module.run_command(all_names)
name = out.split()
if not name:
return [], out, err
command.extend(name)
rc, out, err = module.run_command(command)
if rc != 0 or 'no secret with name or id' in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (name or 'all secrets', err))
if rc != 0 or "no secret with name or id" in err:
module.fail_json(
msg="Unable to gather info for %s: %s" % (name or "all secrets", err)
)
if not out or json.loads(out) is None:
return [], out, err
return json.loads(out), out, err
@ -95,16 +98,16 @@ def get_secret_info(module, executable, show, name):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='str'),
showsecret=dict(type='bool', default=False),
executable=dict(type="str", default="podman"),
name=dict(type="str"),
showsecret=dict(type="bool", default=False),
),
supports_check_mode=True,
)
name = module.params['name']
showsecret = module.params['showsecret']
executable = module.get_bin_path(module.params['executable'], required=True)
name = module.params["name"]
showsecret = module.params["showsecret"]
executable = module.get_bin_path(module.params["executable"], required=True)
inspect_results, out, err = get_secret_info(module, executable, showsecret, name)
@ -117,5 +120,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
DOCUMENTATION = r"""
module: podman_tag
short_description: Add an additional name to a local image
author: Christian Bourque (@ocafebabe)
@ -33,50 +33,51 @@ options:
type: str
requirements:
- "Podman installed on host"
'''
"""
RETURN = '''
'''
RETURN = """
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- containers.podman.podman_tag:
image: docker.io/continuumio/miniconda3
target_names:
- miniconda3
- miniconda
'''
"""
from ansible.module_utils.basic import AnsibleModule # noqa: E402
def tag(module, executable):
changed = False
command = [executable, 'tag']
command.append(module.params['image'])
command.extend(module.params['target_names'])
command = [executable, "tag"]
command.append(module.params["image"])
command.extend(module.params["target_names"])
if module.check_mode:
return changed, '', ''
return changed, "", ""
rc, out, err = module.run_command(command)
if rc == 0:
changed = True
else:
module.fail_json(msg="Error tagging local image %s: %s" % (
module.params['image'], err))
module.fail_json(
msg="Error tagging local image %s: %s" % (module.params["image"], err)
)
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
image=dict(type='str', required=True),
target_names=dict(type='list', elements='str', required=True),
executable=dict(type='str', default='podman')
image=dict(type="str", required=True),
target_names=dict(type="list", elements="str", required=True),
executable=dict(type="str", default="podman"),
),
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
executable = module.get_bin_path(module.params["executable"], required=True)
changed, out, err = tag(module, executable)
results = {
@ -87,5 +88,5 @@ def main():
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -4,9 +4,10 @@
# flake8: noqa: E501
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
DOCUMENTATION = """
---
module: podman_volume
short_description: Manage Podman volumes
@ -101,9 +102,9 @@ options:
requirements:
- "podman"
'''
"""
RETURN = '''
RETURN = """
volume:
description: Volume inspection results if exists.
returned: always
@ -121,9 +122,9 @@ volume:
Options: {}
Scope: local
'''
"""
EXAMPLES = '''
EXAMPLES = """
# What modules does for example
- name: Create a volume
containers.podman.podman_volume:
@ -147,26 +148,32 @@ EXAMPLES = '''
- Copy=true
- Image=quay.io/centos/centos:latest
'''
"""
# noqa: F402
import json # noqa: F402
import os # noqa: F402
from ansible.module_utils.basic import AnsibleModule # noqa: F402
from ansible.module_utils._text import to_bytes, to_native # noqa: F402
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion
from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import create_quadlet_state
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
LooseVersion,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
lower_keys,
)
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import (
create_quadlet_state,
)
class PodmanVolumeModuleParams:
"""Creates list of arguments for podman CLI command.
Arguments:
action {str} -- action type from 'create', 'delete'
params {dict} -- dictionary of module parameters
Arguments:
action {str} -- action type from 'create', 'delete'
params {dict} -- dictionary of module parameters
"""
"""
def __init__(self, action, params, podman_version, module):
self.params = params
@ -180,58 +187,64 @@ class PodmanVolumeModuleParams:
Returns:
list -- list of byte strings for Popen command
"""
if self.action in ['delete', 'mount', 'unmount']:
if self.action in ["delete", "mount", "unmount"]:
return self._simple_action()
if self.action in ['create']:
if self.action in ["create"]:
return self._create_action()
def _simple_action(self):
if self.action == 'delete':
cmd = ['rm', '-f', self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
if self.action == 'mount':
cmd = ['mount', self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
if self.action == 'unmount':
cmd = ['unmount', self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
if self.action == "delete":
cmd = ["rm", "-f", self.params["name"]]
return [to_bytes(i, errors="surrogate_or_strict") for i in cmd]
if self.action == "mount":
cmd = ["mount", self.params["name"]]
return [to_bytes(i, errors="surrogate_or_strict") for i in cmd]
if self.action == "unmount":
cmd = ["unmount", self.params["name"]]
return [to_bytes(i, errors="surrogate_or_strict") for i in cmd]
def _create_action(self):
cmd = [self.action, self.params['name']]
all_param_methods = [func for func in dir(self)
if callable(getattr(self, func))
and func.startswith("addparam")]
cmd = [self.action, self.params["name"]]
all_param_methods = [
func
for func in dir(self)
if callable(getattr(self, func)) and func.startswith("addparam")
]
params_set = (i for i in self.params if self.params[i] is not None)
for param in params_set:
func_name = "_".join(["addparam", param])
if func_name in all_param_methods:
cmd = getattr(self, func_name)(cmd)
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
return [to_bytes(i, errors="surrogate_or_strict") for i in cmd]
def check_version(self, param, minv=None, maxv=None):
if minv and LooseVersion(minv) > LooseVersion(
self.podman_version):
self.module.fail_json(msg="Parameter %s is supported from podman "
"version %s only! Current version is %s" % (
param, minv, self.podman_version))
if maxv and LooseVersion(maxv) < LooseVersion(
self.podman_version):
self.module.fail_json(msg="Parameter %s is supported till podman "
"version %s only! Current version is %s" % (
param, minv, self.podman_version))
if minv and LooseVersion(minv) > LooseVersion(self.podman_version):
self.module.fail_json(
msg="Parameter %s is supported from podman "
"version %s only! Current version is %s"
% (param, minv, self.podman_version)
)
if maxv and LooseVersion(maxv) < LooseVersion(self.podman_version):
self.module.fail_json(
msg="Parameter %s is supported till podman "
"version %s only! Current version is %s"
% (param, minv, self.podman_version)
)
def addparam_label(self, c):
for label in self.params['label'].items():
c += ['--label', b'='.join(
[to_bytes(l, errors='surrogate_or_strict') for l in label])]
for label in self.params["label"].items():
c += [
"--label",
b"=".join([to_bytes(l, errors="surrogate_or_strict") for l in label]),
]
return c
def addparam_driver(self, c):
return c + ['--driver', self.params['driver']]
return c + ["--driver", self.params["driver"]]
def addparam_options(self, c):
for opt in self.params['options']:
c += ['--opt', opt]
for opt in self.params["options"]:
c += ["--opt", opt]
return c
@ -239,11 +252,7 @@ class PodmanVolumeDefaults:
def __init__(self, module, podman_version):
self.module = module
self.version = podman_version
self.defaults = {
'driver': 'local',
'label': {},
'options': {}
}
self.defaults = {"driver": "local", "label": {}, "options": {}}
def default_dict(self):
# make here any changes to self.defaults related to podman version
@ -257,13 +266,14 @@ class PodmanVolumeDiff:
self.default_dict = None
self.info = lower_keys(info)
self.params = self.defaultize()
self.diff = {'before': {}, 'after': {}}
self.diff = {"before": {}, "after": {}}
self.non_idempotent = {}
def defaultize(self):
params_with_defaults = {}
self.default_dict = PodmanVolumeDefaults(
self.module, self.version).default_dict()
self.module, self.version
).default_dict()
for p in self.module.params:
if self.module.params[p] is None and p in self.default_dict:
params_with_defaults[p] = self.default_dict[p]
@ -273,29 +283,29 @@ class PodmanVolumeDiff:
def _diff_update_and_compare(self, param_name, before, after):
if before != after:
self.diff['before'].update({param_name: before})
self.diff['after'].update({param_name: after})
self.diff["before"].update({param_name: before})
self.diff["after"].update({param_name: after})
return True
return False
def diffparam_label(self):
before = self.info['labels'] if 'labels' in self.info else {}
after = self.params['label']
return self._diff_update_and_compare('label', before, after)
before = self.info["labels"] if "labels" in self.info else {}
after = self.params["label"]
return self._diff_update_and_compare("label", before, after)
def diffparam_driver(self):
before = self.info['driver']
after = self.params['driver']
return self._diff_update_and_compare('driver', before, after)
before = self.info["driver"]
after = self.params["driver"]
return self._diff_update_and_compare("driver", before, after)
def diffparam_options(self):
before = self.info['options'] if 'options' in self.info else {}
before = self.info["options"] if "options" in self.info else {}
# Removing GID and UID from options list
before.pop('uid', None)
before.pop('gid', None)
before.pop("uid", None)
before.pop("gid", None)
# Collecting all other options in the list
before = ["=".join((k, v)) for k, v in before.items()]
after = self.params['options']
after = self.params["options"]
# # For UID, GID
# if 'uid' in self.info or 'gid' in self.info:
# ids = []
@ -312,12 +322,14 @@ class PodmanVolumeDiff:
# after = [i for i in after if 'gid' not in i and 'uid' not in i]
# after += ids
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('options', before, after)
return self._diff_update_and_compare("options", before, after)
def is_different(self):
diff_func_list = [func for func in dir(self)
if callable(getattr(self, func)) and func.startswith(
"diffparam")]
diff_func_list = [
func
for func in dir(self)
if callable(getattr(self, func)) and func.startswith("diffparam")
]
fail_fast = not bool(self.module._diff)
different = False
for func_name in diff_func_list:
@ -329,7 +341,11 @@ class PodmanVolumeDiff:
different = True
# Check non idempotent parameters
for p in self.non_idempotent:
if self.module.params[p] is not None and self.module.params[p] not in [{}, [], '']:
if self.module.params[p] is not None and self.module.params[p] not in [
{},
[],
"",
]:
different = True
return different
@ -351,7 +367,7 @@ class PodmanVolume:
super(PodmanVolume, self).__init__()
self.module = module
self.name = name
self.stdout, self.stderr = '', ''
self.stdout, self.stderr = "", ""
self.mount_point = None
self.info = self.get_info()
self.version = self._get_podman_version()
@ -366,26 +382,30 @@ class PodmanVolume:
@property
def different(self):
"""Check if volume is different."""
diffcheck = PodmanVolumeDiff(
self.module,
self.info,
self.version)
diffcheck = PodmanVolumeDiff(self.module, self.info, self.version)
is_different = diffcheck.is_different()
diffs = diffcheck.diff
if self.module._diff and is_different and diffs['before'] and diffs['after']:
self.diff['before'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['before'].items())]) + "\n"
self.diff['after'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['after'].items())]) + "\n"
if self.module._diff and is_different and diffs["before"] and diffs["after"]:
self.diff["before"] = (
"\n".join(
["%s - %s" % (k, v) for k, v in sorted(diffs["before"].items())]
)
+ "\n"
)
self.diff["after"] = (
"\n".join(
["%s - %s" % (k, v) for k, v in sorted(diffs["after"].items())]
)
+ "\n"
)
return is_different
def get_info(self):
"""Inspect volume and gather info about it."""
# pylint: disable=unused-variable
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'volume', b'inspect', self.name])
[self.module.params["executable"], b"volume", b"inspect", self.name]
)
if rc == 0:
data = json.loads(out)
if data:
@ -397,10 +417,12 @@ class PodmanVolume:
def _get_podman_version(self):
# pylint: disable=unused-variable
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'--version'])
[self.module.params["executable"], b"--version"]
)
if rc != 0 or not out or "version" not in out:
self.module.fail_json(msg="%s run failed!" %
self.module.params['executable'])
self.module.fail_json(
msg="%s run failed!" % self.module.params["executable"]
)
return out.split("version")[1].strip()
def _perform_action(self, action):
@ -409,47 +431,50 @@ class PodmanVolume:
Arguments:
action {str} -- action to perform - create, delete, mount, unmout
"""
b_command = PodmanVolumeModuleParams(action,
self.module.params,
self.version,
self.module,
).construct_command_from_params()
full_cmd = " ".join([self.module.params['executable'], 'volume']
+ [to_native(i) for i in b_command])
b_command = PodmanVolumeModuleParams(
action,
self.module.params,
self.version,
self.module,
).construct_command_from_params()
full_cmd = " ".join(
[self.module.params["executable"], "volume"]
+ [to_native(i) for i in b_command]
)
# check if running not from root
if os.getuid() != 0 and action == 'mount':
if os.getuid() != 0 and action == "mount":
full_cmd = f"{self.module.params['executable']} unshare {full_cmd}"
self.module.log("PODMAN-VOLUME-DEBUG: %s" % full_cmd)
self.actions.append(full_cmd)
if not self.module.check_mode:
rc, out, err = self.module.run_command(
full_cmd,
expand_user_and_vars=False)
rc, out, err = self.module.run_command(full_cmd, expand_user_and_vars=False)
self.stdout = out
self.stderr = err
if rc != 0:
self.module.fail_json(
msg="Can't %s volume %s" % (action, self.name),
stdout=out, stderr=err)
stdout=out,
stderr=err,
)
# in case of mount/unmount, return path to the volume from stdout
if action in ['mount']:
if action in ["mount"]:
self.mount_point = out.strip()
def delete(self):
"""Delete the volume."""
self._perform_action('delete')
self._perform_action("delete")
def create(self):
"""Create the volume."""
self._perform_action('create')
self._perform_action("create")
def mount(self):
"""Delete the volume."""
self._perform_action('mount')
self._perform_action("mount")
def unmount(self):
"""Create the volume."""
self._perform_action('unmount')
self._perform_action("unmount")
def recreate(self):
"""Recreate the volume."""
@ -474,16 +499,16 @@ class PodmanVolumeManager:
self.module = module
self.results = {
'changed': False,
'actions': [],
'volume': {},
"changed": False,
"actions": [],
"volume": {},
}
self.name = self.module.params['name']
self.executable = \
self.module.get_bin_path(self.module.params['executable'],
required=True)
self.state = self.module.params['state']
self.recreate = self.module.params['recreate']
self.name = self.module.params["name"]
self.executable = self.module.get_bin_path(
self.module.params["executable"], required=True
)
self.state = self.module.params["state"]
self.recreate = self.module.params["recreate"]
self.volume = PodmanVolume(self.module, self.name)
def update_volume_result(self, changed=True):
@ -495,39 +520,45 @@ class PodmanVolumeManager:
"""
facts = self.volume.get_info() if changed else self.volume.info
out, err = self.volume.stdout, self.volume.stderr
self.results.update({'changed': changed, 'volume': facts,
'podman_actions': self.volume.actions},
stdout=out, stderr=err)
self.results.update(
{
"changed": changed,
"volume": facts,
"podman_actions": self.volume.actions,
},
stdout=out,
stderr=err,
)
if self.volume.diff:
self.results.update({'diff': self.volume.diff})
if self.module.params['debug']:
self.results.update({'podman_version': self.volume.version})
self.results.update({"diff": self.volume.diff})
if self.module.params["debug"]:
self.results.update({"podman_version": self.volume.version})
self.module.exit_json(**self.results)
def execute(self):
"""Execute the desired action according to map of actions & states."""
states_map = {
'present': self.make_present,
'absent': self.make_absent,
'mounted': self.make_mount,
'unmounted': self.make_unmount,
'quadlet': self.make_quadlet,
"present": self.make_present,
"absent": self.make_absent,
"mounted": self.make_mount,
"unmounted": self.make_unmount,
"quadlet": self.make_quadlet,
}
process_action = states_map[self.state]
process_action()
self.module.fail_json(msg="Unexpected logic error happened, "
"please contact maintainers ASAP!")
self.module.fail_json(
msg="Unexpected logic error happened, " "please contact maintainers ASAP!"
)
def make_present(self):
"""Run actions if desired state is 'started'."""
if not self.volume.exists:
self.volume.create()
self.results['actions'].append('created %s' % self.volume.name)
self.results["actions"].append("created %s" % self.volume.name)
self.update_volume_result()
elif self.recreate or self.volume.different:
self.volume.recreate()
self.results['actions'].append('recreated %s' %
self.volume.name)
self.results["actions"].append("recreated %s" % self.volume.name)
self.update_volume_result()
else:
self.update_volume_result(changed=False)
@ -535,31 +566,30 @@ class PodmanVolumeManager:
def make_absent(self):
"""Run actions if desired state is 'absent'."""
if not self.volume.exists:
self.results.update({'changed': False})
self.results.update({"changed": False})
elif self.volume.exists:
self.volume.delete()
self.results['actions'].append('deleted %s' % self.volume.name)
self.results.update({'changed': True})
self.results.update({'volume': {},
'podman_actions': self.volume.actions})
self.results["actions"].append("deleted %s" % self.volume.name)
self.results.update({"changed": True})
self.results.update({"volume": {}, "podman_actions": self.volume.actions})
self.module.exit_json(**self.results)
def make_mount(self):
"""Run actions if desired state is 'mounted'."""
if not self.volume.exists:
self.volume.create()
self.results['actions'].append('created %s' % self.volume.name)
self.results["actions"].append("created %s" % self.volume.name)
self.volume.mount()
self.results['actions'].append('mounted %s' % self.volume.name)
self.results["actions"].append("mounted %s" % self.volume.name)
if self.volume.mount_point:
self.results.update({'mount_point': self.volume.mount_point})
self.results.update({"mount_point": self.volume.mount_point})
self.update_volume_result()
def make_unmount(self):
"""Run actions if desired state is 'unmounted'."""
if self.volume.exists:
self.volume.unmount()
self.results['actions'].append('unmounted %s' % self.volume.name)
self.results["actions"].append("unmounted %s" % self.volume.name)
self.update_volume_result()
else:
self.module.fail_json(msg="Volume %s does not exist!" % self.name)
@ -573,23 +603,27 @@ class PodmanVolumeManager:
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', default="present",
choices=['present', 'absent', 'mounted', 'unmounted', 'quadlet']),
name=dict(type='str', required=True),
label=dict(type='dict', required=False),
driver=dict(type='str', required=False),
options=dict(type='list', elements='str', required=False),
recreate=dict(type='bool', default=False),
executable=dict(type='str', required=False, default='podman'),
debug=dict(type='bool', default=False),
quadlet_dir=dict(type='path', required=False),
quadlet_filename=dict(type='str', required=False),
quadlet_file_mode=dict(type='raw', required=False),
quadlet_options=dict(type='list', elements='str', required=False),
))
state=dict(
type="str",
default="present",
choices=["present", "absent", "mounted", "unmounted", "quadlet"],
),
name=dict(type="str", required=True),
label=dict(type="dict", required=False),
driver=dict(type="str", required=False),
options=dict(type="list", elements="str", required=False),
recreate=dict(type="bool", default=False),
executable=dict(type="str", required=False, default="podman"),
debug=dict(type="bool", default=False),
quadlet_dir=dict(type="path", required=False),
quadlet_filename=dict(type="str", required=False),
quadlet_file_mode=dict(type="raw", required=False),
quadlet_options=dict(type="list", elements="str", required=False),
)
)
PodmanVolumeManager(module).execute()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,10 +3,11 @@
# 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'''
DOCUMENTATION = r"""
module: podman_volume_info
author:
- "Sagi Shnaidman (@sshnaidm)"
@ -27,7 +28,7 @@ options:
machine running C(podman)
default: 'podman'
type: str
'''
"""
EXAMPLES = r"""
- name: Gather info about all present volumes
@ -65,14 +66,16 @@ from ansible.module_utils.basic import AnsibleModule
def get_volume_info(module, executable, name):
command = [executable, 'volume', 'inspect']
command = [executable, "volume", "inspect"]
if name:
command.append(name)
else:
command.append("--all")
rc, out, err = module.run_command(command)
if rc != 0 or 'no such volume' in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (name or 'all volumes', err))
if rc != 0 or "no such volume" in err:
module.fail_json(
msg="Unable to gather info for %s: %s" % (name or "all volumes", err)
)
if not out or json.loads(out) is None:
return [], out, err
return json.loads(out), out, err
@ -81,25 +84,20 @@ def get_volume_info(module, executable, name):
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='str')
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)
name = module.params["name"]
executable = module.get_bin_path(module.params["executable"], required=True)
inspect_results, out, err = get_volume_info(module, executable, name)
results = {
"changed": False,
"volumes": inspect_results,
"stderr": err
}
results = {"changed": False, "volumes": inspect_results, "stderr": err}
module.exit_json(**results)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,7 +3,4 @@
import setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True,
py_modules=[])
setuptools.setup(setup_requires=["pbr"], pbr=True, py_modules=[])

View file

@ -1,4 +1,5 @@
from __future__ import (absolute_import, division, print_function)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import pytest
@ -8,12 +9,17 @@ from ansible_collections.containers.podman.plugins.module_utils.podman.common im
)
@pytest.mark.parametrize('test_input, expected', [
(["AAA", "BBB"], ["AAA", "BBB"]),
("AAQQ", "AAQQ"),
({"AAA": "AaaAa", "11": 22, "AbCdEf": None, "bbb": "aaaAA"},
{"aaa": "AaaAa", "11": 22, "abcdef": None, "bbb": "aaaAA"})
])
@pytest.mark.parametrize(
"test_input, expected",
[
(["AAA", "BBB"], ["AAA", "BBB"]),
("AAQQ", "AAQQ"),
(
{"AAA": "AaaAa", "11": 22, "AbCdEf": None, "bbb": "aaaAA"},
{"aaa": "AaaAa", "11": 22, "abcdef": None, "bbb": "aaaAA"},
),
],
)
def test_lower_keys(test_input, expected):
print(lower_keys.__code__.co_filename)
assert lower_keys(test_input) == expected

View file

@ -66,15 +66,17 @@ def test_container_add_params(test_input, expected):
[
None, # module
{"conmon_pidfile": "bbb"}, # module params
{"conmonpidfile": "ccc",
{
"conmonpidfile": "ccc",
"config": {
"createcommand": [
"podman",
"create",
"--conmon-pidfile=ccc",
"testcont",
]}
}, # container info
]
},
}, # container info
{}, # image info
"4.1.1", # podman version
],
@ -84,15 +86,17 @@ def test_container_add_params(test_input, expected):
[
None, # module
{"conmon_pidfile": None}, # module params
{"conmonpidfile": "ccc",
{
"conmonpidfile": "ccc",
"config": {
"createcommand": [
"podman",
"create",
"--conmon-pidfile=ccc",
"testcont",
]}
}, # container info
]
},
}, # container info
{}, # image info
"4.1.1", # podman version
],
@ -102,14 +106,16 @@ def test_container_add_params(test_input, expected):
[
None, # module
{"conmon_pidfile": None}, # module params
{"conmonpidfile": None,
{
"conmonpidfile": None,
"config": {
"createcommand": [
"podman",
"create",
"testcont",
]}
}, # container info
]
},
}, # container info
{}, # image info
"4.1.1", # podman version
],
@ -118,15 +124,17 @@ def test_container_add_params(test_input, expected):
(
[
None, # module
{"conmon_pidfile": 'aaa'}, # module params
{"conmonpidfile": None,
{"conmon_pidfile": "aaa"}, # module params
{
"conmonpidfile": None,
"config": {
"createcommand": [
"podman",
"create",
"testcont",
]}
}, # container info
]
},
}, # container info
{}, # image info
"4.1.1", # podman version
],
@ -135,16 +143,18 @@ def test_container_add_params(test_input, expected):
(
[
None, # module
{"conmon_pidfile": 'aaa'}, # module params
{"conmonpidfile": 'aaa',
{"conmon_pidfile": "aaa"}, # module params
{
"conmonpidfile": "aaa",
"config": {
"createcommand": [
"podman",
"create",
"--conmon-pidfile=aaa",
"testcont",
]}
}, # container info
]
},
}, # container info
{}, # image info
"4.1.1", # podman version
],