Update podman_container with better idempotency
Add image inspection, idempotency for ulimits, workdir, user, other image overriden parameters. Add idempotency for volumes, published ports, others. Small improvements for docs. Change-Id: I01011e451827387f70d505242811e896a3a8ad4d
This commit is contained in:
parent
1804a43d86
commit
15818924b1
@ -25,17 +25,12 @@ import yaml
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.0',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: podman_container
|
||||
author:
|
||||
- "Sagi Shnaidman (@sshnaidm)"
|
||||
version_added: '2.9'
|
||||
version_added: '1.0.0'
|
||||
short_description: Manage podman containers
|
||||
notes: []
|
||||
description:
|
||||
@ -149,7 +144,7 @@ options:
|
||||
type: path
|
||||
cmd_args:
|
||||
description:
|
||||
- Any additionl command options you want to pass to podman command,
|
||||
- Any additional command options you want to pass to podman command,
|
||||
cmd_args - ['--other-param', 'value']
|
||||
Be aware module doesn't support idempotency if this is set.
|
||||
type: list
|
||||
@ -498,7 +493,7 @@ options:
|
||||
type: str
|
||||
pids_limit:
|
||||
description:
|
||||
- Tune the container's pids limit. Set -1 to have unlimited pids for the
|
||||
- Tune the container's PIDs limit. Set -1 to have unlimited PIDs for the
|
||||
container.
|
||||
type: str
|
||||
pod:
|
||||
@ -563,8 +558,9 @@ options:
|
||||
rootfs:
|
||||
description:
|
||||
- If true, the first argument refers to an exploded container on the file
|
||||
system. The dafault is false.
|
||||
system. The default is false.
|
||||
type: bool
|
||||
default: False
|
||||
security_opt:
|
||||
description:
|
||||
- Security Options. For example security_opt "seccomp=unconfined"
|
||||
@ -1272,9 +1268,10 @@ class PodmanModuleParams:
|
||||
|
||||
|
||||
class PodmanDefaults:
|
||||
def __init__(self, module, podman_version):
|
||||
def __init__(self, module, image_info, podman_version):
|
||||
self.module = module
|
||||
self.version = podman_version
|
||||
self.image_info = image_info
|
||||
self.defaults = {
|
||||
"blkio_weight": 0,
|
||||
"cgroups": "default",
|
||||
@ -1308,7 +1305,10 @@ class PodmanDefaults:
|
||||
"privileged": False,
|
||||
"rm": False,
|
||||
"security_opt": [],
|
||||
"stop_signal": self.image_info['config'].get('stopsignal', "15"),
|
||||
"tty": False,
|
||||
"user": self.image_info.get('user', ''),
|
||||
"workdir": self.image_info['config'].get('workingdir', '/'),
|
||||
"uts": "",
|
||||
}
|
||||
|
||||
@ -1318,27 +1318,32 @@ class PodmanDefaults:
|
||||
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"
|
||||
return self.defaults
|
||||
|
||||
|
||||
class PodmanContainerDiff:
|
||||
def __init__(self, module, info, podman_version):
|
||||
def __init__(self, module, info, image_info, podman_version):
|
||||
self.module = module
|
||||
self.version = podman_version
|
||||
self.default_dict = None
|
||||
self.info = yaml.safe_load(json.dumps(info).lower())
|
||||
self.image_info = yaml.safe_load(json.dumps(image_info).lower())
|
||||
self.params = self.defaultize()
|
||||
self.diff = {'before': {}, 'after': {}}
|
||||
self.non_idempotent = {
|
||||
'env_file',
|
||||
'env_file', # We can't get env vars from file to check
|
||||
'env_host',
|
||||
"ulimit", # Defaults depend on user and platform, impossible to guess
|
||||
}
|
||||
|
||||
def defaultize(self):
|
||||
params_with_defaults = {}
|
||||
self.default_dict = PodmanDefaults(
|
||||
self.module, self.version).default_dict()
|
||||
self.module, 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]
|
||||
@ -1574,7 +1579,7 @@ class PodmanContainerDiff:
|
||||
|
||||
def diffparam_label(self):
|
||||
before = self.info['config']['labels'] or {}
|
||||
after = before.copy()
|
||||
after = self.image_info.get('labels') or {}
|
||||
if self.params['label']:
|
||||
after.update({
|
||||
str(k).lower(): str(v).lower()
|
||||
@ -1654,6 +1659,27 @@ class PodmanContainerDiff:
|
||||
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):
|
||||
ports = self.info['hostconfig']['portbindings']
|
||||
before = [":".join([
|
||||
j[0]['hostip'],
|
||||
str(j[0]["hostport"]),
|
||||
i.replace('/tcp', '')
|
||||
]).strip(':') for i, j in ports.items()]
|
||||
after = self.params['publish'] or []
|
||||
if self.params['publish_all']:
|
||||
image_ports = self.image_info['config'].get('exposedports', {})
|
||||
if image_ports:
|
||||
after += list(image_ports.keys())
|
||||
after = [i.replace("/tcp", "") 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_rm(self):
|
||||
before = self.info['hostconfig']['autoremove']
|
||||
after = self.params['rm']
|
||||
@ -1666,10 +1692,46 @@ class PodmanContainerDiff:
|
||||
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']
|
||||
if after is None:
|
||||
after = before
|
||||
signals = {
|
||||
"sighup": "1",
|
||||
"sigint": "2",
|
||||
"sigquit": "3",
|
||||
"sigill": "4",
|
||||
"sigtrap": "5",
|
||||
"sigabrt": "6",
|
||||
"sigiot": "6",
|
||||
"sigbus": "7",
|
||||
"sigfpe": "8",
|
||||
"sigkill": "9",
|
||||
"sigusr1": "10",
|
||||
"sigsegv": "11",
|
||||
"sigusr2": "12",
|
||||
"sigpipe": "13",
|
||||
"sigalrm": "14",
|
||||
"sigterm": "15",
|
||||
"sigstkflt": "16",
|
||||
"sigchld": "17",
|
||||
"sigcont": "18",
|
||||
"sigstop": "19",
|
||||
"sigtstp": "20",
|
||||
"sigttin": "21",
|
||||
"sigttou": "22",
|
||||
"sigurg": "23",
|
||||
"sigxcpu": "24",
|
||||
"sigxfsz": "25",
|
||||
"sigvtalrm": "26",
|
||||
"sigprof": "27",
|
||||
"sigwinch": "28",
|
||||
"sigio": "29",
|
||||
"sigpwr": "30",
|
||||
"sigsys": "31"
|
||||
}
|
||||
before = str(self.info['config']['stopsignal'])
|
||||
if not before.isdigit():
|
||||
before = signals[before]
|
||||
after = str(self.params['stop_signal'])
|
||||
if not after.isdigit():
|
||||
after = signals[after]
|
||||
return self._diff_update_and_compare('stop_signal', before, after)
|
||||
|
||||
def diffparam_tty(self):
|
||||
@ -1680,10 +1742,34 @@ class PodmanContainerDiff:
|
||||
def diffparam_user(self):
|
||||
before = self.info['config']['user']
|
||||
after = self.params['user']
|
||||
if after is None:
|
||||
after = before
|
||||
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']
|
||||
@ -1692,20 +1778,30 @@ class PodmanContainerDiff:
|
||||
return self._diff_update_and_compare('uts', before, after)
|
||||
|
||||
def diffparam_volume(self):
|
||||
def clean_volume(x):
|
||||
'''Remove trailing and double slashes from volumes.'''
|
||||
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([m['name'], m['destination']])
|
||||
else:
|
||||
if m['type'] != 'volume':
|
||||
volumes.append([m['source'], m['destination']])
|
||||
elif m['type'] == 'volume':
|
||||
local_vols.append([m['name'], m['destination']])
|
||||
before = [":".join(v) for v in volumes]
|
||||
# Ignore volumes option for idempotency
|
||||
before_local_vols = [":".join(v) for v in local_vols]
|
||||
if self.params['volume'] is not None:
|
||||
after = [":".join(v.split(":")[:2]) for v in self.params['volume']]
|
||||
after = [":".join(
|
||||
[clean_volume(i) for i in v.split(":")[:2]]
|
||||
) for v in self.params['volume']]
|
||||
else:
|
||||
after = before
|
||||
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)
|
||||
|
||||
@ -1718,8 +1814,6 @@ class PodmanContainerDiff:
|
||||
def diffparam_workdir(self):
|
||||
before = self.info['config']['workingdir']
|
||||
after = self.params['workdir']
|
||||
if after is None:
|
||||
after = before
|
||||
return self._diff_update_and_compare('workdir', before, after)
|
||||
|
||||
def is_different(self):
|
||||
@ -1798,7 +1892,11 @@ class PodmanContainer:
|
||||
@property
|
||||
def different(self):
|
||||
"""Check if container is different."""
|
||||
diffcheck = PodmanContainerDiff(self.module, self.info, self.version)
|
||||
diffcheck = PodmanContainerDiff(
|
||||
self.module,
|
||||
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']:
|
||||
@ -1827,6 +1925,13 @@ class PodmanContainer:
|
||||
[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
|
||||
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(
|
||||
@ -2033,7 +2138,6 @@ def main():
|
||||
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
|
||||
mutually_exclusive=(
|
||||
['no_hosts', 'etc_hosts'],
|
||||
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user