Merge "Idempotency for podman_container"

This commit is contained in:
Zuul 2019-11-13 09:17:43 +00:00 committed by Gerrit Code Review
commit ad27e8022d
3 changed files with 793 additions and 115 deletions

View File

@ -23,6 +23,8 @@ __metaclass__ = type
import json
import yaml
from packaging import version
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils._text import to_bytes, to_native
@ -101,7 +103,7 @@ options:
(Not available for remote commands) You can also override the default
path of the authentication file by setting the ``REGISTRY_AUTH_FILE``
environment variable. ``export REGISTRY_AUTH_FILE=path``
type: str
type: path
blkio_weight:
description:
- Block IO weight (relative weight) accepts a weight value between 10 and
@ -128,7 +130,22 @@ options:
If the path is not absolute, the path is considered to be relative to
the cgroups path of the init process. Cgroups will be created if they
do not already exist.
type: path
cgroupns:
description:
- Path to cgroups under which the cgroup for the container will be
created.
type: str
cgroups:
description:
- Determines whether the container will create CGroups.
Valid values are enabled and disabled, which the default being enabled.
The disabled option will force the container to not create CGroups,
and thus conflicts with CGroup options cgroupns and cgroup-parent.
type: str
choices:
- default
- disabled
cidfile:
description:
- Write the container ID to the file
@ -154,9 +171,17 @@ options:
description:
- Limit the CPU real-time period in microseconds
type: int
cpu_rt_period:
description:
- Limit the CPU real-time period in microseconds.
Limit the container's Real Time CPU usage. This flag tell the kernel to
restrict the container's Real Time CPU usage to the period you specify.
type: int
cpu_rt_runtime:
description:
- Limit the CPU real-time runtime in microseconds
- Limit the CPU real-time runtime in microseconds.
This flag tells the kernel to limit the amount of time in a given CPU
period Real Time tasks may consume.
type: int
cpu_shares:
description:
@ -180,6 +205,11 @@ options:
- Run container in detach mode
type: bool
default: True
debug:
description:
- Return additional information which can be helpful for investigations.
type: bool
default: False
detach_keys:
description:
- Override the key sequence for detaching a container. Format is a single
@ -190,27 +220,28 @@ options:
- Add a host device to the container.
The format is <device-on-host>[:<device-on-container>][:<permissions>]
(e.g. device /dev/sdc:/dev/xvdc:rwm)
type: str
type: list
elements: str
device_read_bps:
description:
- Limit read rate (bytes per second) from a device
(e.g. device-read-bps /dev/sda:1mb)
type: str
type: list
device_read_iops:
description:
- Limit read rate (IO per second) from a device
(e.g. device-read-iops /dev/sda:1000)
type: str
type: list
device_write_bps:
description:
- Limit write rate (bytes per second) to a device
(e.g. device-write-bps /dev/sda:1mb)
type: str
type: list
device_write_iops:
description:
- Limit write rate (IO per second) to a device
(e.g. device-write-iops /dev/sda:1000)
type: str
type: list
dns:
description:
- Set custom DNS servers
@ -240,6 +271,11 @@ options:
description:
- Read in a line delimited file of environment variables
type: path
env_host:
description:
- Use all current host environment variables in container.
Defaults to false.
type: bool
etc_hosts:
description:
- Dict of host-to-IP mappings, where each host name is a key in the
@ -271,7 +307,7 @@ options:
group_add:
description:
- Add additional groups to run as
type: str
type: list
healthcheck:
description:
- Set or alter a healthcheck command for a container.
@ -356,6 +392,7 @@ options:
description:
- Kernel memory limit
(format <number>[<unit>], where unit = b, k, m or g)
Note - idempotency is supported for integers only.
type: str
label:
description:
@ -385,10 +422,12 @@ options:
memory:
description:
- Memory limit (format 10k, where unit = b, k, m or g)
Note - idempotency is supported for integers only.
type: str
memory_reservation:
description:
- Memory soft limit (format 100m, where unit = b, k, m or g)
Note - idempotency is supported for integers only.
type: str
memory_swap:
description:
@ -396,6 +435,7 @@ options:
(--memory) flag.
The swap LIMIT should always be larger than -m (--memory) value.
By default, the swap LIMIT will be set to double the value of --memory
Note - idempotency is supported for integers only.
type: str
memory_swappiness:
description:
@ -419,7 +459,8 @@ options:
* ns:<path> path to a network namespace to join
* slirp4netns use slirp4netns to create a user network stack.
This is the default for rootless containers
type: str
type: list
elements: str
aliases:
- net
no_hosts:
@ -512,7 +553,8 @@ options:
security_opt:
description:
- Security Options. For example security_opt "seccomp=unconfined"
type: str
type: list
elements: str
shm_size:
description:
- Size of /dev/shm. The format is <number><unit>. number must be greater
@ -530,7 +572,7 @@ options:
stop_signal:
description:
- Signal to stop a container. Default is SIGTERM.
type: str
type: int
stop_timeout:
description:
- Timeout (in seconds) to stop a container. Default is 10.
@ -813,9 +855,12 @@ class PodmanModuleParams:
params {dict} -- dictionary of module parameters
"""
def __init__(self, action, params):
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.
@ -853,19 +898,26 @@ class PodmanModuleParams:
cmd = ['rm', '-f', self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
def addparam_detach(self, c):
return c + ['--detach=%s' % self.params['detach']]
def addparam_etc_hosts(self, c):
for host_ip in self.params['etc_hosts'].items():
c += ['--add-host', ':'.join(host_ip)]
return c
def check_version(self, param, minv=None, maxv=None):
if minv and version.parse(minv) > version.parse(
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 version.parse(maxv) < version.parse(
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']]
@ -884,6 +936,14 @@ class PodmanModuleParams:
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']]
@ -896,6 +956,9 @@ class PodmanModuleParams:
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']]
@ -911,23 +974,36 @@ class PodmanModuleParams:
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):
return c + ['--device', self.params['device']]
for dev in self.params['device']:
c += ['--device', dev]
return c
def addparam_device_read_bps(self, c):
return c + ['--device-read-bps', self.params['device_read_bps']]
for dev in self.params['device_read_bps']:
c += ['--device-read-bps', dev]
return c
def addparam_device_read_iops(self, c):
return c + ['--device-read-iops', self.params['device_read_iops']]
for dev in self.params['device_read_iops']:
c += ['--device-read-iops', dev]
return c
def addparam_device_write_bps(self, c):
return c + ['--device-write-bps', self.params['device_write_bps']]
for dev in self.params['device_write_bps']:
c += ['--device-write-bps', dev]
return c
def addparam_device_write_iops(self, c):
return c + ['--device-write-iops', self.params['device_write_iops']]
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'])]
@ -951,6 +1027,15 @@ class PodmanModuleParams:
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]
@ -960,7 +1045,9 @@ class PodmanModuleParams:
return c + ['--gidmap', self.params['gidmap']]
def addparam_group_add(self, c):
return c + ['--group-add', self.params['group_add']]
for g in self.params['group_add']:
c += ['--group-add', g]
return c
def addparam_healthcheck(self, c):
return c + ['--healthcheck', self.params['healthcheck']]
@ -1010,7 +1097,8 @@ class PodmanModuleParams:
def addparam_label(self, c):
for label in self.params['label'].items():
c += ['--label', '='.join(label)]
c += ['--label', b'='.join([to_bytes(l, errors='surrogate_or_strict')
for l in label])]
return c
def addparam_label_file(self, c):
@ -1038,7 +1126,7 @@ class PodmanModuleParams:
return c + ['--mount', self.params['mount']]
def addparam_network(self, c):
return c + ['--network', self.params['network']]
return c + ['--network', ",".join(self.params['network'])]
def addparam_no_hosts(self, c):
return c + ['--no-hosts=%s' % self.params['no_hosts']]
@ -1085,7 +1173,9 @@ class PodmanModuleParams:
return c + ['--rootfs=%s' % self.params['rootfs']]
def addparam_security_opt(self, c):
return c + ['--security-opt', self.params['security_opt']]
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']]
@ -1161,6 +1251,507 @@ class PodmanModuleParams:
return c + self.params['cmd_args']
class PodmanDefaults:
def __init__(self, module, podman_version):
self.module = module
self.version = podman_version
self.defaults = {
"annotation": [
"io.container.manager",
"io.kubernetes.cri-o.containertype",
"io.kubernetes.cri-o.created",
"io.kubernetes.cri-o.tty",
"io.podman.annotations.autoremove",
"io.podman.annotations.init",
"io.podman.annotations.privileged",
"io.podman.annotations.publish-all",
"org.opencontainers.image.stopsignal",
],
"blkio_weight": 0,
"cap_add": ['cap_audit_write',
'cap_chown',
'cap_dac_override',
'cap_fowner',
'cap_fsetid',
'cap_kill',
'cap_mknod',
'cap_net_bind_service',
'cap_net_raw',
'cap_setfcap',
'cap_setgid',
'cap_setpcap',
'cap_setuid',
'cap_sys_chroot'],
"cgroups": "default",
"cgroup_parent": "",
"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": [
"path",
"term",
"hostname",
"container",
"home"
],
"env_host": False,
"etc_hosts": [],
"group_add": [],
"ipc": "",
"kernelmemory": "0",
"log_driver": "k8s-file",
"memory": "0",
"memory_swap": "0",
"memory_reservation": "0",
# "memory_swappiness": -1,
"no_hosts": False,
"oom_score_adj": 0,
"pid": "",
"privileged": False,
"rm": False,
"security_opt": [],
"stop_signal": 15,
"tty": False,
"user": "",
"uts": "",
"volume": [],
"workdir": "/",
}
def default_dict(self):
# make here any changes to self.defaults related to podman version
if version.parse(self.version) < version.parse("1.6.0"):
self.defaults["env"] = [
"path",
"term",
"hostname",
"container",
]
return self.defaults
class PodmanContainerDiff:
def __init__(self, module, info, podman_version):
self.module = module
self.version = podman_version
self.default_dict = None
self.info = json.loads(json.dumps(info).lower())
self.params = self.defaultize()
self.diff = {'before': {}, 'after': {}}
self.non_idempotent = {
'env_file',
'env_host',
'stop_timeout',
"network", # Issue in libpod
"ulimit", # Defaults depend on user and platform, impossible to guess
}
self.all_caps = ['cap_audit_control',
'cap_audit_read',
'cap_audit_write',
'cap_block_suspend',
'cap_chown',
'cap_dac_override',
'cap_dac_read_search',
'cap_fowner',
'cap_fsetid',
'cap_ipc_lock',
'cap_ipc_owner',
'cap_kill',
'cap_lease',
'cap_linux_immutable',
'cap_mac_admin',
'cap_mac_override',
'cap_mknod',
'cap_net_admin',
'cap_net_bind_service',
'cap_net_broadcast',
'cap_net_raw',
'cap_setfcap',
'cap_setgid',
'cap_setpcap',
'cap_setuid',
'cap_sys_admin',
'cap_sys_boot',
'cap_sys_chroot',
'cap_sys_module',
'cap_sys_nice',
'cap_sys_pacct',
'cap_sys_ptrace',
'cap_sys_rawio',
'cap_sys_resource',
'cap_sys_time',
'cap_sys_tty_config',
'cap_syslog',
'cap_wake_alarm']
def defaultize(self):
params_with_defaults = {}
self.default_dict = PodmanDefaults(
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_annotation(self):
# before = self.info['config']['annotations']
# after = before.copy()
# if (self.params['annotation'] and self.params['annotation'] != self.default_dict['annotation']):
# after.update(self.params['annotation'])
# for key in list(after):
# if (key not in self.params['annotation'] and key not in self.default_dict['annotation']):
# del after[key]
# 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']
after = self.default_dict['cap_add']
if self.module.params['cap_add'] is not None:
after += ["cap_" + i.lower()
for i in self.module.params['cap_add']]
if self.params['privileged']:
after = self.all_caps
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']
after = self.default_dict['cap_add']
if self.module.params['cap_drop'] is not None:
for c in ["cap_" + i.lower() for i in self.module.params['cap_drop']]:
if c in after:
after.remove(c)
if self.params['privileged']:
after = self.all_caps
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('cap_add', before, after)
def diffparam_cgroup_parent(self):
before = self.info['hostconfig']['cgroupparent']
after = self.params['cgroup_parent']
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']
return self._diff_update_and_compare('cidfile', before, after)
def diffparam_command(self):
before = self.info['config']['cmd']
after = self.params['command']
if isinstance(after, str):
after = after.split()
return self._diff_update_and_compare('command', before, after)
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']]
after = [":".join(i.split(":")[:2]) for i in self.params['device']]
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):
keys = [i.split("=")[0] for i in self.info['config']['env']]
if self.module.params['env'] is None:
if sorted(keys) == sorted(self.default_dict['env']):
before = after = self.info['config']['env']
else:
before = self.info['config']['env']
after = ["=".join([i, "default"]) for i in self.default_dict['env']]
return self._diff_update_and_compare('env', before, after)
before = {i.split("=")[0]: i.split("=")[1] for i in self.info['config']['env']}
after = {}
for key in self.default_dict['env']:
after[key] = before[key]
for d_key in self.module.params['env']:
after[d_key.lower()] = self.module.params['env'][d_key].lower()
return self._diff_update_and_compare('env', before, after)
def diffparam_etc_hosts(self):
if self.info['hostconfig']['extrahosts']:
before = dict([i.split(":") 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)
# 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 = self.info['config']['image'].replace(
"docker.io/library/", "").replace(
"docker.io/", "").replace(
":latest", "")
after = self.params['image']
return self._diff_update_and_compare('image', before, after)
def diffparam_ipc(self):
before = self.info['hostconfig']['ipcmode']
after = self.params['ipc']
return self._diff_update_and_compare('ipc', before, after)
def diffparam_label(self):
before = self.info['config']['labels']
after = self.params['label'] if not self.params['label'] else {
k: str(v) for k, v in self.params['label'].items()
}
return self._diff_update_and_compare('label', before, after)
def diffparam_log_driver(self):
before = self.info['hostconfig']['logconfig']['type']
after = self.params['log_driver']
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 = self.info['logpath']
if self.module.params['log_opt'] in [None, '']:
after = before
else:
after = self.params['log_opt'].split("=")[1]
return self._diff_update_and_compare('log_opt', before, after)
def diffparam_memory(self):
before = str(self.info['hostconfig']['memory'])
after = self.params['memory']
return self._diff_update_and_compare('memory', before, after)
def diffparam_memory_swap(self):
# By default it's twice memory parameter
before = str(self.info['hostconfig']['memoryswap'])
after = self.params['memory_swap']
if (self.module.params['memory_swap'] is None and
self.params['memory'] != 0 and
self.params['memory'].isdigit()):
after = str(int(self.params['memory']) * 2)
return self._diff_update_and_compare('memory_swap', before, after)
def diffparam_memory_reservation(self):
before = str(self.info['hostconfig']['memoryreservation'])
after = self.params['memory_reservation']
return self._diff_update_and_compare('memory_reservation', before, after)
def diffparam_no_hosts(self):
before = not bool(self.info['hostspath'])
after = self.params['no_hosts']
return self._diff_update_and_compare('no_hosts', 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)
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']
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 = self.info['config']['stopsignal']
after = self.params['stop_signal']
return self._diff_update_and_compare('stop_signal', 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_uts(self):
before = self.info['hostconfig']['utsmode']
after = self.params['uts']
return self._diff_update_and_compare('uts', before, after)
def diffparam_volume(self):
before = self.info['mounts']
if before:
volumes = []
for m in before:
if m['type'] == 'volume':
volumes.append([m['name'], m['destination']])
else:
volumes.append([m['source'], m['destination']])
before = [":".join(v) for v in volumes]
# Ignore volumes option for idempotency
after = [":".join(v.split(":")[:2]) for v in self.params['volume']]
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('volume', before, after)
def diffparam_volumes_from(self):
before = self.info['hostconfig']['volumesfrom']
after = self.params['volumes_from']
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
else:
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):
"""If image is passed, ensure it exists, if not - pull it or fail.
@ -1205,6 +1796,9 @@ class PodmanContainer:
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):
@ -1214,9 +1808,17 @@ class PodmanContainer:
@property
def different(self):
"""Check if container is different."""
# TODO(sshnaidm): implement difference calculation between input vars
# and current container to understand if we need to recreate it
return True
diffcheck = PodmanContainerDiff(self.module, self.info, self.version)
is_different = diffcheck.is_different()
diffs = diffcheck.diff
if self.module._diff and is_different and diffs['before'] and diffs['after']:
self.diff['before'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['before'].items())]) + "\n"
self.diff['after'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['after'].items())]) + "\n"
return is_different
@property
def running(self):
@ -1234,6 +1836,13 @@ class PodmanContainer:
[self.module.params['executable'], b'container', b'inspect', self.name])
return json.loads(out)[0] if rc == 0 else {}
def _get_podman_version(self):
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.
@ -1241,19 +1850,25 @@ class PodmanContainer:
action {str} -- action to perform - start, create, stop, run,
delete
"""
b_command = PodmanModuleParams(action, self.module.params
b_command = PodmanModuleParams(action,
self.module.params,
self.version,
self.module,
).construct_command_from_params()
self.module.log("PODMAN-CONTAINER-DEBUG: " +
"%s" % " ".join([to_native(i) for i in b_command]))
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'container'] + b_command,
expand_user_and_vars=False)
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)
full_cmd = " ".join([self.module.params['executable']] +
[to_native(i) for i in b_command])
self.module.log("PODMAN-CONTAINER-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'container'] + b_command,
expand_user_and_vars=False)
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."""
@ -1326,11 +1941,15 @@ class PodmanManager:
changed {bool} -- whether any action was performed
(default: {True})
"""
facts = self.container.get_info()
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,
'ansible_facts': {'podman_container': facts}},
stdout=out, stderr=err)
'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']:
self.results.update({'podman_version': self.container.version})
self.module.exit_json(**self.results)
def make_started(self):
@ -1347,7 +1966,7 @@ class PodmanManager:
self.results['actions'].append('restarted %s' %
self.container.name)
self.update_container_result()
self.module.exit_json(**self.results)
self.update_container_result(changed=False)
elif not self.container.exists:
self.container.run()
self.results['actions'].append('started %s' % self.container.name)
@ -1387,7 +2006,7 @@ class PodmanManager:
self.results['actions'].append('deleted %s' % self.container.name)
self.results.update({'changed': True})
self.results.update({'container': {},
'ansible_facts': {'podman_container': {}}})
'podman_actions': self.container.actions})
self.module.exit_json(**self.results)
def execute(self):
@ -1411,6 +2030,7 @@ def main():
['no_hosts', 'etc_hosts'],
),
supports_check_mode=True,
)
# work on input vars
if module.params['state'] in ['started', 'present'] and \

View File

@ -71,17 +71,11 @@
assert:
that:
- image is changed
- image.ansible_facts is defined
- image.ansible_facts.podman_container is defined
- image.ansible_facts.podman_container['State']['Running']
- image.container is defined
- image.container['State']['Running']
- "'pulled image alpine:3.7' in image.actions"
- "'started container' in image.actions"
- image2 is changed
- image2.ansible_facts is defined
- image2.ansible_facts.podman_container is defined
- image2.ansible_facts.podman_container['State']['Running']
- image2.container is defined
- image2.container['State']['Running']
- "'pulled image alpine:3.7' not in image2.actions"
@ -118,9 +112,8 @@
assert:
that:
- recreated is changed
- recreated.ansible_facts is defined
- recreated.ansible_facts.podman_container is defined
- recreated.ansible_facts.podman_container['State']['Running']
- recreated.container is defined
- recreated.container['State']['Running']
- "'recreated container' in recreated.actions"
fail_msg: Force recreate test failed!
success_msg: Force recreate test passed!
@ -141,14 +134,12 @@
assert:
that:
- stopped is changed
- stopped.ansible_facts is defined
- stopped.ansible_facts.podman_container is defined
- not stopped.ansible_facts.podman_container['State']['Running']
- stopped.container is defined
- not stopped.container['State']['Running']
- "'stopped container' in stopped.actions"
- stopped_again is not changed
- stopped_again.ansible_facts is defined
- stopped_again.ansible_facts.podman_container is defined
- not stopped_again.ansible_facts.podman_container['State']['Running']
- stopped_again.container is defined
- not stopped_again.container['State']['Running']
- stopped_again.actions == []
fail_msg: Stopping container test failed!
success_msg: Stopping container test passed!
@ -169,14 +160,12 @@
assert:
that:
- deleted is changed
- deleted.ansible_facts is defined
- deleted.ansible_facts.podman_container is defined
- deleted.ansible_facts.podman_container == {}
- deleted.container is defined
- deleted.container == {}
- "'deleted container' in deleted.actions"
- deleted_again is not changed
- deleted_again.ansible_facts is defined
- deleted_again.ansible_facts.podman_container is defined
- deleted_again.ansible_facts.podman_container == {}
- deleted_again.container is defined
- deleted_again.container == {}
- deleted_again.actions == []
fail_msg: Deleting stopped container test failed!
success_msg: Deleting stopped container test passed!
@ -193,10 +182,9 @@
assert:
that:
- created is changed
- created.ansible_facts is defined
- created.ansible_facts.podman_container is defined
- created.ansible_facts.podman_container != {}
- not created.ansible_facts.podman_container['State']['Running']
- created.container is defined
- created.container != {}
- not created.container['State']['Running']
- "'created container' in created.actions"
fail_msg: "Creating stopped container test failed!"
success_msg: "Creating stopped container test passed!"
@ -218,9 +206,6 @@
assert:
that:
- started is changed
- started.ansible_facts is defined
- started.ansible_facts.podman_container is defined
- started.ansible_facts.podman_container['State']['Running']
- started.container is defined
- started.container['State']['Running']
- "'pulled image alpine:3.7' not in started.actions"
@ -241,14 +226,12 @@
assert:
that:
- deleted is changed
- deleted.ansible_facts is defined
- deleted.ansible_facts.podman_container is defined
- deleted.ansible_facts.podman_container == {}
- deleted.container is defined
- deleted.container == {}
- "'deleted container' in deleted.actions"
- deleted_again is not changed
- deleted_again.ansible_facts is defined
- deleted_again.ansible_facts.podman_container is defined
- deleted_again.ansible_facts.podman_container == {}
- deleted_again.container is defined
- deleted_again.container == {}
- deleted_again.actions == []
fail_msg: Deleting started container test failed!
success_msg: Deleting started container test passed!
@ -293,54 +276,112 @@
assert:
that:
- test is changed
- test.ansible_facts is defined
- test.ansible_facts.podman_container is defined
- test.ansible_facts.podman_container != {}
- test.ansible_facts.podman_container['State']['Running']
- test.container is defined
- test.container != {}
- test.container['State']['Running']
# test capabilities
- "'CAP_SYS_TIME' in test.ansible_facts.podman_container['BoundingCaps']"
- "'CAP_NET_ADMIN' in test.ansible_facts.podman_container['BoundingCaps']"
- "'CAP_SYS_TIME' in test.container['BoundingCaps']"
- "'CAP_NET_ADMIN' in test.container['BoundingCaps']"
# test annotations
- test.ansible_facts.podman_container['Config']['Annotations']['this'] is defined
- test.ansible_facts.podman_container['Config']['Annotations']['this'] == "annotation_value"
- test.container['Config']['Annotations']['this'] is defined
- test.container['Config']['Annotations']['this'] == "annotation_value"
# test DNS
- >-
(test.ansible_facts.podman_container['HostConfig']['Dns'] is defined and
test.ansible_facts.podman_container['HostConfig']['Dns'] == ['1.1.1.1', '8.8.4.4']) or
(test.ansible_facts.podman_container['HostConfig']['DNS'] is defined and
test.ansible_facts.podman_container['HostConfig']['DNS'] == ['1.1.1.1', '8.8.4.4'])
(test.container['HostConfig']['Dns'] is defined and
test.container['HostConfig']['Dns'] == ['1.1.1.1', '8.8.4.4']) or
(test.container['HostConfig']['DNS'] is defined and
test.container['HostConfig']['DNS'] == ['1.1.1.1', '8.8.4.4'])
# test ports
- test.ansible_facts.podman_container['NetworkSettings']['Ports']|length == 2
- test.container['NetworkSettings']['Ports']|length == 2
# test working dir
- test.ansible_facts.podman_container['Config']['WorkingDir'] == "/bin"
- test.container['Config']['WorkingDir'] == "/bin"
# test dns search
- >-
(test.ansible_facts.podman_container['HostConfig']['DnsSearch'] is defined and
test.ansible_facts.podman_container['HostConfig']['DnsSearch'] == ['example.com']) or
(test.ansible_facts.podman_container['HostConfig']['DNSSearch'] is defined and
test.ansible_facts.podman_container['HostConfig']['DNSSearch'] == ['example.com'])
(test.container['HostConfig']['DnsSearch'] is defined and
test.container['HostConfig']['DnsSearch'] == ['example.com']) or
(test.container['HostConfig']['DNSSearch'] is defined and
test.container['HostConfig']['DNSSearch'] == ['example.com'])
# test environment variables
- "'FOO=bar' in test.ansible_facts.podman_container['Config']['Env']"
- "'BAR=foo' in test.ansible_facts.podman_container['Config']['Env']"
- "'TEST=1' in test.ansible_facts.podman_container['Config']['Env']"
- "'BOOL=False' in test.ansible_facts.podman_container['Config']['Env']"
- "'FOO=bar' in test.container['Config']['Env']"
- "'BAR=foo' in test.container['Config']['Env']"
- "'TEST=1' in test.container['Config']['Env']"
- "'BOOL=False' in test.container['Config']['Env']"
# test labels
- test.ansible_facts.podman_container['Config']['Labels'] | length == 2
- test.ansible_facts.podman_container['Config']['Labels']['somelabel'] == "labelvalue"
- test.ansible_facts.podman_container['Config']['Labels']['otheralbe'] == "othervalue"
- test.container['Config']['Labels'] | length == 2
- test.container['Config']['Labels']['somelabel'] == "labelvalue"
- test.container['Config']['Labels']['otheralbe'] == "othervalue"
# test mounts
- >-
(test.ansible_facts.podman_container['Mounts'][0]['Destination'] is defined and
'/data' in test.ansible_facts.podman_container['Mounts'] | map(attribute='Destination') | list) or
(test.ansible_facts.podman_container['Mounts'][0]['destination'] is defined and
'/data' in test.ansible_facts.podman_container['Mounts'] | map(attribute='destination') | list)
(test.container['Mounts'][0]['Destination'] is defined and
'/data' in test.container['Mounts'] | map(attribute='Destination') | list) or
(test.container['Mounts'][0]['destination'] is defined and
'/data' in test.container['Mounts'] | map(attribute='destination') | list)
- >-
(test.ansible_facts.podman_container['Mounts'][0]['Source'] is defined and
'/tmp' in test.ansible_facts.podman_container['Mounts'] | map(attribute='Source') | list) or
(test.ansible_facts.podman_container['Mounts'][0]['source'] is defined and
'/tmp' in test.ansible_facts.podman_container['Mounts'] | map(attribute='source') | list)
(test.container['Mounts'][0]['Source'] is defined and
'/tmp' in test.container['Mounts'] | map(attribute='Source') | list) or
(test.container['Mounts'][0]['source'] is defined and
'/tmp' in test.container['Mounts'] | map(attribute='source') | list)
fail_msg: Parameters container test failed!
success_msg: Parameters container test passed!
- name: Check basic idempotency of running container
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
- name: Check basic idempotency of running container - run it again
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
register: idem
- name: Check that nothing was changed
assert:
that:
- not idem.changed
- name: Run changed container (with tty enabled)
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
tty: true
register: idem1
- name: Check that container is recreated when changed
assert:
that:
- idem1 is changed
- name: Run changed container without specifying an option, use defaults
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
register: idem2
- name: Check that container is recreated when changed to default value
assert:
that:
- idem2 is changed
- name: Remove container
podman_container:
name: testidem
state: absent
register: remove
- name: Check podman_actions
assert:
that:
- "'podman rm -f testidem' in remove.podman_actions"
always:
- name: Delete all container leftovers from tests
podman_container:

View File

@ -16,7 +16,24 @@
- name: Prepare
hosts: all
gather_facts: true
roles:
- role: test_deps
test_deps_extra_packages:
- podman
post_tasks:
- name: Check podman version
command: podman version
register: p_ver
changed_when: false
- name: Print podman version
debug:
msg: |
podman version:
{{ p_ver.stdout }}
Testing with ansible {{ ansible_version.full }}
with python {{ ansible_python_version }}
on host {{ ansible_distribution }} {{ ansible_distribution_version }}