summaryrefslogtreecommitdiffstats
path: root/ansible_collections/containers/podman/plugins/module_utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/containers/podman/plugins/module_utils
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/containers/podman/plugins/module_utils')
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py0
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/common.py232
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py1696
-rw-r--r--ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py880
5 files changed, 2808 insertions, 0 deletions
diff --git a/ansible_collections/containers/podman/plugins/module_utils/__init__.py b/ansible_collections/containers/podman/plugins/module_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/module_utils/__init__.py
diff --git a/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py b/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py
diff --git a/ansible_collections/containers/podman/plugins/module_utils/podman/common.py b/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
new file mode 100644
index 000000000..dba3aff65
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
@@ -0,0 +1,232 @@
+# Copyright (c) 2020 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
+
+import json
+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)
+
+
+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:
+ command.extend(args)
+ 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))
+ 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"]:
+ 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('time'):
+ command.extend(['--time', str(sysconf['time'])])
+ 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:
+
+ 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']
+ 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']
+ 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']:
+ 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))
+
+ 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
+
+
+def generate_systemd(module, module_params, name, version):
+ empty = {}
+ 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
+ else:
+ try:
+ data = json.loads(systemd)
+ if sysconf.get('path'):
+ full_path = os.path.expanduser(sysconf['path'])
+ if not os.path.exists(full_path):
+ os.makedirs(full_path)
+ 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"
+ with open(os.path.join(full_path, file_name), 'w') as f:
+ f.write(file_content)
+ return data
+ except Exception as e:
+ module.log(
+ "PODMAN-CONTAINER-DEBUG: Error writing systemd: %s" % e)
+ return empty
+
+
+def delete_systemd(module, module_params, name, version):
+ 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!")
+ return
+ 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
+ else:
+ try:
+ data = json.loads(systemd)
+ for file_name in data.keys():
+ file_name += ".service"
+ 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)
+ return
+
+
+def lower_keys(x):
+ if isinstance(x, list):
+ return [lower_keys(v) for v in x]
+ elif isinstance(x, dict):
+ return dict((k.lower(), lower_keys(v)) for k, v in x.items())
+ else:
+ return x
+
+
+def remove_file_or_dir(path):
+ if os.path.isfile(path):
+ os.unlink(path)
+ elif os.path.isdir(path):
+ shutil.rmtree(path)
+ else:
+ raise ValueError("file %s is not a file or dir." % path)
+
+
+# Generated from https://github.com/containers/podman/blob/main/pkg/signal/signal_linux.go
+# and https://github.com/containers/podman/blob/main/pkg/signal/signal_linux_mipsx.go
+_signal_map = {
+ "ABRT": 6,
+ "ALRM": 14,
+ "BUS": 7,
+ "CHLD": 17,
+ "CLD": 17,
+ "CONT": 18,
+ "EMT": 7,
+ "FPE": 8,
+ "HUP": 1,
+ "ILL": 4,
+ "INT": 2,
+ "IO": 29,
+ "IOT": 6,
+ "KILL": 9,
+ "PIPE": 13,
+ "POLL": 29,
+ "PROF": 27,
+ "PWR": 30,
+ "QUIT": 3,
+ "RTMAX": 64,
+ "RTMIN": 34,
+ "SEGV": 11,
+ "STKFLT": 16,
+ "STOP": 19,
+ "SYS": 31,
+ "TERM": 15,
+ "TRAP": 5,
+ "TSTP": 20,
+ "TTIN": 21,
+ "TTOU": 22,
+ "URG": 23,
+ "USR1": 10,
+ "USR2": 12,
+ "VTALRM": 26,
+ "WINCH": 28,
+ "XCPU": 24,
+ "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
+
+
+def normalize_signal(signal_name_or_number):
+ signal_name_or_number = str(signal_name_or_number)
+ if signal_name_or_number.isdigit():
+ return signal_name_or_number
+ else:
+ signal_name = signal_name_or_number.upper()
+ 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))
+ return str(_signal_map[signal_name])
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
new file mode 100644
index 000000000..1ba28f4c8
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py
@@ -0,0 +1,1696 @@
+from __future__ import (absolute_import, division, print_function)
+import json # noqa: F402
+import os # noqa: F402
+import shlex # 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.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
+
+__metaclass__ = type
+
+ARGUMENTS_SPEC_CONTAINER = dict(
+ name=dict(required=True, type='str'),
+ executable=dict(default='podman', type='str'),
+ state=dict(type='str', default='started', choices=[
+ 'absent', 'present', 'stopped', 'started', 'created']),
+ image=dict(type='str'),
+ annotation=dict(type='dict'),
+ authfile=dict(type='path'),
+ blkio_weight=dict(type='int'),
+ blkio_weight_device=dict(type='dict'),
+ cap_add=dict(type='list', elements='str', aliases=['capabilities']),
+ cap_drop=dict(type='list', elements='str'),
+ cgroup_parent=dict(type='path'),
+ cgroupns=dict(type='str'),
+ cgroups=dict(type='str'),
+ cidfile=dict(type='path'),
+ cmd_args=dict(type='list', elements='str'),
+ conmon_pidfile=dict(type='path'),
+ command=dict(type='raw'),
+ cpu_period=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'),
+ detach=dict(type='bool', default=True),
+ debug=dict(type='bool', default=False),
+ detach_keys=dict(type='str', no_log=False),
+ device=dict(type='list', elements='str'),
+ device_read_bps=dict(type='list', elements='str'),
+ device_read_iops=dict(type='list', elements='str'),
+ device_write_bps=dict(type='list', elements='str'),
+ device_write_iops=dict(type='list', elements='str'),
+ dns=dict(type='list', elements='str', aliases=['dns_servers']),
+ dns_option=dict(type='str', aliases=['dns_opts']),
+ dns_search=dict(type='str', aliases=['dns_search_domains']),
+ entrypoint=dict(type='str'),
+ env=dict(type='dict'),
+ env_file=dict(type='path'),
+ 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']),
+ generate_systemd=dict(type='dict', default={}),
+ gidmap=dict(type='list', elements='str'),
+ group_add=dict(type='list', elements='str', aliases=['groups']),
+ healthcheck=dict(type='str'),
+ healthcheck_interval=dict(type='str'),
+ healthcheck_retries=dict(type='int'),
+ healthcheck_start_period=dict(type='str'),
+ healthcheck_timeout=dict(type='str'),
+ hooks_dir=dict(type='list', elements='str'),
+ hostname=dict(type='str'),
+ http_proxy=dict(type='bool'),
+ image_volume=dict(type='str', choices=['bind', 'tmpfs', 'ignore']),
+ image_strict=dict(type='bool', default=False),
+ init=dict(type='bool'),
+ init_path=dict(type='str'),
+ interactive=dict(type='bool'),
+ ip=dict(type='str'),
+ ipc=dict(type='str', aliases=['ipc_mode']),
+ kernel_memory=dict(type='str'),
+ label=dict(type='dict', aliases=['labels']),
+ label_file=dict(type='str'),
+ log_driver=dict(type='str', choices=[
+ 'k8s-file', 'journald', 'json-file']),
+ log_level=dict(
+ type='str',
+ choices=["debug", "info", "warn", "error", "fatal", "panic"]),
+ log_opt=dict(type='dict', aliases=['log_options'],
+ options=dict(
+ max_size=dict(type='str'),
+ path=dict(type='str'),
+ tag=dict(type='str'))),
+ mac_address=dict(type='str'),
+ memory=dict(type='str'),
+ memory_reservation=dict(type='str'),
+ memory_swap=dict(type='str'),
+ memory_swappiness=dict(type='int'),
+ mount=dict(type='list', elements='str', aliases=['mounts']),
+ network=dict(type='list', elements='str', aliases=['net', 'network_mode']),
+ network_aliases=dict(type='list', elements='str'),
+ no_hosts=dict(type='bool'),
+ oom_kill_disable=dict(type='bool'),
+ oom_score_adj=dict(type='int'),
+ pid=dict(type='str', aliases=['pid_mode']),
+ pids_limit=dict(type='str'),
+ pod=dict(type='str'),
+ privileged=dict(type='bool'),
+ publish=dict(type='list', elements='str', aliases=[
+ 'ports', 'published', 'published_ports']),
+ publish_all=dict(type='bool'),
+ read_only=dict(type='bool'),
+ read_only_tmpfs=dict(type='bool'),
+ recreate=dict(type='bool', default=False),
+ requires=dict(type='list', elements='str'),
+ restart_policy=dict(type='str'),
+ rm=dict(type='bool', aliases=['remove', 'auto_remove']),
+ rootfs=dict(type='bool'),
+ secrets=dict(type='list', elements='str', no_log=True),
+ sdnotify=dict(type='str'),
+ security_opt=dict(type='list', elements='str'),
+ shm_size=dict(type='str'),
+ sig_proxy=dict(type='bool'),
+ stop_signal=dict(type='int'),
+ stop_timeout=dict(type='int'),
+ subgidname=dict(type='str'),
+ subuidname=dict(type='str'),
+ sysctl=dict(type='dict'),
+ systemd=dict(type='str'),
+ timezone=dict(type='str'),
+ tmpfs=dict(type='dict'),
+ tty=dict(type='bool'),
+ uidmap=dict(type='list', elements='str'),
+ ulimit=dict(type='list', elements='str', aliases=['ulimits']),
+ user=dict(type='str'),
+ userns=dict(type='str', aliases=['userns_mode']),
+ uts=dict(type='str'),
+ volume=dict(type='list', elements='str', aliases=['volumes']),
+ volumes_from=dict(type='list', elements='str'),
+ workdir=dict(type='str', aliases=['working_dir'])
+)
+
+
+def init_options():
+ default = {}
+ opts = ARGUMENTS_SPEC_CONTAINER
+ for k, v in opts.items():
+ if 'default' in v:
+ default[k] = v['default']
+ else:
+ default[k] = None
+ return default
+
+
+def update_options(opts_dict, container):
+ def to_bool(x):
+ return str(x).lower() not in ['no', 'false']
+
+ aliases = {}
+ for k, v in ARGUMENTS_SPEC_CONTAINER.items():
+ if 'aliases' in v:
+ for alias in v['aliases']:
+ aliases[alias] = k
+ for k in list(container):
+ if k in aliases:
+ key = aliases[k]
+ container[key] = container.pop(k)
+ else:
+ key = k
+ if ARGUMENTS_SPEC_CONTAINER[key]['type'] == 'list' and not isinstance(container[key], list):
+ opts_dict[key] = [container[key]]
+ elif ARGUMENTS_SPEC_CONTAINER[key]['type'] == 'bool' and not isinstance(container[key], bool):
+ opts_dict[key] = to_bool(container[key])
+ elif ARGUMENTS_SPEC_CONTAINER[key]['type'] == 'int' and not isinstance(container[key], int):
+ opts_dict[key] = int(container[key])
+ else:
+ opts_dict[key] = container[key]
+
+ return opts_dict
+
+
+def set_container_opts(input_vars):
+ default_options_templ = init_options()
+ options_dict = update_options(default_options_templ, input_vars)
+ return options_dict
+
+
+class PodmanModuleParams:
+ """Creates list of arguments for podman CLI command.
+
+ Arguments:
+ action {str} -- action type from 'run', 'stop', 'create', 'delete',
+ 'start', 'restart'
+ params {dict} -- dictionary of module parameters
+
+ """
+
+ def __init__(self, action, params, podman_version, module):
+ self.params = params
+ self.action = action
+ self.podman_version = podman_version
+ self.module = module
+
+ def construct_command_from_params(self):
+ """Create a podman command from given module parameters.
+
+ Returns:
+ list -- list of byte strings for Popen command
+ """
+ if self.action in ['start', 'stop', 'delete', 'restart']:
+ return self.start_stop_delete()
+ if self.action in ['create', 'run']:
+ cmd = [self.action, '--name', 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)
+ cmd.append(self.params['image'])
+ if self.params['command']:
+ if isinstance(self.params['command'], list):
+ cmd += self.params['command']
+ else:
+ cmd += self.params['command'].split()
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ def start_stop_delete(self):
+
+ if self.action in ['stop', 'start', 'restart']:
+ cmd = [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']]
+ 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))
+
+ def addparam_annotation(self, c):
+ for annotate in self.params['annotation'].items():
+ c += ['--annotation', '='.join(annotate)]
+ return c
+
+ def addparam_authfile(self, c):
+ return c + ['--authfile', self.params['authfile']]
+
+ def addparam_blkio_weight(self, c):
+ return c + ['--blkio-weight', self.params['blkio_weight']]
+
+ def addparam_blkio_weight_device(self, c):
+ for blkio in self.params['blkio_weight_device'].items():
+ c += ['--blkio-weight-device', ':'.join(blkio)]
+ return c
+
+ def addparam_cap_add(self, c):
+ for cap_add in self.params['cap_add']:
+ c += ['--cap-add', cap_add]
+ return c
+
+ def addparam_cap_drop(self, c):
+ for cap_drop in self.params['cap_drop']:
+ c += ['--cap-drop', cap_drop]
+ return c
+
+ def addparam_cgroups(self, c):
+ self.check_version('--cgroups', minv='1.6.0')
+ return c + ['--cgroups=%s' % self.params['cgroups']]
+
+ def addparam_cgroupns(self, c):
+ self.check_version('--cgroupns', minv='1.6.2')
+ return c + ['--cgroupns=%s' % self.params['cgroupns']]
+
+ def addparam_cgroup_parent(self, c):
+ return c + ['--cgroup-parent', self.params['cgroup_parent']]
+
+ def addparam_cidfile(self, c):
+ return c + ['--cidfile', self.params['cidfile']]
+
+ def addparam_conmon_pidfile(self, c):
+ return c + ['--conmon-pidfile', self.params['conmon_pidfile']]
+
+ def addparam_cpu_period(self, c):
+ return c + ['--cpu-period', self.params['cpu_period']]
+
+ def addparam_cpu_rt_period(self, c):
+ return c + ['--cpu-rt-period', self.params['cpu_rt_period']]
+
+ def addparam_cpu_rt_runtime(self, c):
+ return c + ['--cpu-rt-runtime', self.params['cpu_rt_runtime']]
+
+ def addparam_cpu_shares(self, c):
+ return c + ['--cpu-shares', self.params['cpu_shares']]
+
+ def addparam_cpus(self, c):
+ return c + ['--cpus', self.params['cpus']]
+
+ def addparam_cpuset_cpus(self, c):
+ return c + ['--cpuset-cpus', self.params['cpuset_cpus']]
+
+ def addparam_cpuset_mems(self, c):
+ return c + ['--cpuset-mems', self.params['cpuset_mems']]
+
+ def addparam_detach(self, c):
+ return c + ['--detach=%s' % self.params['detach']]
+
+ def addparam_detach_keys(self, c):
+ return c + ['--detach-keys', self.params['detach_keys']]
+
+ def addparam_device(self, c):
+ for dev in self.params['device']:
+ c += ['--device', dev]
+ return c
+
+ def addparam_device_read_bps(self, c):
+ for dev in self.params['device_read_bps']:
+ c += ['--device-read-bps', dev]
+ return c
+
+ def addparam_device_read_iops(self, c):
+ for dev in self.params['device_read_iops']:
+ c += ['--device-read-iops', dev]
+ return c
+
+ def addparam_device_write_bps(self, c):
+ for dev in self.params['device_write_bps']:
+ c += ['--device-write-bps', dev]
+ return c
+
+ def addparam_device_write_iops(self, c):
+ for dev in self.params['device_write_iops']:
+ c += ['--device-write-iops', dev]
+ return c
+
+ def addparam_dns(self, c):
+ return c + ['--dns', ','.join(self.params['dns'])]
+
+ def addparam_dns_option(self, c):
+ return c + ['--dns-option', self.params['dns_option']]
+
+ def addparam_dns_search(self, c):
+ return c + ['--dns-search', self.params['dns_search']]
+
+ def addparam_entrypoint(self, c):
+ return c + ['--entrypoint', self.params['entrypoint']]
+
+ def addparam_env(self, c):
+ for env_value in self.params['env'].items():
+ c += ['--env',
+ b"=".join([to_bytes(k, errors='surrogate_or_strict')
+ for k in env_value])]
+ return c
+
+ def addparam_env_file(self, c):
+ return c + ['--env-file', self.params['env_file']]
+
+ def addparam_env_host(self, c):
+ self.check_version('--env-host', minv='1.5.0')
+ return c + ['--env-host=%s' % self.params['env_host']]
+
+ def addparam_etc_hosts(self, c):
+ for host_ip in self.params['etc_hosts'].items():
+ c += ['--add-host', ':'.join(host_ip)]
+ return c
+
+ def addparam_expose(self, c):
+ for exp in self.params['expose']:
+ c += ['--expose', exp]
+ return c
+
+ def addparam_gidmap(self, c):
+ for gidmap in self.params['gidmap']:
+ c += ['--gidmap', gidmap]
+ return c
+
+ def addparam_group_add(self, c):
+ for g in self.params['group_add']:
+ c += ['--group-add', g]
+ return c
+
+ def addparam_healthcheck(self, c):
+ return c + ['--healthcheck-command', self.params['healthcheck']]
+
+ def addparam_healthcheck_interval(self, c):
+ return c + ['--healthcheck-interval',
+ self.params['healthcheck_interval']]
+
+ def addparam_healthcheck_retries(self, c):
+ return c + ['--healthcheck-retries',
+ self.params['healthcheck_retries']]
+
+ def addparam_healthcheck_start_period(self, c):
+ return c + ['--healthcheck-start-period',
+ self.params['healthcheck_start_period']]
+
+ def addparam_healthcheck_timeout(self, c):
+ return c + ['--healthcheck-timeout',
+ self.params['healthcheck_timeout']]
+
+ def addparam_hooks_dir(self, c):
+ for hook_dir in self.params['hooks_dir']:
+ c += ['--hooks-dir=%s' % hook_dir]
+ return c
+
+ def addparam_hostname(self, c):
+ return c + ['--hostname', self.params['hostname']]
+
+ def addparam_http_proxy(self, c):
+ return c + ['--http-proxy=%s' % self.params['http_proxy']]
+
+ def addparam_image_volume(self, c):
+ return c + ['--image-volume', self.params['image_volume']]
+
+ def addparam_init(self, c):
+ if self.params['init']:
+ c += ['--init']
+ return c
+
+ def addparam_init_path(self, c):
+ return c + ['--init-path', self.params['init_path']]
+
+ def addparam_interactive(self, c):
+ return c + ['--interactive=%s' % self.params['interactive']]
+
+ def addparam_ip(self, c):
+ return c + ['--ip', self.params['ip']]
+
+ def addparam_ipc(self, c):
+ return c + ['--ipc', self.params['ipc']]
+
+ def addparam_kernel_memory(self, c):
+ return c + ['--kernel-memory', self.params['kernel_memory']]
+
+ def addparam_label(self, c):
+ for label in self.params['label'].items():
+ c += ['--label', b'='.join([to_bytes(la, errors='surrogate_or_strict')
+ for la in label])]
+ return c
+
+ def addparam_label_file(self, c):
+ return c + ['--label-file', self.params['label_file']]
+
+ def addparam_log_driver(self, c):
+ return c + ['--log-driver', self.params['log_driver']]
+
+ def addparam_log_opt(self, c):
+ for k, v in self.params['log_opt'].items():
+ if v is not None:
+ c += ['--log-opt',
+ b"=".join([to_bytes(k.replace('max_size', 'max-size'),
+ errors='surrogate_or_strict'),
+ to_bytes(v,
+ errors='surrogate_or_strict')])]
+ return c
+
+ def addparam_log_level(self, c):
+ return c + ['--log-level', self.params['log_level']]
+
+ def addparam_mac_address(self, c):
+ return c + ['--mac-address', self.params['mac_address']]
+
+ def addparam_memory(self, c):
+ return c + ['--memory', self.params['memory']]
+
+ def addparam_memory_reservation(self, c):
+ return c + ['--memory-reservation', self.params['memory_reservation']]
+
+ def addparam_memory_swap(self, c):
+ return c + ['--memory-swap', self.params['memory_swap']]
+
+ def addparam_memory_swappiness(self, c):
+ return c + ['--memory-swappiness', self.params['memory_swappiness']]
+
+ def addparam_mount(self, c):
+ for mnt in self.params['mount']:
+ if mnt:
+ c += ['--mount', mnt]
+ return c
+
+ def addparam_network(self, c):
+ if LooseVersion(self.podman_version) >= LooseVersion('4.0.0'):
+ for net in self.params['network']:
+ c += ['--network', net]
+ return c
+ return c + ['--network', ",".join(self.params['network'])]
+
+ def addparam_network_aliases(self, c):
+ for alias in self.params['network_aliases']:
+ c += ['--network-alias', alias]
+ return c
+
+ def addparam_no_hosts(self, c):
+ return c + ['--no-hosts=%s' % self.params['no_hosts']]
+
+ def addparam_oom_kill_disable(self, c):
+ return c + ['--oom-kill-disable=%s' % self.params['oom_kill_disable']]
+
+ def addparam_oom_score_adj(self, c):
+ return c + ['--oom-score-adj', self.params['oom_score_adj']]
+
+ def addparam_pid(self, c):
+ return c + ['--pid', self.params['pid']]
+
+ def addparam_pids_limit(self, c):
+ return c + ['--pids-limit', self.params['pids_limit']]
+
+ def addparam_pod(self, c):
+ return c + ['--pod', self.params['pod']]
+
+ def addparam_privileged(self, c):
+ return c + ['--privileged=%s' % self.params['privileged']]
+
+ def addparam_publish(self, c):
+ for pub in self.params['publish']:
+ c += ['--publish', pub]
+ return c
+
+ def addparam_publish_all(self, c):
+ return c + ['--publish-all=%s' % self.params['publish_all']]
+
+ def addparam_read_only(self, c):
+ return c + ['--read-only=%s' % self.params['read_only']]
+
+ def addparam_read_only_tmpfs(self, c):
+ return c + ['--read-only-tmpfs=%s' % self.params['read_only_tmpfs']]
+
+ def addparam_requires(self, c):
+ return c + ['--requires', ",".join(self.params['requires'])]
+
+ def addparam_restart_policy(self, c):
+ return c + ['--restart=%s' % self.params['restart_policy']]
+
+ def addparam_rm(self, c):
+ if self.params['rm']:
+ c += ['--rm']
+ return c
+
+ def addparam_rootfs(self, c):
+ return c + ['--rootfs=%s' % self.params['rootfs']]
+
+ def addparam_sdnotify(self, c):
+ return c + ['--sdnotify=%s' % self.params['sdnotify']]
+
+ def addparam_secrets(self, c):
+ for secret in self.params['secrets']:
+ c += ['--secret', secret]
+ return c
+
+ def addparam_security_opt(self, c):
+ for secopt in self.params['security_opt']:
+ c += ['--security-opt', secopt]
+ return c
+
+ def addparam_shm_size(self, c):
+ return c + ['--shm-size', self.params['shm_size']]
+
+ def addparam_sig_proxy(self, c):
+ return c + ['--sig-proxy=%s' % self.params['sig_proxy']]
+
+ def addparam_stop_signal(self, c):
+ return c + ['--stop-signal', self.params['stop_signal']]
+
+ def addparam_stop_timeout(self, c):
+ return c + ['--stop-timeout', self.params['stop_timeout']]
+
+ def addparam_subgidname(self, c):
+ return c + ['--subgidname', self.params['subgidname']]
+
+ def addparam_subuidname(self, c):
+ return c + ['--subuidname', self.params['subuidname']]
+
+ def addparam_sysctl(self, c):
+ for sysctl in self.params['sysctl'].items():
+ c += ['--sysctl',
+ b"=".join([to_bytes(k, errors='surrogate_or_strict')
+ for k in sysctl])]
+ return c
+
+ def addparam_systemd(self, c):
+ return c + ['--systemd=%s' % str(self.params['systemd']).lower()]
+
+ def addparam_tmpfs(self, c):
+ for tmpfs in self.params['tmpfs'].items():
+ c += ['--tmpfs', ':'.join(tmpfs)]
+ return c
+
+ def addparam_timezone(self, c):
+ return c + ['--tz=%s' % self.params['timezone']]
+
+ def addparam_tty(self, c):
+ return c + ['--tty=%s' % self.params['tty']]
+
+ def addparam_uidmap(self, c):
+ for uidmap in self.params['uidmap']:
+ c += ['--uidmap', uidmap]
+ return c
+
+ def addparam_ulimit(self, c):
+ for u in self.params['ulimit']:
+ c += ['--ulimit', u]
+ return c
+
+ def addparam_user(self, c):
+ return c + ['--user', self.params['user']]
+
+ def addparam_userns(self, c):
+ return c + ['--userns', self.params['userns']]
+
+ def addparam_uts(self, c):
+ return c + ['--uts', self.params['uts']]
+
+ def addparam_volume(self, c):
+ for vol in self.params['volume']:
+ if vol:
+ c += ['--volume', vol]
+ return c
+
+ def addparam_volumes_from(self, c):
+ for vol in self.params['volumes_from']:
+ c += ['--volumes-from', vol]
+ return c
+
+ def addparam_workdir(self, c):
+ return c + ['--workdir', self.params['workdir']]
+
+ # Add your own args for podman command
+ def addparam_cmd_args(self, c):
+ return c + self.params['cmd_args']
+
+
+class PodmanDefaults:
+ def __init__(self, image_info, podman_version):
+ self.version = podman_version
+ self.image_info = image_info
+ self.defaults = {
+ "blkio_weight": 0,
+ "cgroups": "default",
+ "cidfile": "",
+ "cpus": 0.0,
+ "cpu_shares": 0,
+ "cpu_quota": 0,
+ "cpu_period": 0,
+ "cpu_rt_runtime": 0,
+ "cpu_rt_period": 0,
+ "cpuset_cpus": "",
+ "cpuset_mems": "",
+ "detach": True,
+ "device": [],
+ "env_host": False,
+ "etc_hosts": {},
+ "group_add": [],
+ "ipc": "",
+ "kernelmemory": "0",
+ "log_level": "error",
+ "memory": "0",
+ "memory_swap": "0",
+ "memory_reservation": "0",
+ # "memory_swappiness": -1,
+ "no_hosts": False,
+ # libpod issue with networks in inspection
+ "oom_score_adj": 0,
+ "pid": "",
+ "privileged": False,
+ "read_only": False,
+ "rm": False,
+ "security_opt": [],
+ "stop_signal": self.image_info.get('config', {}).get('stopsignal', "15"),
+ "tty": False,
+ "user": self.image_info.get('user', ''),
+ "workdir": self.image_info.get('config', {}).get('workingdir', '/'),
+ "uts": "",
+ }
+
+ def default_dict(self):
+ # make here any changes to self.defaults related to podman version
+ # https://github.com/containers/libpod/pull/5669
+ if (LooseVersion(self.version) >= LooseVersion('1.8.0')
+ and LooseVersion(self.version) < LooseVersion('1.9.0')):
+ self.defaults['cpu_shares'] = 1024
+ if (LooseVersion(self.version) >= LooseVersion('2.0.0')):
+ self.defaults['network'] = ["slirp4netns"]
+ self.defaults['ipc'] = "private"
+ self.defaults['uts'] = "private"
+ self.defaults['pid'] = "private"
+ if (LooseVersion(self.version) >= LooseVersion('3.0.0')):
+ self.defaults['log_level'] = "warning"
+ if (LooseVersion(self.version) >= LooseVersion('4.1.0')):
+ self.defaults['ipc'] = "shareable"
+ return self.defaults
+
+
+class PodmanContainerDiff:
+ def __init__(self, module, module_params, info, image_info, podman_version):
+ self.module = module
+ self.module_params = module_params
+ self.version = podman_version
+ self.default_dict = None
+ self.info = lower_keys(info)
+ self.image_info = lower_keys(image_info)
+ self.params = self.defaultize()
+ self.diff = {'before': {}, 'after': {}}
+ self.non_idempotent = {}
+
+ def defaultize(self):
+ params_with_defaults = {}
+ self.default_dict = PodmanDefaults(
+ self.image_info, 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]
+ else:
+ params_with_defaults[p] = self.module_params[p]
+ return params_with_defaults
+
+ 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})
+ return True
+ return False
+
+ def diffparam_annotation(self):
+ before = self.info['config']['annotations'] or {}
+ after = before.copy()
+ if self.module_params['annotation'] is not None:
+ after.update(self.params['annotation'])
+ 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
+ before = False
+ after = self.params['env_host']
+ return self._diff_update_and_compare('env_host', before, after)
+
+ def diffparam_blkio_weight(self):
+ before = self.info['hostconfig']['blkioweight']
+ after = self.params['blkio_weight']
+ return self._diff_update_and_compare('blkio_weight', before, after)
+
+ def diffparam_blkio_weight_device(self):
+ before = self.info['hostconfig']['blkioweightdevice']
+ if before == [] and self.module_params['blkio_weight_device'] is None:
+ after = []
+ else:
+ after = self.params['blkio_weight_device']
+ return self._diff_update_and_compare('blkio_weight_device', before, after)
+
+ def diffparam_cap_add(self):
+ before = self.info['effectivecaps'] or []
+ before = [i.lower() for i in before]
+ after = []
+ if self.module_params['cap_add'] is not None:
+ for cap in self.module_params['cap_add']:
+ cap = cap.lower()
+ cap = cap if cap.startswith('cap_') else 'cap_' + cap
+ after.append(cap)
+ after += before
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('cap_add', before, after)
+
+ def diffparam_cap_drop(self):
+ before = self.info['effectivecaps'] or []
+ before = [i.lower() for i in before]
+ after = before[:]
+ if self.module_params['cap_drop'] is not None:
+ for cap in self.module_params['cap_drop']:
+ cap = cap.lower()
+ cap = cap if cap.startswith('cap_') else 'cap_' + cap
+ if cap in after:
+ after.remove(cap)
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('cap_drop', before, after)
+
+ def diffparam_cgroup_parent(self):
+ before = self.info['hostconfig']['cgroupparent']
+ after = self.params['cgroup_parent']
+ if after is None:
+ after = before
+ return self._diff_update_and_compare('cgroup_parent', before, after)
+
+ def diffparam_cgroups(self):
+ # Cgroups output is not supported in all versions
+ if 'cgroups' in self.info['hostconfig']:
+ before = self.info['hostconfig']['cgroups']
+ after = self.params['cgroups']
+ return self._diff_update_and_compare('cgroups', before, after)
+ return False
+
+ def diffparam_cidfile(self):
+ before = self.info['hostconfig']['containeridfile']
+ after = self.params['cidfile']
+ labels = self.info['config']['labels'] or {}
+ # Ignore cidfile that is coming from systemd files
+ # https://github.com/containers/ansible-podman-collections/issues/276
+ if 'podman_systemd_unit' in labels:
+ after = before
+ return self._diff_update_and_compare('cidfile', before, after)
+
+ def diffparam_command(self):
+ # TODO(sshnaidm): to inspect image to get the default command
+ if self.module_params['command'] is not None:
+ before = self.info['config']['cmd']
+ after = self.params['command']
+ if isinstance(after, str):
+ after = shlex.split(after)
+ return self._diff_update_and_compare('command', before, after)
+ return False
+
+ def diffparam_conmon_pidfile(self):
+ before = self.info['conmonpidfile']
+ if self.module_params['conmon_pidfile'] is None:
+ after = before
+ else:
+ after = self.params['conmon_pidfile']
+ return self._diff_update_and_compare('conmon_pidfile', before, after)
+
+ def diffparam_cpu_period(self):
+ before = self.info['hostconfig']['cpuperiod']
+ after = self.params['cpu_period']
+ return self._diff_update_and_compare('cpu_period', before, after)
+
+ def diffparam_cpu_rt_period(self):
+ before = self.info['hostconfig']['cpurealtimeperiod']
+ after = self.params['cpu_rt_period']
+ return self._diff_update_and_compare('cpu_rt_period', before, after)
+
+ def diffparam_cpu_rt_runtime(self):
+ before = self.info['hostconfig']['cpurealtimeruntime']
+ after = self.params['cpu_rt_runtime']
+ return self._diff_update_and_compare('cpu_rt_runtime', before, after)
+
+ def diffparam_cpu_shares(self):
+ before = self.info['hostconfig']['cpushares']
+ after = self.params['cpu_shares']
+ 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']
+ return self._diff_update_and_compare('cpus', before, after)
+
+ def diffparam_cpuset_cpus(self):
+ before = self.info['hostconfig']['cpusetcpus']
+ after = self.params['cpuset_cpus']
+ return self._diff_update_and_compare('cpuset_cpus', before, after)
+
+ def diffparam_cpuset_mems(self):
+ before = self.info['hostconfig']['cpusetmems']
+ after = self.params['cpuset_mems']
+ return self._diff_update_and_compare('cpuset_mems', before, after)
+
+ def diffparam_device(self):
+ 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 = [":".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 = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('devices', before, after)
+
+ def diffparam_device_read_bps(self):
+ before = self.info['hostconfig']['blkiodevicereadbps'] or []
+ before = ["%s:%s" % (i['path'], i['rate']) for i in before]
+ after = self.params['device_read_bps'] or []
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('device_read_bps', before, after)
+
+ def diffparam_device_read_iops(self):
+ before = self.info['hostconfig']['blkiodevicereadiops'] or []
+ before = ["%s:%s" % (i['path'], i['rate']) for i in before]
+ after = self.params['device_read_iops'] or []
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('device_read_iops', before, after)
+
+ def diffparam_device_write_bps(self):
+ before = self.info['hostconfig']['blkiodevicewritebps'] or []
+ before = ["%s:%s" % (i['path'], i['rate']) for i in before]
+ after = self.params['device_write_bps'] or []
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('device_write_bps', before, after)
+
+ def diffparam_device_write_iops(self):
+ before = self.info['hostconfig']['blkiodevicewriteiops'] or []
+ before = ["%s:%s" % (i['path'], i['rate']) for i in before]
+ after = self.params['device_write_iops'] or []
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('device_write_iops', before, after)
+
+ # Limited idempotency, it can't guess default values
+ def diffparam_env(self):
+ env_before = self.info['config']['env'] or {}
+ before = {i.split("=")[0]: "=".join(i.split("=")[1:])
+ for i in env_before}
+ after = before.copy()
+ if self.params['env']:
+ after.update({k: str(v) for k, v in self.params['env'].items()})
+ return self._diff_update_and_compare('env', before, after)
+
+ def diffparam_etc_hosts(self):
+ if self.info['hostconfig']['extrahosts']:
+ before = dict([i.split(":", 1)
+ for i in self.info['hostconfig']['extrahosts']])
+ else:
+ before = {}
+ after = self.params['etc_hosts']
+ return self._diff_update_and_compare('etc_hosts', before, after)
+
+ def diffparam_group_add(self):
+ before = self.info['hostconfig']['groupadd']
+ after = self.params['group_add']
+ return self._diff_update_and_compare('group_add', before, after)
+
+ # 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):
+ 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 = ''
+ after = self.params['healthcheck'] or before
+ return self._diff_update_and_compare('healthcheck', before, after)
+
+ # Because of hostname is random generated, this parameter has partial idempotency only.
+ def diffparam_hostname(self):
+ before = self.info['config']['hostname']
+ after = self.params['hostname'] or before
+ return self._diff_update_and_compare('hostname', before, after)
+
+ def diffparam_image(self):
+ before_id = self.info['image'] or self.info['rootfs']
+ after_id = self.image_info['id']
+ if before_id == after_id:
+ return self._diff_update_and_compare('image', before_id, after_id)
+ is_rootfs = self.info['rootfs'] != '' or self.params['rootfs']
+ before = self.info['config']['image'] or before_id
+ after = self.params['image']
+ mode = self.params['image_strict'] or is_rootfs
+ if mode is None or not mode:
+ # In a idempotency 'lite mode' assume all images from different registries are the same
+ before = before.replace(":latest", "")
+ after = after.replace(":latest", "")
+ before = before.split("/")[-1]
+ after = after.split("/")[-1]
+ else:
+ return self._diff_update_and_compare('image', before_id, after_id)
+ return self._diff_update_and_compare('image', before, after)
+
+ def diffparam_ipc(self):
+ before = self.info['hostconfig']['ipcmode']
+ after = self.params['ipc']
+ if self.params['pod'] and not self.module_params['ipc']:
+ after = before
+ return self._diff_update_and_compare('ipc', before, after)
+
+ def diffparam_label(self):
+ before = self.info['config']['labels'] or {}
+ after = self.image_info.get('labels') or {}
+ if self.params['label']:
+ after.update({
+ str(k).lower(): str(v)
+ for k, v in self.params['label'].items()
+ })
+ # Strip out labels that are coming from systemd files
+ # https://github.com/containers/ansible-podman-collections/issues/276
+ if 'podman_systemd_unit' in before:
+ after.pop('podman_systemd_unit', None)
+ before.pop('podman_systemd_unit', None)
+ return self._diff_update_and_compare('label', before, after)
+
+ def diffparam_log_driver(self):
+ before = self.info['hostconfig']['logconfig']['type']
+ if self.module_params['log_driver'] is not None:
+ after = self.params['log_driver']
+ else:
+ after = before
+ return self._diff_update_and_compare('log_driver', before, after)
+
+ # Parameter has limited idempotency, unable to guess the default log_path
+ def diffparam_log_opt(self):
+ before, after = {}, {}
+
+ # Log path
+ path_before = None
+ if 'logpath' in self.info:
+ path_before = self.info['logpath']
+ # For Podman v3
+ if ('logconfig' in self.info['hostconfig'] and
+ 'path' in self.info['hostconfig']['logconfig']):
+ path_before = self.info['hostconfig']['logconfig']['path']
+ if path_before is not None:
+ if (self.module_params['log_opt'] and
+ 'path' in self.module_params['log_opt'] and
+ self.module_params['log_opt']['path'] is not None):
+ path_after = self.params['log_opt']['path']
+ else:
+ path_after = path_before
+ if path_before != path_after:
+ before.update({'log-path': path_before})
+ after.update({'log-path': path_after})
+
+ # Log tag
+ tag_before = None
+ if 'logtag' in self.info:
+ tag_before = self.info['logtag']
+ # For Podman v3
+ if ('logconfig' in self.info['hostconfig'] and
+ 'tag' in self.info['hostconfig']['logconfig']):
+ tag_before = self.info['hostconfig']['logconfig']['tag']
+ if tag_before is not None:
+ if (self.module_params['log_opt'] and
+ 'tag' in self.module_params['log_opt'] and
+ self.module_params['log_opt']['tag'] is not None):
+ tag_after = self.params['log_opt']['tag']
+ else:
+ tag_after = ''
+ if tag_before != tag_after:
+ before.update({'log-tag': tag_before})
+ after.update({'log-tag': tag_after})
+
+ # Log size
+ # For Podman v3
+ # size_before = '0B'
+ # TODO(sshnaidm): integrate B/KB/MB/GB calculation for sizes
+ # if ('logconfig' in self.info['hostconfig'] and
+ # 'size' in self.info['hostconfig']['logconfig']):
+ # size_before = self.info['hostconfig']['logconfig']['size']
+ # if size_before != '0B':
+ # if (self.module_params['log_opt'] and
+ # 'max_size' in self.module_params['log_opt'] and
+ # self.module_params['log_opt']['max_size'] is not None):
+ # size_after = self.params['log_opt']['max_size']
+ # else:
+ # size_after = ''
+ # if size_before != size_after:
+ # before.update({'log-size': size_before})
+ # after.update({'log-size': size_after})
+
+ return self._diff_update_and_compare('log_opt', before, after)
+
+ def diffparam_mac_address(self):
+ before = str(self.info['networksettings']['macaddress'])
+ if not before and self.info['networksettings'].get('networks'):
+ nets = self.info['networksettings']['networks']
+ macs = [
+ nets[i]["macaddress"] for i in nets if nets[i]["macaddress"]]
+ 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()
+ if self.module_params['mac_address'] is not None:
+ after = self.params['mac_address']
+ else:
+ after = before
+ return self._diff_update_and_compare('mac_address', before, after)
+
+ def diffparam_network(self):
+ net_mode_before = self.info['hostconfig']['networkmode']
+ net_mode_after = ''
+ before = list(self.info['networksettings'].get('networks', {}))
+ # Remove default 'podman' network in v3 for comparison
+ if before == ['podman']:
+ 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]
+ 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']:
+ after = before
+ return self._diff_update_and_compare('network', before, after)
+ # Check special network modes
+ if after in [['bridge'], ['host'], ['slirp4netns'], ['none']]:
+ net_mode_after = after[0]
+ # If changes are only for network mode and container has no networks
+ if net_mode_after and not before:
+ # Remove differences between v1 and v2
+ net_mode_after = net_mode_after.replace('bridge', 'default')
+ net_mode_after = net_mode_after.replace('slirp4netns', 'default')
+ net_mode_before = net_mode_before.replace('bridge', 'default')
+ net_mode_before = net_mode_before.replace('slirp4netns', 'default')
+ return self._diff_update_and_compare('network', net_mode_before, net_mode_after)
+ # If container is attached to network of a different container
+ if "container" in net_mode_before:
+ for netw in after:
+ if "container" in netw:
+ before = after = netw
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('network', before, after)
+
+ def diffparam_oom_score_adj(self):
+ before = self.info['hostconfig']['oomscoreadj']
+ after = self.params['oom_score_adj']
+ return self._diff_update_and_compare('oom_score_adj', before, after)
+
+ def diffparam_privileged(self):
+ before = self.info['hostconfig']['privileged']
+ after = self.params['privileged']
+ return self._diff_update_and_compare('privileged', before, after)
+
+ def diffparam_pid(self):
+ before = self.info['hostconfig']['pidmode']
+ after = self.params['pid']
+ return self._diff_update_and_compare('pid', before, after)
+
+ # TODO(sshnaidm) Need to add port ranges support
+ def diffparam_publish(self):
+ def compose(p, h):
+ s = ":".join(
+ [str(h["hostport"]), p.replace('/tcp', '')]
+ ).strip(":")
+ if h['hostip']:
+ return ":".join([h['hostip'], s])
+ return s
+
+ ports = self.info['hostconfig']['portbindings']
+ before = []
+ for port, hosts in ports.items():
+ if hosts:
+ for h in hosts:
+ before.append(compose(port, h))
+ after = self.params['publish'] or []
+ if self.params['publish_all']:
+ image_ports = self.image_info.get('config', {}).get('exposedports', {})
+ if image_ports:
+ after += list(image_ports.keys())
+ after = [
+ i.replace("/tcp", "").replace("[", "").replace("]", "")
+ for i in after]
+ # No support for port ranges yet
+ for ports in after:
+ if "-" in ports:
+ return self._diff_update_and_compare('publish', '', '')
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('publish', before, after)
+
+ def diffparam_read_only(self):
+ before = self.info['hostconfig']['readonlyrootfs']
+ after = self.params['read_only']
+ return self._diff_update_and_compare('read_only', before, after)
+
+ def diffparam_restart_policy(self):
+ before = self.info['hostconfig']['restartpolicy']['name']
+ after = self.params['restart_policy'] or ""
+ return self._diff_update_and_compare('restart_policy', before, after)
+
+ def diffparam_rm(self):
+ before = self.info['hostconfig']['autoremove']
+ after = self.params['rm']
+ 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)))
+ return self._diff_update_and_compare('security_opt', before, after)
+
+ def diffparam_stop_signal(self):
+ before = normalize_signal(self.info['config']['stopsignal'])
+ after = normalize_signal(self.params['stop_signal'])
+ return self._diff_update_and_compare('stop_signal', before, after)
+
+ def diffparam_timezone(self):
+ before = self.info['config'].get('timezone')
+ after = self.params['timezone']
+ return self._diff_update_and_compare('timezone', before, after)
+
+ def diffparam_tty(self):
+ before = self.info['config']['tty']
+ after = self.params['tty']
+ return self._diff_update_and_compare('tty', before, after)
+
+ def diffparam_user(self):
+ before = self.info['config']['user']
+ after = self.params['user']
+ return self._diff_update_and_compare('user', before, after)
+
+ def diffparam_ulimit(self):
+ 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, after = sorted(before), sorted(after)
+ return self._diff_update_and_compare('ulimit', before, after)
+ if after:
+ ulimits = self.info['hostconfig']['ulimits']
+ before = {
+ u['name'].replace('rlimit_', ''): "%s:%s" % (u['soft'], u['hard']) for u in ulimits}
+ after = {i.split('=')[0]: i.split('=')[1]
+ for i in self.params['ulimit']}
+ new_before = []
+ new_after = []
+ for u in list(after.keys()):
+ # We don't support unlimited ulimits because it depends on platform
+ if u in before and "-1" not in after[u]:
+ new_before.append([u, before[u]])
+ new_after.append([u, after[u]])
+ return self._diff_update_and_compare('ulimit', new_before, new_after)
+ return self._diff_update_and_compare('ulimit', '', '')
+
+ def diffparam_uts(self):
+ before = self.info['hostconfig']['utsmode']
+ after = self.params['uts']
+ if self.params['pod'] and not self.module_params['uts']:
+ after = before
+ return self._diff_update_and_compare('uts', before, after)
+
+ def diffparam_volume(self):
+ def clean_volume(x):
+ '''Remove trailing and double slashes from volumes.'''
+ if not x.rstrip("/"):
+ return "/"
+ return x.replace("//", "/").rstrip("/")
+
+ before = self.info['mounts']
+ before_local_vols = []
+ if before:
+ volumes = []
+ local_vols = []
+ for m in before:
+ if m['type'] != 'volume':
+ volumes.append(
+ [
+ clean_volume(m['source']),
+ clean_volume(m['destination'])
+ ])
+ elif m['type'] == 'volume':
+ local_vols.append(
+ [m['name'], clean_volume(m['destination'])])
+ before = [":".join(v) for v in volumes]
+ before_local_vols = [":".join(v) for v in local_vols]
+ if self.params['volume'] is not None:
+ after = [":".join(
+ [clean_volume(i) for i in v.split(":")[:2]]
+ ) for v in self.params['volume']]
+ else:
+ after = []
+ if before_local_vols:
+ after = list(set(after).difference(before_local_vols))
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('volume', before, after)
+
+ def diffparam_volumes_from(self):
+ # Possibly volumesfrom is not in config
+ before = self.info['hostconfig'].get('volumesfrom', []) or []
+ after = self.params['volumes_from'] or []
+ return self._diff_update_and_compare('volumes_from', before, after)
+
+ def diffparam_workdir(self):
+ before = self.info['config']['workingdir']
+ after = self.params['workdir']
+ return self._diff_update_and_compare('workdir', before, after)
+
+ def is_different(self):
+ 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:
+ dff_func = getattr(self, func_name)
+ if dff_func():
+ if fail_fast:
+ return True
+ 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 [{}, [], '']:
+ different = True
+ return different
+
+
+def ensure_image_exists(module, image, module_params):
+ """If image is passed, ensure it exists, if not - pull it or fail.
+
+ Arguments:
+ module {obj} -- ansible module object
+ image {str} -- name of image
+
+ Returns:
+ list -- list of image actions - if it pulled or nothing was done
+ """
+ image_actions = []
+ module_exec = module_params['executable']
+ is_rootfs = module_params['rootfs']
+
+ if is_rootfs:
+ if not os.path.exists(image) or not os.path.isdir(image):
+ module.fail_json(msg="Image rootfs doesn't exist %s" % image)
+ return image_actions
+ if not image:
+ return image_actions
+ rc, out, err = module.run_command([module_exec, 'image', 'exists', image])
+ if rc == 0:
+ return image_actions
+ rc, out, err = module.run_command([module_exec, 'image', 'pull', image])
+ if rc != 0:
+ module.fail_json(msg="Can't pull image %s" % image, stdout=out,
+ stderr=err)
+ image_actions.append("pulled image %s" % image)
+ return image_actions
+
+
+class PodmanContainer:
+ """Perform container tasks.
+
+ Manages podman container, inspects it and checks its current state
+ """
+
+ def __init__(self, module, name, module_params):
+ """Initialize PodmanContainer class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ name {str} -- name of container
+ """
+
+ self.module = module
+ self.module_params = module_params
+ self.name = name
+ self.stdout, self.stderr = '', ''
+ self.info = self.get_info()
+ self.version = self._get_podman_version()
+ self.diff = {}
+ self.actions = []
+
+ @property
+ def exists(self):
+ """Check if container exists."""
+ return bool(self.info != {})
+
+ @property
+ def different(self):
+ """Check if container is different."""
+ diffcheck = PodmanContainerDiff(
+ self.module,
+ self.module_params,
+ self.info,
+ self.get_image_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"
+ return is_different
+
+ @property
+ def running(self):
+ """Return True if container is running now."""
+ return self.exists and self.info['State']['Running']
+
+ @property
+ def stopped(self):
+ """Return True if container exists and is not running now."""
+ return self.exists and not self.info['State']['Running']
+
+ def get_info(self):
+ """Inspect container and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'container', b'inspect', self.name])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def get_image_info(self):
+ """Inspect container image and gather info about it."""
+ # pylint: disable=unused-variable
+ is_rootfs = self.module_params['rootfs']
+ if is_rootfs:
+ return {'Id': self.module_params['image']}
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'image', b'inspect', self.module_params['image']])
+ 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'])
+ if rc != 0 or not out or "version" not in out:
+ self.module.fail_json(msg="%s run failed!" %
+ self.module_params['executable'])
+ return out.split("version")[1].strip()
+
+ def _perform_action(self, action):
+ """Perform action with container.
+
+ Arguments:
+ action {str} -- action to perform - start, create, stop, run,
+ delete, restart
+ """
+ b_command = PodmanModuleParams(action,
+ self.module_params,
+ 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)
+ if self.module.check_mode:
+ self.module.log(
+ "PODMAN-CONTAINER-DEBUG (check_mode): %s" % full_cmd)
+ else:
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'container'] + b_command,
+ expand_user_and_vars=False)
+ self.module.log("PODMAN-CONTAINER-DEBUG: %s" % full_cmd)
+ if self.module_params['debug']:
+ self.module.log("PODMAN-CONTAINER-DEBUG STDOUT: %s" % out)
+ self.module.log("PODMAN-CONTAINER-DEBUG STDERR: %s" % err)
+ self.module.log("PODMAN-CONTAINER-DEBUG RC: %s" % rc)
+ self.stdout = out
+ self.stderr = err
+ if rc != 0:
+ self.module.fail_json(
+ msg="Can't %s container %s" % (action, self.name),
+ stdout=out, stderr=err)
+
+ def run(self):
+ """Run the container."""
+ self._perform_action('run')
+
+ def delete(self):
+ """Delete the container."""
+ self._perform_action('delete')
+
+ def stop(self):
+ """Stop the container."""
+ self._perform_action('stop')
+
+ def start(self):
+ """Start the container."""
+ self._perform_action('start')
+
+ def restart(self):
+ """Restart the container."""
+ self._perform_action('restart')
+
+ def create(self):
+ """Create the container."""
+ self._perform_action('create')
+
+ def recreate(self):
+ """Recreate the container."""
+ if self.running:
+ self.stop()
+ if not self.info['HostConfig']['AutoRemove']:
+ self.delete()
+ self.create()
+
+ def recreate_run(self):
+ """Recreate and run the container."""
+ if self.running:
+ self.stop()
+ if not self.info['HostConfig']['AutoRemove']:
+ self.delete()
+ self.run()
+
+
+class PodmanManager:
+ """Module manager class.
+
+ Defines according to parameters what actions should be applied to container
+ """
+
+ def __init__(self, module, params):
+ """Initialize PodmanManager class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ """
+
+ self.module = module
+ self.results = {
+ 'changed': False,
+ 'actions': [],
+ 'container': {},
+ }
+ self.module_params = params
+ self.name = self.module_params['name']
+ self.executable = \
+ self.module.get_bin_path(self.module_params['executable'],
+ required=True)
+ self.image = self.module_params['image']
+ image_actions = ensure_image_exists(
+ self.module, self.image, self.module_params)
+ self.results['actions'] += image_actions
+ self.state = self.module_params['state']
+ self.restart = self.module_params['force_restart']
+ self.recreate = self.module_params['recreate']
+
+ if self.module_params['generate_systemd'].get('new'):
+ self.module_params['rm'] = True
+
+ self.container = PodmanContainer(
+ self.module, self.name, self.module_params)
+
+ def update_container_result(self, changed=True):
+ """Inspect the current container, update results with last info, exit.
+
+ Keyword Arguments:
+ changed {bool} -- whether any action was performed
+ (default: {True})
+ """
+ facts = self.container.get_info() if changed else self.container.info
+ out, err = self.container.stdout, self.container.stderr
+ self.results.update({'changed': changed, 'container': facts,
+ 'podman_actions': self.container.actions},
+ stdout=out, stderr=err)
+ if self.container.diff:
+ 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})
+ self.results.update(
+ {'podman_systemd': generate_systemd(self.module,
+ self.module_params,
+ self.name,
+ self.container.version)})
+
+ def make_started(self):
+ """Run actions if desired state is 'started'."""
+ if not self.image:
+ if not self.container.exists:
+ self.module.fail_json(msg='Cannot start container when image'
+ ' is not specified!')
+ if self.restart:
+ self.container.restart()
+ self.results['actions'].append('restarted %s' %
+ self.container.name)
+ else:
+ self.container.start()
+ self.results['actions'].append('started %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ if self.container.exists and self.restart:
+ if self.container.running:
+ self.container.restart()
+ self.results['actions'].append('restarted %s' %
+ self.container.name)
+ else:
+ self.container.start()
+ self.results['actions'].append('started %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ if self.container.running and \
+ (self.container.different or self.recreate):
+ self.container.recreate_run()
+ self.results['actions'].append('recreated %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ elif self.container.running and not self.container.different:
+ if self.restart:
+ self.container.restart()
+ self.results['actions'].append('restarted %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ self.update_container_result(changed=False)
+ return
+ elif not self.container.exists:
+ self.container.run()
+ self.results['actions'].append('started %s' % self.container.name)
+ self.update_container_result()
+ return
+ elif self.container.stopped and self.container.different:
+ self.container.recreate_run()
+ self.results['actions'].append('recreated %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ elif self.container.stopped and not self.container.different:
+ self.container.start()
+ self.results['actions'].append('started %s' % self.container.name)
+ self.update_container_result()
+ return
+
+ def make_created(self):
+ """Run actions if desired state is 'created'."""
+ if not self.container.exists and not self.image:
+ self.module.fail_json(msg='Cannot create container when image'
+ ' is not specified!')
+ if not self.container.exists:
+ self.container.create()
+ self.results['actions'].append('created %s' % self.container.name)
+ self.update_container_result()
+ return
+ else:
+ if (self.container.different or self.recreate):
+ self.container.recreate()
+ self.results['actions'].append('recreated %s' %
+ self.container.name)
+ if self.container.running:
+ self.container.start()
+ self.results['actions'].append('started %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ elif self.restart:
+ if self.container.running:
+ self.container.restart()
+ self.results['actions'].append('restarted %s' %
+ self.container.name)
+ else:
+ self.container.start()
+ self.results['actions'].append('started %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ self.update_container_result(changed=False)
+ return
+
+ def make_stopped(self):
+ """Run actions if desired state is 'stopped'."""
+ if not self.container.exists and not self.image:
+ self.module.fail_json(msg='Cannot create container when image'
+ ' is not specified!')
+ if not self.container.exists:
+ self.container.create()
+ self.results['actions'].append('created %s' % self.container.name)
+ self.update_container_result()
+ return
+ if self.container.stopped:
+ self.update_container_result(changed=False)
+ return
+ elif self.container.running:
+ self.container.stop()
+ self.results['actions'].append('stopped %s' % self.container.name)
+ self.update_container_result()
+ return
+
+ def make_absent(self):
+ """Run actions if desired state is 'absent'."""
+ if not self.container.exists:
+ self.results.update({'changed': False})
+ elif self.container.exists:
+ delete_systemd(self.module,
+ self.module_params,
+ self.name,
+ self.container.version)
+ self.container.delete()
+ self.results['actions'].append('deleted %s' % self.container.name)
+ self.results.update({'changed': True})
+ self.results.update({'container': {},
+ 'podman_actions': self.container.actions})
+
+ def execute(self):
+ """Execute the desired action according to map of actions & states."""
+ states_map = {
+ 'present': self.make_created,
+ 'started': self.make_started,
+ 'absent': self.make_absent,
+ 'stopped': self.make_stopped,
+ 'created': self.make_created,
+ }
+ process_action = states_map[self.state]
+ process_action()
+ return self.results
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
new file mode 100644
index 000000000..0b4afc0bc
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py
@@ -0,0 +1,880 @@
+from __future__ import (absolute_import, division, print_function)
+import json
+
+from ansible.module_utils._text import to_bytes, to_native
+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.common import generate_systemd
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import delete_systemd
+
+
+__metaclass__ = type
+
+ARGUMENTS_SPEC_POD = dict(
+ state=dict(
+ type='str',
+ default="created",
+ choices=[
+ 'created',
+ 'killed',
+ 'restarted',
+ 'absent',
+ 'started',
+ 'stopped',
+ 'paused',
+ 'unpaused',
+ ]),
+ recreate=dict(type='bool', default=False),
+ add_host=dict(type='list', required=False, elements='str'),
+ cgroup_parent=dict(type='str', required=False),
+ cpus=dict(type='str', required=False),
+ cpuset_cpus=dict(type='str', required=False),
+ device=dict(type='list', elements='str', required=False),
+ device_read_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),
+ generate_systemd=dict(type='dict', default={}),
+ gidmap=dict(type='list', elements='str', required=False),
+ hostname=dict(type='str', required=False),
+ infra=dict(type='bool', required=False),
+ infra_conmon_pidfile=dict(type='str', required=False),
+ infra_command=dict(type='str', required=False),
+ infra_image=dict(type='str', required=False),
+ infra_name=dict(type='str', required=False),
+ ip=dict(type='str', required=False),
+ label=dict(type='dict', required=False),
+ label_file=dict(type='str', required=False),
+ mac_address=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,
+ aliases=['network_aliases']),
+ no_hosts=dict(type='bool', required=False),
+ pid=dict(type='str', required=False),
+ pod_id_file=dict(type='str', required=False),
+ publish=dict(type='list', required=False,
+ elements='str', aliases=['ports']),
+ share=dict(type='str', required=False),
+ subgidname=dict(type='str', required=False),
+ subuidname=dict(type='str', required=False),
+ uidmap=dict(type='list', elements='str', required=False),
+ userns=dict(type='str', required=False),
+ volume=dict(type='list', elements='str', aliases=['volumes'],
+ required=False),
+ executable=dict(type='str', required=False, default='podman'),
+ debug=dict(type='bool', default=False),
+)
+
+
+class PodmanPodModuleParams:
+ """Creates list of arguments for podman CLI command.
+
+ Arguments:
+ action {str} -- action type from 'run', 'stop', 'create', 'delete',
+ 'start'
+ params {dict} -- dictionary of module parameters
+
+ """
+
+ def __init__(self, action, params, podman_version, module):
+ self.params = params
+ self.action = action
+ self.podman_version = podman_version
+ self.module = module
+
+ def construct_command_from_params(self):
+ """Create a podman command from given module parameters.
+
+ Returns:
+ list -- list of byte strings for Popen command
+ """
+ if self.action in ['start', 'restart', 'stop', 'delete', 'pause',
+ 'unpause', 'kill']:
+ return self._simple_action()
+ if self.action in ['create']:
+ return self._create_action()
+ self.module.fail_json(msg="Unknown action %s" % self.action)
+
+ def _simple_action(self):
+ if self.action in ['start', 'restart', 'stop', 'pause', 'unpause', 'kill']:
+ cmd = [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']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+ self.module.fail_json(msg="Unknown action %s" % self.action)
+
+ def _create_action(self):
+ cmd = [self.action]
+ 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]
+
+ 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))
+
+ def addparam_add_host(self, c):
+ for g in self.params['add_host']:
+ c += ['--add-host', g]
+ return c
+
+ def addparam_cgroup_parent(self, c):
+ return c + ['--cgroup-parent', self.params['cgroup_parent']]
+
+ def addparam_cpus(self, c):
+ return c + ['--cpus', self.params['cpus']]
+
+ def addparam_cpuset_cpus(self, c):
+ return c + ['--cpuset-cpus', self.params['cpuset_cpus']]
+
+ def addparam_device(self, c):
+ for dev in self.params['device']:
+ c += ['--device', dev]
+ return c
+
+ def addparam_device_read_bps(self, c):
+ for dev in self.params['device_read_bps']:
+ c += ['--device-read-bps', dev]
+ return c
+
+ def addparam_dns(self, c):
+ for g in self.params['dns']:
+ c += ['--dns', g]
+ return c
+
+ def addparam_dns_opt(self, c):
+ for g in self.params['dns_opt']:
+ c += ['--dns-opt', g]
+ return c
+
+ def addparam_dns_search(self, c):
+ for g in self.params['dns_search']:
+ c += ['--dns-search', g]
+ return c
+
+ def addparam_gidmap(self, c):
+ for gidmap in self.params['gidmap']:
+ c += ['--gidmap', gidmap]
+ return c
+
+ def addparam_hostname(self, c):
+ return c + ['--hostname', self.params['hostname']]
+
+ def addparam_infra(self, c):
+ return c + [b'='.join([b'--infra',
+ to_bytes(self.params['infra'],
+ errors='surrogate_or_strict')])]
+
+ def addparam_infra_conmon_pidfile(self, c):
+ return c + ['--infra-conmon-pidfile', self.params['infra_conmon_pidfile']]
+
+ def addparam_infra_command(self, c):
+ return c + ['--infra-command', self.params['infra_command']]
+
+ def addparam_infra_image(self, c):
+ return c + ['--infra-image', self.params['infra_image']]
+
+ def addparam_infra_name(self, c):
+ return c + ['--infra-name', self.params['infra_name']]
+
+ def addparam_ip(self, c):
+ return c + ['--ip', self.params['ip']]
+
+ def addparam_label(self, c):
+ for label in self.params['label'].items():
+ c += ['--label', b'='.join(
+ [to_bytes(i, errors='surrogate_or_strict') for i in label])]
+ return c
+
+ def addparam_label_file(self, c):
+ return c + ['--label-file', self.params['label_file']]
+
+ def addparam_mac_address(self, c):
+ return c + ['--mac-address', self.params['mac_address']]
+
+ def addparam_name(self, c):
+ return c + ['--name', self.params['name']]
+
+ def addparam_network(self, c):
+ if LooseVersion(self.podman_version) >= LooseVersion('4.0.0'):
+ for net in self.params['network']:
+ c += ['--network', net]
+ return c
+ return c + ['--network', ",".join(self.params['network'])]
+
+ def addparam_network_aliases(self, c):
+ for alias in self.params['network_aliases']:
+ c += ['--network-alias', alias]
+ return c
+
+ def addparam_no_hosts(self, c):
+ return c + ["=".join('--no-hosts', self.params['no_hosts'])]
+
+ def addparam_pid(self, c):
+ return c + ['--pid', self.params['pid']]
+
+ def addparam_pod_id_file(self, c):
+ return c + ['--pod-id-file', self.params['pod_id_file']]
+
+ def addparam_publish(self, c):
+ for g in self.params['publish']:
+ c += ['--publish', g]
+ return c
+
+ def addparam_share(self, c):
+ return c + ['--share', self.params['share']]
+
+ def addparam_subgidname(self, c):
+ return c + ['--subgidname', self.params['subgidname']]
+
+ def addparam_subuidname(self, c):
+ return c + ['--subuidname', self.params['subuidname']]
+
+ def addparam_uidmap(self, c):
+ for uidmap in self.params['uidmap']:
+ c += ['--uidmap', uidmap]
+ return c
+
+ def addparam_userns(self, c):
+ return c + ['--userns', self.params['userns']]
+
+ def addparam_volume(self, c):
+ for vol in self.params['volume']:
+ if vol:
+ c += ['--volume', vol]
+ return c
+
+
+class PodmanPodDefaults:
+ def __init__(self, module, podman_version):
+ self.module = module
+ self.version = podman_version
+ self.defaults = {
+ 'add_host': [],
+ 'dns': [],
+ 'dns_opt': [],
+ 'dns_search': [],
+ 'infra': True,
+ 'label': {},
+ }
+
+ def default_dict(self):
+ # make here any changes to self.defaults related to podman version
+ # https://github.com/containers/libpod/pull/5669
+ # if (LooseVersion(self.version) >= LooseVersion('1.8.0')
+ # and LooseVersion(self.version) < LooseVersion('1.9.0')):
+ # self.defaults['cpu_shares'] = 1024
+ return self.defaults
+
+
+class PodmanPodDiff:
+ def __init__(self, module, module_params, info, infra_info, podman_version):
+ self.module = module
+ self.module_params = module_params
+ self.version = podman_version
+ self.default_dict = None
+ self.info = lower_keys(info)
+ self.infra_info = lower_keys(infra_info)
+ self.params = self.defaultize()
+ self.diff = {'before': {}, 'after': {}}
+ self.non_idempotent = {}
+
+ def defaultize(self):
+ params_with_defaults = {}
+ self.default_dict = PodmanPodDefaults(
+ 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]
+ else:
+ params_with_defaults[p] = self.module_params[p]
+ return params_with_defaults
+
+ 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})
+ return True
+ return False
+
+ def diffparam_add_host(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('add_host', '', '')
+ before = self.infra_info['hostconfig']['extrahosts'] or []
+ after = self.params['add_host']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ 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']
+ after = self.params['cgroup_parent'] or before
+ return self._diff_update_and_compare('cgroup_parent', before, after)
+
+ def diffparam_dns(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('dns', '', '')
+ before = self.infra_info['hostconfig']['dns'] or []
+ after = self.params['dns']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('dns', before, after)
+
+ def diffparam_dns_opt(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('dns_opt', '', '')
+ before = self.infra_info['hostconfig']['dnsoptions'] or []
+ after = self.params['dns_opt']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('dns_opt', before, after)
+
+ def diffparam_dns_search(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('dns_search', '', '')
+ before = self.infra_info['hostconfig']['dnssearch'] or []
+ after = self.params['dns_search']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('dns_search', before, after)
+
+ def diffparam_hostname(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('hostname', '', '')
+ before = self.infra_info['config']['hostname']
+ after = self.params['hostname'] or before
+ return self._diff_update_and_compare('hostname', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/issues/6968
+ def diffparam_infra(self):
+ if 'state' in self.info and 'infracontainerid' in self.info['state']:
+ before = self.info['state']['infracontainerid'] != ""
+ else:
+ # TODO(sshnaidm): https://github.com/containers/podman/issues/6968
+ before = 'infracontainerid' in self.info
+ after = self.params['infra']
+ return self._diff_update_and_compare('infra', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/issues/6969
+ # def diffparam_infra_command(self):
+ # before = str(self.info['hostconfig']['infra_command'])
+ # after = self.params['infra_command']
+ # return self._diff_update_and_compare('infra_command', before, after)
+
+ def diffparam_infra_image(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('infra_image', '', '')
+ before = str(self.infra_info['imagename'])
+ after = before
+ if self.module_params['infra_image']:
+ after = self.params['infra_image']
+ before = before.replace(":latest", "")
+ after = after.replace(":latest", "")
+ before = before.split("/")[-1] # pylint: disable=W,C,R
+ after = after.split("/")[-1] # pylint: disable=W,C,R
+ return self._diff_update_and_compare('infra_image', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/pull/6956
+ # def diffparam_ip(self):
+ # before = str(self.info['hostconfig']['ip'])
+ # after = self.params['ip']
+ # return self._diff_update_and_compare('ip', before, after)
+
+ def diffparam_label(self):
+ if 'config' in self.info and 'labels' in self.info['config']:
+ before = self.info['config'].get('labels') or {}
+ else:
+ before = self.info['labels'] if 'labels' in self.info else {}
+ after = self.params['label']
+ # Strip out labels that are coming from systemd files
+ # https://github.com/containers/ansible-podman-collections/issues/276
+ if 'podman_systemd_unit' in before:
+ after.pop('podman_systemd_unit', None)
+ before.pop('podman_systemd_unit', None)
+ return self._diff_update_and_compare('label', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/pull/6956
+ # def diffparam_mac_address(self):
+ # before = str(self.info['hostconfig']['mac_address'])
+ # after = self.params['mac_address']
+ # return self._diff_update_and_compare('mac_address', before, after)
+
+ def diffparam_network(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('network', [], [])
+ net_mode_before = self.infra_info['hostconfig']['networkmode']
+ net_mode_after = ''
+ before = list(self.infra_info['networksettings'].get('networks', {}))
+ # Remove default 'podman' network in v3 for comparison
+ if before == ['podman']:
+ before = []
+ after = self.params['network'] or []
+ # Special case for options for slirp4netns rootless networking from v2
+ if net_mode_before == 'slirp4netns' and 'createcommand' in self.info:
+ cr_com = self.info['createcommand']
+ if '--network' in cr_com:
+ cr_net = cr_com[cr_com.index('--network') + 1].lower()
+ if 'slirp4netns:' in cr_net:
+ before = [cr_net]
+ # Currently supported only 'host' and 'none' network modes idempotency
+ if after in [['bridge'], ['host'], ['slirp4netns']]:
+ net_mode_after = after[0]
+
+ if net_mode_after and not before:
+ # Remove differences between v1 and v2
+ net_mode_after = net_mode_after.replace('bridge', 'default')
+ net_mode_after = net_mode_after.replace('slirp4netns', 'default')
+ net_mode_before = net_mode_before.replace('bridge', 'default')
+ net_mode_before = net_mode_before.replace('slirp4netns', 'default')
+ return self._diff_update_and_compare('network', net_mode_before, net_mode_after)
+ # For 4.4.0+ podman versions with no network specified
+ if not net_mode_after and net_mode_before == 'slirp4netns' and not after:
+ net_mode_after = 'slirp4netns'
+ if before == ['slirp4netns']:
+ after = ['slirp4netns']
+ if not net_mode_after and net_mode_before == 'bridge' and not after:
+ net_mode_after = 'bridge'
+ if before == ['bridge']:
+ after = ['bridge']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('network', before, after)
+
+ # TODO(sshnaidm)
+ # def diffparam_no_hosts(self):
+ # before = str(self.info['hostconfig']['no_hosts'])
+ # after = self.params['no_hosts']
+ # return self._diff_update_and_compare('no_hosts', before, after)
+
+ # TODO(sshnaidm) Need to add port ranges support
+ def diffparam_publish(self):
+ def compose(p, h):
+ s = ":".join(
+ [str(h["hostport"]), p.replace('/tcp', '')]
+ ).strip(":")
+ if h['hostip']:
+ return ":".join([h['hostip'], s])
+ return s
+
+ if not self.infra_info:
+ return self._diff_update_and_compare('publish', '', '')
+
+ ports = self.infra_info['hostconfig']['portbindings']
+ before = []
+ for port, hosts in ports.items():
+ if hosts:
+ for h in hosts:
+ before.append(compose(port, h))
+ after = self.params['publish'] or []
+ after = [
+ i.replace("/tcp", "").replace("[", "").replace("]", "")
+ for i in after]
+ # No support for port ranges yet
+ for ports in after:
+ if "-" in ports:
+ return self._diff_update_and_compare('publish', '', '')
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('publish', before, after)
+
+ def diffparam_share(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('share', '', '')
+ if 'sharednamespaces' in self.info:
+ before = self.info['sharednamespaces']
+ elif 'config' in self.info:
+ before = [
+ i.split('shares')[1].lower()
+ for i in self.info['config'] if 'shares' in i]
+ # TODO(sshnaidm): to discover why in podman v1 'cgroup' appears
+ before.remove('cgroup')
+ else:
+ before = []
+ if self.params['share'] is not None:
+ after = self.params['share'].split(",")
+ else:
+ after = ['uts', 'ipc', 'net']
+ # 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"]:
+ after.append('user')
+
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('share', before, after)
+
+ def is_different(self):
+ 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:
+ dff_func = getattr(self, func_name)
+ if dff_func():
+ if fail_fast:
+ return True
+ 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 [{}, [], '']:
+ different = True
+ return different
+
+
+class PodmanPod:
+ """Perform pod tasks.
+
+ Manages podman pod, inspects it and checks its current state
+ """
+
+ def __init__(self, module, name, module_params):
+ """Initialize PodmanPod class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ name {str} -- name of pod
+ """
+
+ self.module = module
+ self.module_params = module_params
+ self.name = name
+ self.stdout, self.stderr = '', ''
+ self.info = self.get_info()
+ self.infra_info = self.get_infra_info()
+ self.version = self._get_podman_version()
+ self.diff = {}
+ self.actions = []
+
+ @property
+ def exists(self):
+ """Check if pod exists."""
+ return bool(self.info != {})
+
+ @property
+ def different(self):
+ """Check if pod is different."""
+ diffcheck = PodmanPodDiff(
+ self.module,
+ self.module_params,
+ self.info,
+ self.infra_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"
+ return is_different
+
+ @property
+ def running(self):
+ """Return True if pod is running now."""
+ if 'status' in self.info['State']:
+ return self.info['State']['status'] == 'Running'
+ # older podman versions (1.6.x) don't have status in 'podman pod inspect'
+ # if other methods fail, use 'podman pod ps'
+ ps_info = self.get_ps()
+ if 'status' in ps_info:
+ return ps_info['status'] == 'Running'
+ return self.info['State'] == 'Running'
+
+ @property
+ def paused(self):
+ """Return True if pod is paused now."""
+ if 'status' in self.info['State']:
+ return self.info['State']['status'] == 'Paused'
+ return self.info['State'] == 'Paused'
+
+ @property
+ def stopped(self):
+ """Return True if pod exists and is not running now."""
+ if not self.exists:
+ return False
+ if 'status' in self.info['State']:
+ return not (self.info['State']['status'] == 'Running')
+ return not (self.info['State'] == 'Running')
+
+ def get_info(self):
+ """Inspect pod and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'pod', b'inspect', self.name])
+ return json.loads(out) if rc == 0 else {}
+
+ def get_ps(self):
+ """Inspect pod process and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'pod', b'ps', b'--format', b'json', b'--filter', 'name=' + self.name])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def get_infra_info(self):
+ """Inspect pod and gather info about it."""
+ if not self.info:
+ return {}
+ if 'InfraContainerID' in self.info:
+ infra_container_id = self.info['InfraContainerID']
+ elif 'State' in self.info and 'infraContainerID' in self.info['State']:
+ infra_container_id = self.info['State']['infraContainerID']
+ else:
+ return {}
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'inspect', infra_container_id])
+ 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'])
+ if rc != 0 or not out or "version" not in out:
+ self.module.fail_json(msg="%s run failed!" % self.module_params['executable'])
+ return out.split("version")[1].strip()
+
+ def _perform_action(self, action):
+ """Perform action with pod.
+
+ Arguments:
+ action {str} -- action to perform - start, create, stop, pause
+ unpause, delete, restart, kill
+ """
+ b_command = PodmanPodModuleParams(action,
+ self.module_params,
+ self.version,
+ self.module,
+ ).construct_command_from_params()
+ full_cmd = " ".join([self.module_params['executable'], 'pod']
+ + [to_native(i) for i in b_command])
+ self.module.log("PODMAN-POD-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'pod'] + b_command,
+ expand_user_and_vars=False)
+ self.stdout = out
+ self.stderr = err
+ if rc != 0:
+ self.module.fail_json(
+ msg="Can't %s pod %s" % (action, self.name),
+ stdout=out, stderr=err)
+
+ def delete(self):
+ """Delete the pod."""
+ self._perform_action('delete')
+
+ def stop(self):
+ """Stop the pod."""
+ self._perform_action('stop')
+
+ def start(self):
+ """Start the pod."""
+ self._perform_action('start')
+
+ def create(self):
+ """Create the pod."""
+ self._perform_action('create')
+
+ def recreate(self):
+ """Recreate the pod."""
+ self.delete()
+ self.create()
+
+ def restart(self):
+ """Restart the pod."""
+ self._perform_action('restart')
+
+ def kill(self):
+ """Kill the pod."""
+ self._perform_action('kill')
+
+ def pause(self):
+ """Pause the pod."""
+ self._perform_action('pause')
+
+ def unpause(self):
+ """Unpause the pod."""
+ self._perform_action('unpause')
+
+
+class PodmanPodManager:
+ """Module manager class.
+
+ Defines according to parameters what actions should be applied to pod
+ """
+
+ def __init__(self, module, params):
+ """Initialize PodmanManager class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ """
+
+ self.module = module
+ self.module_params = params
+ self.results = {
+ 'changed': False,
+ 'actions': [],
+ 'pod': {},
+ }
+ 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.pod = PodmanPod(self.module, self.name, self.module_params)
+
+ def update_pod_result(self, changed=True):
+ """Inspect the current pod, update results with last info, exit.
+
+ Keyword Arguments:
+ changed {bool} -- whether any action was performed
+ (default: {True})
+ """
+ facts = self.pod.get_info() if changed else self.pod.info
+ out, err = self.pod.stdout, self.pod.stderr
+ self.results.update({'changed': changed, 'pod': facts,
+ 'podman_actions': self.pod.actions},
+ stdout=out, stderr=err)
+ if self.pod.diff:
+ 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})
+ self.results.update(
+ {'podman_systemd': generate_systemd(self.module,
+ self.module_params,
+ self.name,
+ self.pod.version)})
+
+ def execute(self):
+ """Execute the desired action according to map of actions & states."""
+ states_map = {
+ 'created': self.make_created,
+ 'started': self.make_started,
+ 'stopped': self.make_stopped,
+ 'restarted': self.make_restarted,
+ 'absent': self.make_absent,
+ 'killed': self.make_killed,
+ 'paused': self.make_paused,
+ 'unpaused': self.make_unpaused,
+
+ }
+ process_action = states_map[self.state]
+ process_action()
+ return self.results
+
+ def _create_or_recreate_pod(self):
+ """Ensure pod exists and is exactly as it should be by input params."""
+ changed = False
+ if self.pod.exists:
+ if self.pod.different or self.recreate:
+ self.pod.recreate()
+ self.results['actions'].append('recreated %s' % self.pod.name)
+ changed = True
+ elif not self.pod.exists:
+ self.pod.create()
+ self.results['actions'].append('created %s' % self.pod.name)
+ changed = True
+ return changed
+
+ def make_created(self):
+ """Run actions if desired state is 'created'."""
+ if self.pod.exists and not self.pod.different:
+ self.update_pod_result(changed=False)
+ return
+ self._create_or_recreate_pod()
+ self.update_pod_result()
+
+ def make_killed(self):
+ """Run actions if desired state is 'killed'."""
+ self._create_or_recreate_pod()
+ self.pod.kill()
+ self.results['actions'].append('killed %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_paused(self):
+ """Run actions if desired state is 'paused'."""
+ changed = self._create_or_recreate_pod()
+ if self.pod.paused:
+ self.update_pod_result(changed=changed)
+ return
+ self.pod.pause()
+ self.results['actions'].append('paused %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_unpaused(self):
+ """Run actions if desired state is 'unpaused'."""
+ changed = self._create_or_recreate_pod()
+ if not self.pod.paused:
+ self.update_pod_result(changed=changed)
+ return
+ self.pod.unpause()
+ self.results['actions'].append('unpaused %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_started(self):
+ """Run actions if desired state is 'started'."""
+ changed = self._create_or_recreate_pod()
+ if not changed and self.pod.running:
+ self.update_pod_result(changed=changed)
+ return
+
+ # self.pod.unpause() TODO(sshnaidm): to unpause if state == started?
+ self.pod.start()
+ self.results['actions'].append('started %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_stopped(self):
+ """Run actions if desired state is 'stopped'."""
+ if not self.pod.exists:
+ self.module.fail_json("Pod %s doesn't exist!" % self.pod.name)
+ if self.pod.running:
+ self.pod.stop()
+ self.results['actions'].append('stopped %s' % self.pod.name)
+ self.update_pod_result()
+ elif self.pod.stopped:
+ self.update_pod_result(changed=False)
+
+ def make_restarted(self):
+ """Run actions if desired state is 'restarted'."""
+ if self.pod.exists:
+ self.pod.restart()
+ self.results['actions'].append('restarted %s' % self.pod.name)
+ self.results.update({'changed': True})
+ self.update_pod_result()
+ else:
+ self.module.fail_json("Pod %s doesn't exist!" % self.pod.name)
+
+ def make_absent(self):
+ """Run actions if desired state is 'absent'."""
+ if not self.pod.exists:
+ self.results.update({'changed': False})
+ elif self.pod.exists:
+ delete_systemd(self.module,
+ self.module_params,
+ self.name,
+ self.pod.version)
+ self.pod.delete()
+ self.results['actions'].append('deleted %s' % self.pod.name)
+ self.results.update({'changed': True})
+ self.results.update({'pod': {},
+ 'podman_actions': self.pod.actions})