summaryrefslogtreecommitdiffstats
path: root/ansible_collections/containers/podman/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
commit38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch)
tree356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/containers/podman/plugins
parentAdding upstream version 7.7.0+dfsg. (diff)
downloadansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz
ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/containers/podman/plugins')
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/common.py119
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py204
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py66
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_container.py92
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_container_exec.py244
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_container_info.py2
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_containers.py2
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_export.py33
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_generate_systemd.py44
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_image.py35
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_import.py41
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_login.py2
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_network.py35
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_play.py65
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_pod.py54
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_prune.py6
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_runlabel.py86
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_save.py19
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_secret.py97
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_secret_info.py121
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_volume.py8
21 files changed, 1243 insertions, 132 deletions
diff --git a/ansible_collections/containers/podman/plugins/module_utils/podman/common.py b/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
index dba3aff65..cbb6b080e 100644
--- a/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
@@ -19,6 +19,25 @@ except ImportError:
' < 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'],
+}
+
def run_podman_command(module, executable='podman', args=None, expected_rc=0, ignore_errors=False):
if not isinstance(executable, list):
@@ -50,8 +69,15 @@ def run_generate_systemd_command(module, module_params, name, version):
command.extend([
'--restart-policy',
sysconf['restart_policy']])
- if sysconf.get('time'):
- command.extend(['--time', str(sysconf['time'])])
+ 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):
@@ -96,34 +122,103 @@ def run_generate_systemd_command(module, module_params, name, version):
return rc, systemd, err
+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
+ # Read the 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('#')])
+
+ # Remove comments from both file contents before comparison
+ current_unit_file_content_nocmnt = remove_comments(current_unit_file_content)
+ unit_content_nocmnt = remove_comments(file_content)
+ if current_unit_file_content_nocmnt == unit_content_nocmnt:
+ 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()]
+
+ return diff_in_string, diff_in_file
+
+
def generate_systemd(module, module_params, name, version):
- empty = {}
+ result = {
+ 'changed': False,
+ 'systemd': {},
+ 'diff': {},
+ }
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)
- return empty
+ 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'])
if not os.path.exists(full_path):
os.makedirs(full_path)
+ 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)
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})
+
+ else:
+ 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:
f.write(file_content)
- return data
+ 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()
+ 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()
+ if diff_before or diff_after:
+ result['diff']['before'] = diff_before + "\n"
+ result['diff']['after'] = diff_after + "\n"
+ else:
+ result['diff'] = {}
+ return result
except Exception as e:
module.log(
"PODMAN-CONTAINER-DEBUG: Error writing systemd: %s" % e)
- return empty
+ if sysconf:
+ module.fail_json(msg="Error writing systemd: %s" % e)
+ return result
def delete_systemd(module, module_params, name, version):
@@ -230,3 +325,15 @@ def normalize_signal(signal_name_or_number):
if signal_name not in _signal_map:
raise RuntimeError("Unknown signal '{0}'".format(signal_name_or_number))
return str(_signal_map[signal_name])
+
+
+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'])
+ 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))
+ return None
+ return out.split("version")[1].strip()
diff --git a/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py b/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py
index 1ba28f4c8..ff4c18629 100644
--- a/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py
@@ -9,6 +9,7 @@ from ansible_collections.containers.podman.plugins.module_utils.podman.common im
from ansible_collections.containers.podman.plugins.module_utils.podman.common import generate_systemd
from ansible_collections.containers.podman.plugins.module_utils.podman.common import delete_systemd
from ansible_collections.containers.podman.plugins.module_utils.podman.common import normalize_signal
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import ARGUMENTS_OPTS_DICT
__metaclass__ = type
@@ -19,6 +20,7 @@ ARGUMENTS_SPEC_CONTAINER = dict(
'absent', 'present', 'stopped', 'started', 'created']),
image=dict(type='str'),
annotation=dict(type='dict'),
+ attach=dict(type='list', elements='str', choices=['stdout', 'stderr', 'stdin']),
authfile=dict(type='path'),
blkio_weight=dict(type='int'),
blkio_weight_device=dict(type='dict'),
@@ -32,12 +34,16 @@ ARGUMENTS_SPEC_CONTAINER = dict(
conmon_pidfile=dict(type='path'),
command=dict(type='raw'),
cpu_period=dict(type='int'),
+ cpu_quota=dict(type='int'),
cpu_rt_period=dict(type='int'),
cpu_rt_runtime=dict(type='int'),
cpu_shares=dict(type='int'),
cpus=dict(type='str'),
cpuset_cpus=dict(type='str'),
cpuset_mems=dict(type='str'),
+ delete_depend=dict(type='bool'),
+ delete_time=dict(type='str'),
+ delete_volumes=dict(type='bool'),
detach=dict(type='bool', default=True),
debug=dict(type='bool', default=False),
detach_keys=dict(type='str', no_log=False),
@@ -51,13 +57,14 @@ ARGUMENTS_SPEC_CONTAINER = dict(
dns_search=dict(type='str', aliases=['dns_search_domains']),
entrypoint=dict(type='str'),
env=dict(type='dict'),
- env_file=dict(type='path'),
+ env_file=dict(type='list', elements='path', aliases=['env_files']),
env_host=dict(type='bool'),
etc_hosts=dict(type='dict', aliases=['add_hosts']),
expose=dict(type='list', elements='str', aliases=[
'exposed', 'exposed_ports']),
force_restart=dict(type='bool', default=False,
aliases=['restart']),
+ force_delete=dict(type='bool', default=True),
generate_systemd=dict(type='dict', default={}),
gidmap=dict(type='list', elements='str'),
group_add=dict(type='list', elements='str', aliases=['groups']),
@@ -66,6 +73,8 @@ ARGUMENTS_SPEC_CONTAINER = dict(
healthcheck_retries=dict(type='int'),
healthcheck_start_period=dict(type='str'),
healthcheck_timeout=dict(type='str'),
+ healthcheck_failure_action=dict(type='str', choices=[
+ 'none', 'kill', 'restart', 'stop']),
hooks_dir=dict(type='list', elements='str'),
hostname=dict(type='str'),
http_proxy=dict(type='bool'),
@@ -112,6 +121,7 @@ ARGUMENTS_SPEC_CONTAINER = dict(
recreate=dict(type='bool', default=False),
requires=dict(type='list', elements='str'),
restart_policy=dict(type='str'),
+ restart_time=dict(type='str'),
rm=dict(type='bool', aliases=['remove', 'auto_remove']),
rootfs=dict(type='bool'),
secrets=dict(type='list', elements='str', no_log=True),
@@ -121,6 +131,7 @@ ARGUMENTS_SPEC_CONTAINER = dict(
sig_proxy=dict(type='bool'),
stop_signal=dict(type='int'),
stop_timeout=dict(type='int'),
+ stop_time=dict(type='str'),
subgidname=dict(type='str'),
subuidname=dict(type='str'),
sysctl=dict(type='dict'),
@@ -227,12 +238,35 @@ class PodmanModuleParams:
def start_stop_delete(self):
+ def complete_params(cmd):
+ if self.params['attach'] and self.action == 'start':
+ cmd.append('--attach')
+ if self.params['detach'] is False and self.action == 'start' and '--attach' not in cmd:
+ cmd.append('--attach')
+ if self.params['detach_keys'] and self.action == 'start':
+ cmd += ['--detach-keys', self.params['detach_keys']]
+ if self.params['sig_proxy'] and self.action == 'start':
+ cmd.append('--sig-proxy')
+ if self.params['stop_time'] and self.action == 'stop':
+ cmd += ['--time', self.params['stop_time']]
+ if self.params['restart_time'] and self.action == 'restart':
+ cmd += ['--time', self.params['restart_time']]
+ if self.params['delete_depend'] and self.action == 'delete':
+ cmd.append('--depend')
+ if self.params['delete_time'] and self.action == 'delete':
+ cmd += ['--time', self.params['delete_time']]
+ if self.params['delete_volumes'] and self.action == 'delete':
+ cmd.append('--volumes')
+ if self.params['force_delete'] and self.action == 'delete':
+ cmd.append('--force')
+ return cmd
+
if self.action in ['stop', 'start', 'restart']:
- cmd = [self.action, self.params['name']]
+ cmd = complete_params([self.action]) + [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']]
+ cmd = complete_params(['rm']) + [self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
def check_version(self, param, minv=None, maxv=None):
@@ -252,6 +286,11 @@ class PodmanModuleParams:
c += ['--annotation', '='.join(annotate)]
return c
+ def addparam_attach(self, c):
+ for attach in self.params['attach']:
+ c += ['--attach=%s' % attach]
+ return c
+
def addparam_authfile(self, c):
return c + ['--authfile', self.params['authfile']]
@@ -293,6 +332,9 @@ class PodmanModuleParams:
def addparam_cpu_period(self, c):
return c + ['--cpu-period', self.params['cpu_period']]
+ def addparam_cpu_quota(self, c):
+ return c + ['--cpu-quota', self.params['cpu_quota']]
+
def addparam_cpu_rt_period(self, c):
return c + ['--cpu-rt-period', self.params['cpu_rt_period']]
@@ -312,6 +354,9 @@ class PodmanModuleParams:
return c + ['--cpuset-mems', self.params['cpuset_mems']]
def addparam_detach(self, c):
+ # Remove detach from create command and don't set if attach is true
+ if self.action == 'create' or self.params['attach']:
+ return c
return c + ['--detach=%s' % self.params['detach']]
def addparam_detach_keys(self, c):
@@ -362,7 +407,9 @@ class PodmanModuleParams:
return c
def addparam_env_file(self, c):
- return c + ['--env-file', self.params['env_file']]
+ for env_file in self.params['env_file']:
+ c += ['--env-file', env_file]
+ return c
def addparam_env_host(self, c):
self.check_version('--env-host', minv='1.5.0')
@@ -407,6 +454,10 @@ class PodmanModuleParams:
return c + ['--healthcheck-timeout',
self.params['healthcheck_timeout']]
+ def addparam_healthcheck_failure_action(self, c):
+ return c + ['--health-on-failure',
+ self.params['healthcheck_failure_action']]
+
def addparam_hooks_dir(self, c):
for hook_dir in self.params['hooks_dir']:
c += ['--hooks-dir=%s' % hook_dir]
@@ -722,6 +773,35 @@ class PodmanContainerDiff:
params_with_defaults[p] = self.module_params[p]
return params_with_defaults
+ def _createcommand(self, argument):
+ """Returns list of values for given argument from CreateCommand
+ from Podman container inspect output.
+
+ Args:
+ argument (str): argument name
+
+ Returns:
+
+ all_values: list of values for given argument from createcommand
+ """
+ if "createcommand" not in self.info["config"]:
+ return []
+ cr_com = self.info["config"]["createcommand"]
+ argument_values = ARGUMENTS_OPTS_DICT.get(argument, [argument])
+ all_values = []
+ for arg in argument_values:
+ for ind, cr_opt in enumerate(cr_com):
+ if arg == cr_opt:
+ # This is a key=value argument
+ if not cr_com[ind + 1].startswith("-"):
+ all_values.append(cr_com[ind + 1])
+ else:
+ # This is a false/true switching argument
+ return [True]
+ if cr_opt.startswith("%s=" % arg):
+ all_values.append(cr_opt.split("=", 1)[1])
+ return all_values
+
def _diff_update_and_compare(self, param_name, before, after):
if before != after:
self.diff['before'].update({param_name: before})
@@ -737,7 +817,7 @@ class PodmanContainerDiff:
return self._diff_update_and_compare('annotation', before, after)
def diffparam_env_host(self):
- # It's impossible to get from inspest, recreate it if not default
+ # It's impossible to get from inspect, recreate it if not default
before = False
after = self.params['env_host']
return self._diff_update_and_compare('env_host', before, after)
@@ -826,9 +906,16 @@ class PodmanContainerDiff:
def diffparam_cpu_period(self):
before = self.info['hostconfig']['cpuperiod']
- after = self.params['cpu_period']
+ # if cpu_period left to default keep settings
+ after = self.params['cpu_period'] or before
return self._diff_update_and_compare('cpu_period', before, after)
+ def diffparam_cpu_quota(self):
+ before = self.info['hostconfig']['cpuquota']
+ # if cpu_quota left to default keep settings
+ after = self.params['cpu_quota'] or before
+ return self._diff_update_and_compare('cpu_quota', before, after)
+
def diffparam_cpu_rt_period(self):
before = self.info['hostconfig']['cpurealtimeperiod']
after = self.params['cpu_rt_period']
@@ -845,8 +932,9 @@ class PodmanContainerDiff:
return self._diff_update_and_compare('cpu_shares', before, after)
def diffparam_cpus(self):
- before = int(self.info['hostconfig']['nanocpus']) / 1000000000
- after = self.params['cpus']
+ before = self.info['hostconfig']['nanocpus'] / 1000000000
+ # if cpus left to default keep settings
+ after = float(self.params['cpus'] or before)
return self._diff_update_and_compare('cpus', before, after)
def diffparam_cpuset_cpus(self):
@@ -863,16 +951,13 @@ class PodmanContainerDiff:
before = [":".join([i['pathonhost'], i['pathincontainer']])
for i in self.info['hostconfig']['devices']]
if not before and 'createcommand' in self.info['config']:
- cr_com = self.info['config']['createcommand']
- if '--device' in cr_com:
- before = [cr_com[k + 1].lower()
- for k, i in enumerate(cr_com) if i == '--device']
+ before = [i.lower() for i in self._createcommand('--device')]
before = [":".join((i, i))
if len(i.split(":")) == 1 else i for i in before]
after = [":".join(i.split(":")[:2]) for i in self.params['device']]
after = [":".join((i, i))
if len(i.split(":")) == 1 else i for i in after]
- after = [i.lower() for i in after]
+ before, after = [i.lower() for i in before], [i.lower() for i in after]
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('devices', before, after)
@@ -931,15 +1016,23 @@ class PodmanContainerDiff:
# Healthcheck is only defined in container config if a healthcheck
# was configured; otherwise the config key isn't part of the config.
def diffparam_healthcheck(self):
+ before = ''
if 'healthcheck' in self.info['config']:
# the "test" key is a list of 2 items where the first one is
# "CMD-SHELL" and the second one is the actual healthcheck command.
- before = self.info['config']['healthcheck']['test'][1]
- else:
- before = ''
+ if len(self.info['config']['healthcheck']['test']) > 1:
+ before = self.info['config']['healthcheck']['test'][1]
after = self.params['healthcheck'] or before
return self._diff_update_and_compare('healthcheck', before, after)
+ def diffparam_healthcheck_failure_action(self):
+ if 'healthcheckonfailureaction' in self.info['config']:
+ before = self.info['config']['healthcheckonfailureaction']
+ else:
+ before = ''
+ after = self.params['healthcheck_failure_action'] or before
+ return self._diff_update_and_compare('healthcheckonfailureaction', before, after)
+
# Because of hostname is random generated, this parameter has partial idempotency only.
def diffparam_hostname(self):
before = self.info['config']['hostname']
@@ -1066,9 +1159,8 @@ class PodmanContainerDiff:
if macs:
before = macs[0]
if not before and 'createcommand' in self.info['config']:
- cr_com = self.info['config']['createcommand']
- if '--mac-address' in cr_com:
- before = cr_com[cr_com.index('--mac-address') + 1].lower()
+ before = [i.lower() for i in self._createcommand('--mac-address')]
+ before = before[0] if before else ''
if self.module_params['mac_address'] is not None:
after = self.params['mac_address']
else:
@@ -1084,11 +1176,10 @@ class PodmanContainerDiff:
before = []
# Special case for options for slirp4netns rootless networking from v2
if net_mode_before == 'slirp4netns' and 'createcommand' in self.info['config']:
- cr_com = self.info['config']['createcommand']
- if '--network' in cr_com:
- cr_net = cr_com[cr_com.index('--network') + 1].lower()
- if 'slirp4netns:' in cr_net:
- before = [cr_net]
+ cr_net = [i.lower() for i in self._createcommand('--network')]
+ for cr_net_opt in cr_net:
+ if 'slirp4netns:' in cr_net_opt:
+ before = [cr_net_opt]
after = self.params['network'] or []
# If container is in pod and no networks are provided
if not self.module_params['network'] and self.params['pod']:
@@ -1124,8 +1215,19 @@ class PodmanContainerDiff:
return self._diff_update_and_compare('privileged', before, after)
def diffparam_pid(self):
+ def get_container_id_by_name(name):
+ rc, podman_inspect_info, err = self.module.run_command(
+ [self.module.params["executable"], "inspect", name, "-f", "{{.Id}}"])
+ if rc != 0:
+ return None
+ return podman_inspect_info.strip()
+
before = self.info['hostconfig']['pidmode']
after = self.params['pid']
+ if after is not None and "container:" in after and "container:" in before:
+ if after.split(":")[1] == before.split(":")[1]:
+ return self._diff_update_and_compare('pid', before, after)
+ after = "container:" + get_container_id_by_name(after.split(":")[1])
return self._diff_update_and_compare('pid', before, after)
# TODO(sshnaidm) Need to add port ranges support
@@ -1150,7 +1252,7 @@ class PodmanContainerDiff:
if image_ports:
after += list(image_ports.keys())
after = [
- i.replace("/tcp", "").replace("[", "").replace("]", "")
+ i.replace("/tcp", "").replace("[", "").replace("]", "").replace("0.0.0.0:", "")
for i in after]
# No support for port ranges yet
for ports in after:
@@ -1166,7 +1268,15 @@ class PodmanContainerDiff:
def diffparam_restart_policy(self):
before = self.info['hostconfig']['restartpolicy']['name']
+ before_max_count = int(self.info['hostconfig']['restartpolicy'].get('maximumretrycount', 0))
after = self.params['restart_policy'] or ""
+ if ':' in after:
+ after, after_max_count = after.rsplit(':', 1)
+ after_max_count = int(after_max_count)
+ else:
+ after_max_count = 0
+ before = "%s:%i" % (before, before_max_count)
+ after = "%s:%i" % (after, after_max_count)
return self._diff_update_and_compare('restart_policy', before, after)
def diffparam_rm(self):
@@ -1175,11 +1285,16 @@ class PodmanContainerDiff:
return self._diff_update_and_compare('rm', before, after)
def diffparam_security_opt(self):
- before = self.info['hostconfig']['securityopt']
- # In rootful containers with apparmor there is a default security opt
- before = [o for o in before if 'apparmor=containers-default' not in o]
- after = self.params['security_opt']
- before, after = sorted(list(set(before))), sorted(list(set(after)))
+ unsorted_before = self.info['hostconfig']['securityopt']
+ unsorted_after = self.params['security_opt']
+ # In rootful containers with apparmor there is a profile, "container-default",
+ # which is already added by default
+ # Since SElinux labels are basically annotations, they are merged in a single list
+ # element by podman so we need to split them in a (sorted) list if we want to compare it
+ # to the list we provide to the module
+ before = sorted(item for element in unsorted_before for item in element.split(',')
+ if 'apparmor=container-default' not in item)
+ after = sorted(list(set(unsorted_after)))
return self._diff_update_and_compare('security_opt', before, after)
def diffparam_stop_signal(self):
@@ -1206,11 +1321,7 @@ class PodmanContainerDiff:
after = self.params['ulimit'] or []
# In case of latest podman
if 'createcommand' in self.info['config']:
- ulimits = []
- for k, c in enumerate(self.info['config']['createcommand']):
- if c == '--ulimit':
- ulimits.append(self.info['config']['createcommand'][k + 1])
- before = ulimits
+ before = self._createcommand('--ulimit')
before, after = sorted(before), sorted(after)
return self._diff_update_and_compare('ulimit', before, after)
if after:
@@ -1428,8 +1539,6 @@ class PodmanContainer:
self.version,
self.module,
).construct_command_from_params()
- if action == 'create':
- b_command.remove(b'--detach=True')
full_cmd = " ".join([self.module_params['executable']]
+ [to_native(i) for i in b_command])
self.actions.append(full_cmd)
@@ -1449,7 +1558,7 @@ class PodmanContainer:
self.stderr = err
if rc != 0:
self.module.fail_json(
- msg="Can't %s container %s" % (action, self.name),
+ msg="Container %s exited with code %s when %sed" % (self.name, rc, action),
stdout=out, stderr=err)
def run(self):
@@ -1547,11 +1656,19 @@ class PodmanManager:
self.results.update({'diff': self.container.diff})
if self.module.params['debug'] or self.module_params['debug']:
self.results.update({'podman_version': self.container.version})
+ sysd = generate_systemd(self.module,
+ self.module_params,
+ self.name,
+ self.container.version)
+ self.results['changed'] = changed or sysd['changed']
self.results.update(
- {'podman_systemd': generate_systemd(self.module,
- self.module_params,
- self.name,
- self.container.version)})
+ {'podman_systemd': sysd['systemd']})
+ if sysd['diff']:
+ if 'diff' not in self.results:
+ self.results.update({'diff': sysd['diff']})
+ else:
+ self.results['diff']['before'] += sysd['diff']['before']
+ self.results['diff']['after'] += sysd['diff']['after']
def make_started(self):
"""Run actions if desired state is 'started'."""
@@ -1601,7 +1718,8 @@ class PodmanManager:
self.results['actions'].append('started %s' % self.container.name)
self.update_container_result()
return
- elif self.container.stopped and self.container.different:
+ elif self.container.stopped and \
+ (self.container.different or self.recreate):
self.container.recreate_run()
self.results['actions'].append('recreated %s' %
self.container.name)
diff --git a/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py b/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py
index 0b4afc0bc..4106136e2 100644
--- a/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py
@@ -27,10 +27,15 @@ ARGUMENTS_SPEC_POD = dict(
recreate=dict(type='bool', default=False),
add_host=dict(type='list', required=False, elements='str'),
cgroup_parent=dict(type='str', required=False),
+ blkio_weight=dict(type='str', required=False),
+ blkio_weight_device=dict(type='list', elements='str', required=False),
cpus=dict(type='str', required=False),
cpuset_cpus=dict(type='str', required=False),
+ cpuset_mems=dict(type='str', required=False),
+ cpu_shares=dict(type='str', required=False),
device=dict(type='list', elements='str', required=False),
device_read_bps=dict(type='list', elements='str', required=False),
+ device_write_bps=dict(type='list', elements='str', required=False),
dns=dict(type='list', elements='str', required=False),
dns_opt=dict(type='list', elements='str', required=False),
dns_search=dict(type='list', elements='str', required=False),
@@ -46,6 +51,8 @@ ARGUMENTS_SPEC_POD = dict(
label=dict(type='dict', required=False),
label_file=dict(type='str', required=False),
mac_address=dict(type='str', required=False),
+ memory=dict(type='str', required=False),
+ memory_swap=dict(type='str', required=False),
name=dict(type='str', required=True),
network=dict(type='list', elements='str', required=False),
network_alias=dict(type='list', elements='str', required=False,
@@ -135,25 +142,52 @@ class PodmanPodModuleParams:
c += ['--add-host', g]
return c
+ def addparam_blkio_weight(self, c):
+ self.check_version('--blkio-weight', minv='4.3.0')
+ return c + ['--blkio-weight', self.params['blkio_weight']]
+
+ def addparam_blkio_weight_device(self, c):
+ self.check_version('--blkio-weight-device', minv='4.3.0')
+ for dev in self.params['blkio_weight_device']:
+ c += ['--blkio-weight-device', dev]
+ return c
+
def addparam_cgroup_parent(self, c):
return c + ['--cgroup-parent', self.params['cgroup_parent']]
def addparam_cpus(self, c):
+ self.check_version('--cpus', minv='4.2.0')
return c + ['--cpus', self.params['cpus']]
def addparam_cpuset_cpus(self, c):
+ self.check_version('--cpus', minv='4.2.0')
return c + ['--cpuset-cpus', self.params['cpuset_cpus']]
+ def addparam_cpuset_mems(self, c):
+ self.check_version('--cpuset-mems', minv='4.3.0')
+ return c + ['--cpuset-mems', self.params['cpuset_mems']]
+
+ def addparam_cpu_shares(self, c):
+ self.check_version('--cpu-shares', minv='4.3.0')
+ return c + ['--cpu-shares', self.params['cpu_shares']]
+
def addparam_device(self, c):
for dev in self.params['device']:
c += ['--device', dev]
return c
def addparam_device_read_bps(self, c):
+ self.check_version('--device-read-bps', minv='4.3.0')
for dev in self.params['device_read_bps']:
c += ['--device-read-bps', dev]
return c
+ def addparam_device_write_bps(self, c):
+ self.check_version('--device-write-bps', minv='4.3.0')
+ for dev in self.params['device_write_bps']:
+ c += ['--device-write-bps', dev]
+ return c
+
def addparam_dns(self, c):
for g in self.params['dns']:
c += ['--dns', g]
@@ -209,6 +243,14 @@ class PodmanPodModuleParams:
def addparam_mac_address(self, c):
return c + ['--mac-address', self.params['mac_address']]
+ def addparam_memory(self, c):
+ self.check_version('--memory', minv='4.2.0')
+ return c + ['--memory', self.params['memory']]
+
+ def addparam_memory_swap(self, c):
+ self.check_version('--memory-swap', minv='4.3.0')
+ return c + ['--memory-swap', self.params['memory_swap']]
+
def addparam_name(self, c):
return c + ['--name', self.params['name']]
@@ -323,10 +365,8 @@ class PodmanPodDiff:
return self._diff_update_and_compare('add_host', before, after)
def diffparam_cgroup_parent(self):
- if 'cgroupparent' in self.info:
- before = self.info['cgroupparent']
- elif 'config' in self.info and self.info['config'].get('cgroupparent'):
- before = self.info['config']['cgroupparent']
+ before = (self.info.get('cgroupparent', '')
+ or self.info.get('hostconfig', {}).get('cgroupparent', ''))
after = self.params['cgroup_parent'] or before
return self._diff_update_and_compare('cgroup_parent', before, after)
@@ -511,7 +551,7 @@ class PodmanPodDiff:
# TODO: find out why on Ubuntu the 'net' is not present
if 'net' not in before:
after.remove('net')
- if self.params["uidmap"] or self.params["gidmap"]:
+ if self.params["uidmap"] or self.params["gidmap"] or self.params["userns"]:
after.append('user')
before, after = sorted(list(set(before))), sorted(list(set(after)))
@@ -759,11 +799,19 @@ class PodmanPodManager:
self.results.update({'diff': self.pod.diff})
if self.module.params['debug'] or self.module_params['debug']:
self.results.update({'podman_version': self.pod.version})
+ sysd = generate_systemd(self.module,
+ self.module_params,
+ self.name,
+ self.pod.version)
+ self.results['changed'] = changed or sysd['changed']
self.results.update(
- {'podman_systemd': generate_systemd(self.module,
- self.module_params,
- self.name,
- self.pod.version)})
+ {'podman_systemd': sysd['systemd']})
+ if sysd['diff']:
+ if 'diff' not in self.results:
+ self.results.update({'diff': sysd['diff']})
+ else:
+ self.results['diff']['before'] += sysd['diff']['before']
+ self.results['diff']['after'] += sysd['diff']['after']
def execute(self):
"""Execute the desired action according to map of actions & states."""
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_container.py b/ansible_collections/containers/podman/plugins/modules/podman_container.py
index 7878352da..51cb57a53 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_container.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_container.py
@@ -76,6 +76,15 @@ options:
- Add an annotation to the container. The format is key value, multiple
times.
type: dict
+ attach:
+ description:
+ - Attach to STDIN, STDOUT or STDERR. The default in Podman is false.
+ type: list
+ elements: str
+ choices:
+ - stdin
+ - stdout
+ - stderr
authfile:
description:
- Path of the authentication file. Default is
@@ -149,7 +158,11 @@ options:
type: raw
cpu_period:
description:
- - Limit the CPU real-time period in microseconds
+ - Limit the CPU CFS (Completely Fair Scheduler) period
+ type: int
+ cpu_quota:
+ description:
+ - Limit the CPU CFS (Completely Fair Scheduler) quota
type: int
cpu_rt_period:
description:
@@ -180,6 +193,22 @@ options:
- Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only
effective on NUMA systems.
type: str
+ delete_depend:
+ description:
+ - Remove selected container and recursively remove all containers that depend on it.
+ Applies to "delete" command.
+ type: bool
+ delete_time:
+ description:
+ - Seconds to wait before forcibly stopping the container. Use -1 for infinite wait.
+ Applies to "delete" command.
+ type: str
+ delete_volumes:
+ description:
+ - Remove anonymous volumes associated with the container.
+ This does not include named volumes created with podman volume create,
+ or the --volume option of podman run and podman create.
+ type: bool
detach:
description:
- Run container in detach mode
@@ -262,7 +291,14 @@ options:
- Read in a line delimited file of environment variables. Doesn't support
idempotency. If users changes the file with environment variables it's
on them to recreate the container.
- type: path
+ The file must be present on the REMOTE machine where actual podman is
+ running, not on the controller machine where Ansible is executing.
+ If you need to copy the file from controller to remote machine, use the
+ copy or slurp module.
+ type: list
+ elements: path
+ aliases:
+ - env_files
env_host:
description:
- Use all current host environment variables in container.
@@ -292,6 +328,11 @@ options:
default: False
aliases:
- restart
+ force_delete:
+ description:
+ - Force deletion of container when it's being deleted.
+ type: bool
+ default: True
generate_systemd:
description:
- Generate systemd unit file for container.
@@ -319,11 +360,21 @@ options:
- 'on-watchdog'
- 'on-abort'
- 'always'
- time:
+ restart_sec:
+ description: Set the systemd service restartsec value.
+ type: int
+ required: false
+ start_timeout:
+ description: Override the default start timeout for the container with the given value.
+ type: int
+ required: false
+ stop_timeout:
description:
- - Override the default stop timeout for the container with the given value.
+ - Override the default stop timeout for the container with the given value. Called `time` before version 4.
type: int
required: false
+ aliases:
+ - time
no_header:
description:
- Do not generate the header including meta data such as the Podman version and the timestamp.
@@ -416,6 +467,17 @@ options:
is considered failed. Like start-period, the value can be expressed in
a time format such as 1m22s. The default value is 30s
type: str
+ healthcheck_failure_action:
+ description:
+ - The action to be taken when the container is considered unhealthy. The action must be one of
+ "none", "kill", "restart", or "stop".
+ The default policy is "none".
+ type: str
+ choices:
+ - 'none'
+ - 'kill'
+ - 'restart'
+ - 'stop'
hooks_dir:
description:
- Each .json file in the path configures a hook for Podman containers.
@@ -693,6 +755,11 @@ options:
* always - Restart containers when they exit, regardless of status,
retrying indefinitely
type: str
+ restart_time:
+ description:
+ - Seconds to wait before forcibly stopping the container when restarting. Use -1 for infinite wait.
+ Applies to "restarted" status.
+ type: str
rm:
description:
- Automatically remove the container when it exits. The default is false.
@@ -740,6 +807,11 @@ options:
description:
- Signal to stop a container. Default is SIGTERM.
type: int
+ stop_time:
+ description:
+ - Seconds to wait before forcibly stopping the container. Use -1 for infinite wait.
+ Applies to "stopped" status.
+ type: str
stop_timeout:
description:
- Timeout (in seconds) to stop a container. Default is 10.
@@ -860,7 +932,7 @@ EXAMPLES = r"""
generate_systemd:
path: /tmp/
restart_policy: always
- time: 120
+ stop_timeout: 120
names: true
container_prefix: ainer
@@ -912,6 +984,16 @@ EXAMPLES = r"""
image: busybox
log_options: path=/var/log/container/mycontainer.json
log_driver: k8s-file
+
+- name: Run container with complex command with quotes
+ containers.podman.podman_container:
+ name: mycontainer
+ image: certbot/certbot
+ command:
+ - renew
+ - --deploy-hook
+ - "echo 1 > /var/lib/letsencrypt/complete"
+
"""
RETURN = r"""
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_container_exec.py b/ansible_collections/containers/podman/plugins/modules/podman_container_exec.py
new file mode 100644
index 000000000..d30e85cdb
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/modules/podman_container_exec.py
@@ -0,0 +1,244 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# 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'''
+module: podman_container_exec
+author:
+ - Takuya Nishimura (@nishipy)
+short_description: Executes a command in a running container.
+description:
+ - Executes a command in a running container.
+options:
+ name:
+ description:
+ - Name of the container where the command is executed.
+ type: str
+ required: true
+ command:
+ description:
+ - The command to run in the container.
+ - One of the I(command) or I(args) is required.
+ type: str
+ argv:
+ description:
+ - Passes the command as a list rather than a string.
+ - One of the I(command) or I(args) is required.
+ type: list
+ elements: str
+ detach:
+ description:
+ - If true, the command runs in the background.
+ - The exec session is automatically removed when it completes.
+ type: bool
+ default: false
+ env:
+ description:
+ - Set environment variables.
+ type: dict
+ privileged:
+ description:
+ - Give extended privileges to the container.
+ type: bool
+ default: false
+ tty:
+ description:
+ - Allocate a pseudo-TTY.
+ type: bool
+ default: false
+ user:
+ description:
+ - The username or UID used and, optionally, the groupname or GID for the specified command.
+ - Both user and group may be symbolic or numeric.
+ type: str
+ workdir:
+ description:
+ - Working directory inside the container.
+ type: str
+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'''
+- name: Execute a command with workdir
+ containers.podman.podman_container_exec:
+ name: ubi8
+ command: "cat redhat-release"
+ workdir: /etc
+
+- name: Execute a command with a list of args and environment variables
+ containers.podman.podman_container_exec:
+ name: test_container
+ argv:
+ - /bin/sh
+ - -c
+ - echo $HELLO $BYE
+ env:
+ HELLO: hello world
+ BYE: goodbye world
+
+- name: Execute command in background by using detach
+ containers.podman.podman_container_exec:
+ name: detach_container
+ command: "cat redhat-release"
+ detach: true
+'''
+
+RETURN = r'''
+stdout:
+ type: str
+ returned: success
+ description:
+ - The standard output of the command executed in the container.
+stderr:
+ type: str
+ returned: success
+ description:
+ - The standard output of the command executed in the container.
+rc:
+ type: int
+ returned: success
+ sample: 0
+ description:
+ - The exit code of the command executed in the container.
+exec_id:
+ type: str
+ returned: success and I(detach=true)
+ 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
+
+
+def run_container_exec(module: AnsibleModule) -> dict:
+ '''
+ Execute podman-container-exec for the given options
+ '''
+ 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']
+
+ if command is not None:
+ argv = shlex.split(command)
+
+ if 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))
+
+ to_text(value, errors='surrogate_or_strict')
+ exec_options += ['--env',
+ '%s="%s"' % (key, value)]
+
+ if privileged:
+ exec_options.append('--privileged')
+
+ if tty:
+ exec_options.append('--tty')
+
+ if user is not None:
+ 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.append(name)
+ exec_options.extend(argv)
+
+ exec_with_args.extend(exec_options)
+
+ rc, stdout, stderr = run_podman_command(
+ module=module, executable='podman', args=exec_with_args)
+
+ result = {
+ 'changed': changed,
+ 'podman_command': exec_options,
+ 'rc': rc,
+ 'stdout': stdout,
+ 'stderr': stderr,
+ }
+
+ if detach:
+ result['exec_id'] = stdout.replace('\n', '')
+
+ return result
+
+
+def main():
+ argument_spec = {
+ 'name': {
+ 'type': 'str',
+ 'required': True,
+ },
+ 'command': {
+ 'type': 'str',
+ },
+ 'argv': {
+ 'type': 'list',
+ 'elements': 'str',
+ },
+ 'detach': {
+ 'type': 'bool',
+ 'default': False,
+ },
+ 'env': {
+ 'type': 'dict',
+ },
+ 'privileged': {
+ 'type': 'bool',
+ 'default': False,
+ },
+ 'tty': {
+ 'type': 'bool',
+ 'default': False,
+ },
+ 'user': {
+ 'type': 'str',
+ },
+ 'workdir': {
+ 'type': 'str',
+ },
+ }
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_one_of=[('argv', 'command')],
+ )
+
+ result = run_container_exec(module)
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_container_info.py b/ansible_collections/containers/podman/plugins/modules/podman_container_info.py
index bbdd29fb9..dd361c449 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_container_info.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_container_info.py
@@ -50,7 +50,7 @@ EXAMPLES = r"""
RETURN = r"""
containers:
- description: Facts from all or specificed containers
+ description: Facts from all or specified containers
returned: always
type: list
elements: dict
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_containers.py b/ansible_collections/containers/podman/plugins/modules/podman_containers.py
index c67aee344..7f418a67b 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_containers.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_containers.py
@@ -41,7 +41,7 @@ EXAMPLES = '''
- name: web
image: nginx
- name: test
- image: python:3-alpine
+ image: python:3.10-alpine
command: python -V
'''
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_export.py b/ansible_collections/containers/podman/plugins/modules/podman_export.py
index e2bb19614..dda0099cb 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_export.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_export.py
@@ -24,7 +24,10 @@ options:
description:
- Container to export.
type: str
- required: true
+ volume:
+ description:
+ - Volume to export.
+ type: str
force:
description:
- Force saving to file even if it exists.
@@ -48,6 +51,9 @@ EXAMPLES = '''
- containers.podman.podman_export:
dest: /path/to/tar/file
container: container-name
+- containers.podman.podman_export:
+ dest: /path/to/tar/file
+ volume: volume-name
'''
import os # noqa: E402
@@ -57,8 +63,16 @@ from ..module_utils.podman.common import remove_file_or_dir # noqa: E402
def export(module, executable):
changed = False
- command = [executable, 'export']
- command += ['-o=%s' % module.params['dest'], module.params['container']]
+ export_type = ''
+ command = []
+ if module.params['container']:
+ export_type = 'container'
+ command = [executable, 'export']
+ else:
+ 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']
if os.path.exists(dest):
@@ -75,8 +89,8 @@ def export(module, executable):
return changed, '', ''
rc, out, err = module.run_command(command)
if rc != 0:
- module.fail_json(msg="Error exporting container %s: %s" % (
- module.params['container'], err))
+ module.fail_json(msg="Error exporting %s %s: %s" % (export_type,
+ module.params['container'], err))
return changed, out, err
@@ -84,11 +98,18 @@ def main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(type='str', required=True),
- container=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'),
+ ],
+ required_one_of=[
+ ('container', 'volume'),
+ ],
)
executable = module.get_bin_path(module.params['executable'], required=True)
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_generate_systemd.py b/ansible_collections/containers/podman/plugins/modules/podman_generate_systemd.py
index 9c9bc7b27..486a18a86 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_generate_systemd.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_generate_systemd.py
@@ -27,6 +27,12 @@ options:
- Use C(/etc/systemd/system) for the system-wide systemd instance.
- Use C(/etc/systemd/user) or C(~/.config/systemd/user) for use with per-user instances of systemd.
type: path
+ force:
+ description:
+ - Replace the systemd unit file(s) even if it already exists.
+ - This works with dest option.
+ type: bool
+ default: false
new:
description:
- Generate unit files that create containers and pods, not only start them.
@@ -219,7 +225,7 @@ podman_command:
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
RESTART_POLICY_CHOICES = [
'no-restart',
@@ -388,7 +394,7 @@ def generate_systemd(module):
# In case of error in running the command
if return_code != 0:
- # Print informations about the error and return and empty dictionary
+ # 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}.'
@@ -425,7 +431,7 @@ def generate_systemd(module):
changed = True
# If destination exist but not a directory
if not os.path.isdir(systemd_units_dest):
- # Stop and tell user that the destination is not a directry
+ # Stop and tell user that the destination is not a directory
message = "Destination {systemd_units_dest} is not a directory."
message += " Can't save systemd unit files in."
module.fail_json(
@@ -446,26 +452,13 @@ def generate_systemd(module):
unit_file_name,
)
- # See if we need to write the unit file, default yes
- need_to_write_file = True
- # If the unit file already exist, compare it with the
- # generated content
- if os.path.exists(unit_file_full_path):
- # Read the file
- with open(unit_file_full_path, 'r') as unit_file:
- current_unit_file_content = unit_file.read()
- # If current unit file content is the same as the
- # generated content
- # Remove comments from files, before comparing
- current_unit_file_content_nocmnt = "\n".join([
- line for line in current_unit_file_content.splitlines()
- if not line.startswith('#')])
- unit_content_nocmnt = "\n".join([
- line for line in unit_content.splitlines()
- if not line.startswith('#')])
- if current_unit_file_content_nocmnt == unit_content_nocmnt:
- # We don't need to write it
- need_to_write_file = False
+ 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))
# Write the file, if needed
if need_to_write_file:
@@ -506,6 +499,11 @@ def run_module():
'required': False,
'default': False,
},
+ 'force': {
+ 'type': 'bool',
+ 'required': False,
+ 'default': False,
+ },
'restart_policy': {
'type': 'str',
'required': False,
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_image.py b/ansible_collections/containers/podman/plugins/modules/podman_image.py
index d66ff5d49..6305a5d5b 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_image.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_image.py
@@ -17,7 +17,7 @@ DOCUMENTATION = r'''
options:
arch:
description:
- - CPU architecutre for the container image
+ - CPU architecture for the container image
type: str
name:
description:
@@ -132,6 +132,10 @@ DOCUMENTATION = r'''
description:
- Extra args to pass to build, if executed. Does not idempotently check for new build args.
type: str
+ target:
+ description:
+ - Specify the target build stage to build.
+ type: str
push_args:
description: Arguments that control pushing images.
type: dict
@@ -512,6 +516,8 @@ class PodmanImageManager(object):
if not self.module.check_mode:
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()
@@ -536,12 +542,21 @@ class PodmanImageManager(object):
image_name = self.image_name
args = ['image', 'ls', image_name, '--format', 'json']
rc, images, err = self._run(args, ignore_errors=True)
- images = json.loads(images)
+ 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))
+ if len(images) == 0:
+ # Let's find out if image exists
+ 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
if len(images) > 0:
inspect_json = self.inspect_image(image_name)
- if self._is_target_arch(inspect_json, self.arch) or not self.arch:
- return images
-
+ if self._is_target_arch(inspect_json, self.arch) or not self.arch:
+ return images or inspect_json
return None
def _is_target_arch(self, inspect_json=None, arch=None):
@@ -565,7 +580,10 @@ class PodmanImageManager(object):
image_name = self.image_name
args = ['inspect', image_name, '--format', 'json']
rc, image_data, err = self._run(args)
- image_data = json.loads(image_data)
+ 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))
if len(image_data) > 0:
return image_data
else:
@@ -656,6 +674,10 @@ class PodmanImageManager(object):
if extra_args:
args.extend(shlex.split(extra_args))
+ target = self.build.get('target')
+ if target:
+ args.extend(['--target', target])
+
args.append(self.path)
rc, out, err = self._run(args, ignore_errors=True)
@@ -812,6 +834,7 @@ def main():
rm=dict(type='bool', default=True),
volume=dict(type='list', elements='str'),
extra_args=dict(type='str'),
+ target=dict(type='str'),
),
),
push_args=dict(
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_import.py b/ansible_collections/containers/podman/plugins/modules/podman_import.py
index 5090b177c..6a408c08e 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_import.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_import.py
@@ -29,6 +29,10 @@ options:
- Set changes as list of key-value pairs, see example.
type: list
elements: dict
+ volume:
+ description:
+ - Volume to import, cannot be used with change and commit_message
+ type: str
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
@@ -95,6 +99,9 @@ EXAMPLES = '''
- "CMD": /bin/bash
- "User": root
commit_message: "Importing image"
+- containers.podman.podman_import:
+ src: /path/to/tar/file
+ volume: myvolume
'''
import json # noqa: E402
@@ -128,25 +135,55 @@ def load(module, executable):
return changed, out, err, info, command
+def volume_load(module, executable):
+ changed = True
+ command = [executable, 'volume', 'import', module.params['volume'], module.params['src']]
+ src = module.params['src']
+ if module.check_mode:
+ 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']])
+ if rc != 0:
+ 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))
+ 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')
+ executable=dict(type='str', default='podman'),
+ volume=dict(type='str', required=False),
),
+ mutually_exclusive=[
+ ('volume', 'commit_message'),
+ ('volume', 'change'),
+ ],
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
- changed, out, err, image_info, command = load(module, executable)
+ 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)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
"image": image_info,
+ "volume": volume_info,
"podman_command": " ".join(command)
}
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_login.py b/ansible_collections/containers/podman/plugins/modules/podman_login.py
index be417c761..8ae8418a9 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_login.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_login.py
@@ -75,7 +75,7 @@ EXAMPLES = r"""
username: user
password: 'p4ssw0rd'
-- name: Login to default registry and create ${XDG_RUNTIME_DIR}/containers/auth.json
+- name: Login to quay.io and create ${XDG_RUNTIME_DIR}/containers/auth.json
containers.podman.podman_login:
username: user
password: 'p4ssw0rd'
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_network.py b/ansible_collections/containers/podman/plugins/modules/podman_network.py
index 846524b65..3f52af4ce 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_network.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_network.py
@@ -37,10 +37,22 @@ options:
description:
- Driver to manage the network (default "bridge")
type: str
+ force:
+ description:
+ - Remove all containers that use the network.
+ If the container is running, it is stopped and removed.
+ default: False
+ type: bool
gateway:
description:
- IPv4 or IPv6 gateway for the subnet
type: str
+ interface_name:
+ description:
+ - For bridge, it uses the bridge interface name.
+ For macvlan, it is the parent device on the host (it is the same
+ as 'opt.parent')
+ type: str
internal:
description:
- Restrict external access from this network (default "false")
@@ -92,7 +104,8 @@ options:
required: false
parent:
description:
- - The host device which should be used for the macvlan interface.
+ - The host device which should be used for the macvlan interface
+ (it is the same as 'interface' in that case).
Defaults to the default route interface.
type: str
required: false
@@ -219,14 +232,15 @@ class PodmanNetworkModuleParams:
list -- list of byte strings for Popen command
"""
if self.action in ['delete']:
- return self._simple_action()
+ return self._delete_action()
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]
+ 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]
def _create_action(self):
cmd = [self.action, self.params['name']]
@@ -270,6 +284,9 @@ class PodmanNetworkModuleParams:
def addparam_macvlan(self, c):
return c + ['--macvlan', self.params['macvlan']]
+ def addparam_interface_name(self, c):
+ return c + ['--interface-name', self.params['interface_name']]
+
def addparam_internal(self, c):
return c + ['--internal=%s' % self.params['internal']]
@@ -291,7 +308,6 @@ class PodmanNetworkDefaults:
self.version = podman_version
self.defaults = {
'driver': 'bridge',
- 'disable_dns': False,
'internal': False,
'ipv6': False
}
@@ -334,6 +350,9 @@ class PodmanNetworkDiff:
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:
+ 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)
@@ -642,7 +661,9 @@ def main():
name=dict(type='str', required=True),
disable_dns=dict(type='bool', 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),
ipv6=dict(type='bool', required=False),
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_play.py b/ansible_collections/containers/podman/plugins/modules/podman_play.py
index 04a30441b..10a9a06fa 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_play.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_play.py
@@ -29,6 +29,12 @@ options:
- Path to file with YAML configuration for a Pod.
type: path
required: True
+ annotation:
+ description:
+ - Add an annotation to the container or pod.
+ type: dict
+ aliases:
+ - annotations
authfile:
description:
- Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json,
@@ -37,6 +43,11 @@ options:
Note - You can also override the default path of the authentication file
by setting the REGISTRY_AUTH_FILE environment variable. export REGISTRY_AUTH_FILE=path
type: path
+ build:
+ description:
+ - Build images even if they are found in the local storage.
+ - It is required to exist subdirectories matching the image names to be build.
+ type: bool
cert_dir:
description:
- Use certificates at path (*.crt, *.cert, *.key) to connect to the registry.
@@ -51,6 +62,11 @@ options:
Kubernetes configmap YAMLs
type: list
elements: path
+ context_dir:
+ description:
+ - Use path as the build context directory for each image.
+ Requires build option be true.
+ type: path
seccomp_profile_root:
description:
- Directory path for seccomp profiles (default is "/var/lib/kubelet/seccomp").
@@ -68,6 +84,28 @@ options:
description:
- Set logging driver for all created containers.
type: str
+ log_opt:
+ description:
+ - Logging driver specific options. Set custom logging configuration.
+ type: dict
+ aliases:
+ - log_options
+ suboptions:
+ path:
+ description:
+ - specify a path to the log file (e.g. /var/log/container/mycontainer.json).
+ type: str
+ required: false
+ max_size:
+ description:
+ - Specify a max size of the log file (e.g 10mb).
+ type: str
+ required: false
+ tag:
+ description:
+ - specify a custom log tag for the container. This option is currently supported only by the journald log driver in Podman.
+ type: str
+ required: false
log_level:
description:
- Set logging level for podman calls. Log messages above specified level
@@ -128,6 +166,18 @@ EXAMPLES = '''
kube_file: ~/kube.yaml
state: started
+- name: Recreate pod from a kube file with options
+ containers.podman.podman_play:
+ kube_file: ~/kube.yaml
+ state: started
+ recreate: true
+ annotations:
+ greeting: hello
+ greet_to: world
+ userns: host
+ log_opt:
+ path: /tmp/my-container.log
+ max_size: 10mb
'''
import re # noqa: F402
try:
@@ -148,6 +198,9 @@ class PodmanKubeManagement:
self.command = [self.executable, 'play', 'kube']
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']:
@@ -160,11 +213,16 @@ class PodmanKubeManagement:
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',
@@ -264,15 +322,22 @@ 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', required=True),
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',
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_pod.py b/ansible_collections/containers/podman/plugins/modules/podman_pod.py
index ab475de99..7b57fd302 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_pod.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_pod.py
@@ -42,6 +42,18 @@ options:
type: list
elements: str
required: false
+ blkio_weight:
+ description:
+ - Block IO relative weight. The weight is a value between 10 and 1000.
+ - This option is not supported on cgroups V1 rootless systems.
+ type: str
+ required: false
+ blkio_weight_device:
+ description:
+ - Block IO relative device weight.
+ type: list
+ elements: str
+ required: false
cgroup_parent:
description:
- Path to cgroups under which the cgroup for the pod will be created. If the path
@@ -61,6 +73,16 @@ options:
Unlike `cpus` this is of type string and parsed as a list of numbers. Format is 0-3,0,1
required: false
type: str
+ cpuset_mems:
+ description:
+ - Memory nodes in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
+ required: false
+ type: str
+ cpu_shares:
+ description:
+ - CPU shares (relative weight).
+ required: false
+ type: str
device:
description:
- Add a host device to the pod. Optional permissions parameter can be used to specify
@@ -74,6 +96,12 @@ options:
elements: str
required: false
type: list
+ device_write_bps:
+ description:
+ - Limit write rate (in bytes per second) to a device.
+ type: list
+ elements: str
+ required: false
dns:
description:
- Set custom DNS servers in the /etc/resolv.conf file that will be shared between
@@ -123,11 +151,21 @@ options:
- 'on-watchdog'
- 'on-abort'
- 'always'
- time:
+ restart_sec:
+ description: Set the systemd service restartsec value.
+ type: int
+ required: false
+ start_timeout:
+ description: Override the default start timeout for the container with the given value.
+ type: int
+ required: false
+ stop_timeout:
description:
- - Override the default stop timeout for the container with the given value.
+ - Override the default stop timeout for the container with the given value. Called `time` before version 4.
type: int
required: false
+ aliases:
+ - time
no_header:
description:
- Do not generate the header including meta data such as the Podman version and the timestamp.
@@ -242,6 +280,18 @@ options:
- Set a static MAC address for the pod's shared network.
type: str
required: false
+ memory:
+ description:
+ - Set memory limit.
+ - A unit can be b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes).
+ type: str
+ required: false
+ memory_swap:
+ description:
+ - Set limit value equal to memory plus swap.
+ - A unit can be b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes).
+ type: str
+ required: false
name:
description:
- Assign a name to the pod.
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_prune.py b/ansible_collections/containers/podman/plugins/modules/podman_prune.py
index ee4c68a93..3fe3b7539 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_prune.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_prune.py
@@ -66,17 +66,17 @@ options:
type: dict
system:
description:
- - Wheter to prune unused pods, containers, image, networks and volume data
+ - Whether to prune unused pods, containers, image, networks and volume data
type: bool
default: false
system_all:
description:
- - Wheter to prune all unused images, not only dangling images.
+ - Whether to prune all unused images, not only dangling images.
type: bool
default: false
system_volumes:
description:
- - Wheter to prune volumes currently unused by any container.
+ - Whether to prune volumes currently unused by any container.
type: bool
default: false
volume:
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_runlabel.py b/ansible_collections/containers/podman/plugins/modules/podman_runlabel.py
new file mode 100644
index 000000000..e5b6cf32f
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/modules/podman_runlabel.py
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# Copyright (c) 2023, Pavel Dostal <@pdostal>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+module: podman_runlabel
+short_description: Run given label from given image
+author: Pavel Dostal (@pdostal)
+description:
+ - podman container runlabel runs selected label from given image
+options:
+ image:
+ description:
+ - Image to get the label from.
+ type: str
+ required: true
+ label:
+ description:
+ - Label to run.
+ type: str
+ required: true
+ executable:
+ description:
+ - Path to C(podman) executable if it is not in the C($PATH) on the
+ machine running C(podman)
+ default: 'podman'
+ type: str
+requirements:
+ - "Podman installed on host"
+'''
+
+RETURN = '''
+'''
+
+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'])
+ 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))
+ 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')
+ ),
+ supports_check_mode=False,
+ )
+
+ executable = module.get_bin_path(module.params['executable'], required=True)
+ changed, out, err = runlabel(module, executable)
+
+ results = {
+ "changed": changed,
+ "stdout": out,
+ "stderr": err
+ }
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_save.py b/ansible_collections/containers/podman/plugins/modules/podman_save.py
index bc7ce252c..e23f31021 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_save.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_save.py
@@ -20,7 +20,8 @@ options:
image:
description:
- Image to save.
- type: str
+ type: list
+ elements: str
required: true
compress:
description:
@@ -70,9 +71,14 @@ RETURN = '''
EXAMPLES = '''
# What modules does for example
- containers.podman.podman_save:
- dest: /path/to/tar/file
- compress: true
- format: oci-dir
+ image: nginx
+ dest: /tmp/file123.tar
+- containers.podman.podman_save:
+ image:
+ - nginx
+ - fedora
+ dest: /tmp/file456.tar
+ multi_image_archive: true
'''
import os # noqa: E402
@@ -92,7 +98,8 @@ def save(module, executable):
for param in module.params:
if module.params[param] is not None and param in cmd_args:
command += cmd_args[param]
- command.append(module.params['image'])
+ for img in module.params['image']:
+ command.append(img)
if module.params['force']:
dest = module.params['dest']
if os.path.exists(dest):
@@ -116,7 +123,7 @@ def save(module, executable):
def main():
module = AnsibleModule(
argument_spec=dict(
- image=dict(type='str', required=True),
+ 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']),
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_secret.py b/ansible_collections/containers/podman/plugins/modules/podman_secret.py
index fc8ec1f1d..a31aae9dc 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_secret.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_secret.py
@@ -61,6 +61,15 @@ options:
choices:
- absent
- present
+ labels:
+ description:
+ - Labels to set on the secret.
+ type: dict
+ debug:
+ description:
+ - Enable debug mode for module.
+ type: bool
+ default: False
'''
EXAMPLES = r"""
@@ -91,19 +100,75 @@ EXAMPLES = r"""
"""
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
+diff = {"before": '', "after": ''}
-def podman_secret_create(module, executable, name, data, force, skip,
- driver, driver_opts):
- if force:
- module.run_command([executable, 'secret', 'rm', name])
- if skip:
+
+def podman_secret_exists(module, executable, name, version):
+ if version is None or LooseVersion(version) < LooseVersion('4.5.0'):
rc, out, err = module.run_command(
[executable, 'secret', 'ls', "--format", "{{.Name}}"])
- if name in [i.strip() for i in out.splitlines()]:
- return {
- "changed": False,
- }
+ return name in [i.strip() for i in out.splitlines()]
+ rc, out, err = module.run_command(
+ [executable, 'secret', 'exists', name])
+ return rc == 0
+
+
+def need_update(module, executable, name, data, driver, driver_opts, debug, labels):
+
+ cmd = [executable, 'secret', 'inspect', '--showsecret', name]
+ rc, out, err = module.run_command(cmd)
+ if rc != 0:
+ if debug:
+ module.log("PODMAN-SECRET-DEBUG: Unable to get secret info: %s" % err)
+ return True
+ try:
+ secret = module.from_json(out)[0]
+ # We support only file driver for now
+ if (driver and driver != 'file') or secret['Spec']['Driver']['Name'] != 'file':
+ if debug:
+ module.log("PODMAN-SECRET-DEBUG: Idempotency of driver %s is not supported" % driver)
+ return True
+ if secret['SecretData'] != data:
+ diff['after'] = "<different-secret>"
+ diff['before'] = "<secret>"
+ 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)])
+ 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)])
+ return True
+ except Exception:
+ return True
+ return False
+
+
+def podman_secret_create(module, executable, name, data, 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')
+ and (driver is None or driver == 'file')):
+ if not skip and need_update(module, executable, name, data, driver, driver_opts, debug, labels):
+ podman_secret_remove(module, executable, name)
+ else:
+ return {"changed": False}
+ else:
+ if force:
+ podman_secret_remove(module, executable, name)
+ if skip and podman_secret_exists(module, executable, name, podman_version):
+ return {"changed": False}
cmd = [executable, 'secret', 'create']
if driver:
@@ -112,6 +177,10 @@ def podman_secret_create(module, executable, name, data, force, skip,
if 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("=".join([k, v]))
cmd.append(name)
cmd.append('-')
@@ -121,6 +190,10 @@ def podman_secret_create(module, executable, name, data, force, skip,
return {
"changed": True,
+ "diff": {
+ "before": diff['before'] + "\n",
+ "after": diff['after'] + "\n",
+ },
}
@@ -150,6 +223,8 @@ def main():
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),
),
)
@@ -165,9 +240,11 @@ def main():
skip = module.params['skip_existing']
driver = module.params['driver']
driver_opts = module.params['driver_opts']
+ debug = module.params['debug']
+ labels = module.params['labels']
results = podman_secret_create(module, executable,
name, data, force, skip,
- driver, driver_opts)
+ driver, driver_opts, debug, labels)
else:
results = podman_secret_remove(module, executable, name)
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_secret_info.py b/ansible_collections/containers/podman/plugins/modules/podman_secret_info.py
new file mode 100644
index 000000000..ebe854241
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/modules/podman_secret_info.py
@@ -0,0 +1,121 @@
+#!/usr/bin/python
+# Copyright (c) 2024 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+module: podman_secret_info
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+short_description: Gather info about podman secrets
+notes: []
+description:
+ - Gather info about podman secrets with podman inspect command.
+requirements:
+ - "Podman installed on host"
+options:
+ name:
+ description:
+ - Name of the secret
+ type: str
+ showsecret:
+ description:
+ - Show secret data value
+ type: bool
+ default: False
+ executable:
+ description:
+ - Path to C(podman) executable if it is not in the C($PATH) on the
+ machine running C(podman)
+ default: 'podman'
+ type: str
+'''
+
+EXAMPLES = r"""
+- name: Gather info about all present secrets
+ podman_secret_info:
+
+- name: Gather info about specific secret
+ podman_secret_info:
+ name: specific_secret
+"""
+
+RETURN = r"""
+secrets:
+ description: Facts from all or specified secrets
+ returned: always
+ type: list
+ sample: [
+ {
+ "ID": "06068c676e9a7f1c7dc0da8dd",
+ "CreatedAt": "2024-01-28T20:32:08.31857841+02:00",
+ "UpdatedAt": "2024-01-28T20:32:08.31857841+02:00",
+ "Spec": {
+ "Name": "secret_name",
+ "Driver": {
+ "Name": "file",
+ "Options": {
+ "path": "/home/user/.local/share/containers/storage/secrets/filedriver"
+ }
+ },
+ "Labels": {}
+ }
+ }
+ ]
+"""
+
+import json
+from ansible.module_utils.basic import AnsibleModule
+
+
+def get_secret_info(module, executable, show, name):
+ command = [executable, 'secret', 'inspect']
+ if show:
+ command.append('--showsecret')
+ if name:
+ command.append(name)
+ else:
+ 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 not out or json.loads(out) is None:
+ return [], out, err
+ return json.loads(out), out, err
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ 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)
+
+ inspect_results, out, err = get_secret_info(module, executable, showsecret, name)
+
+ results = {
+ "changed": False,
+ "secrets": inspect_results,
+ "stderr": err,
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_volume.py b/ansible_collections/containers/podman/plugins/modules/podman_volume.py
index c533091e1..b4d5062fa 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_volume.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_volume.py
@@ -327,7 +327,13 @@ class PodmanVolume:
# pylint: disable=unused-variable
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'volume', b'inspect', self.name])
- return json.loads(out)[0] if rc == 0 else {}
+ if rc == 0:
+ data = json.loads(out)
+ if data:
+ data = data[0]
+ if data.get("Name") == self.name:
+ return data
+ return {}
def _get_podman_version(self):
# pylint: disable=unused-variable