community.docker/plugins/module_utils/module_container/base.py
2023-12-10 09:03:32 +01:00

1240 lines
39 KiB
Python

# Copyright (c) 2022 Felix Fontein <felix@fontein.de>
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import abc
import os
import re
import shlex
from functools import partial
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.common.text.formatters import human_to_bytes
from ansible.module_utils.six import string_types
from ansible_collections.community.docker.plugins.module_utils.util import (
clean_dict_booleans_for_docker_api,
compare_generic,
normalize_healthcheck,
omit_none_from_dict,
)
from ansible_collections.community.docker.plugins.module_utils._platform import (
compare_platform_strings,
)
from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import (
parse_env_file,
)
_DEFAULT_IP_REPLACEMENT_STRING = '[[DEFAULT_IP:iewahhaeB4Sae6Aen8IeShairoh4zeph7xaekoh8Geingunaesaeweiy3ooleiwi]]'
_MOUNT_OPTION_TYPES = dict(
volume_driver='volume',
volume_options='volume',
propagation='bind',
no_copy='volume',
labels='volume',
tmpfs_size='tmpfs',
tmpfs_mode='tmpfs',
)
def _get_ansible_type(type):
if type == 'set':
return 'list'
if type not in ('list', 'dict', 'bool', 'int', 'float', 'str'):
raise Exception('Invalid type "%s"' % (type, ))
return type
class Option(object):
def __init__(
self,
name,
type,
owner,
ansible_type=None,
elements=None,
ansible_elements=None,
ansible_suboptions=None,
ansible_aliases=None,
ansible_choices=None,
needs_no_suboptions=False,
default_comparison=None,
not_a_container_option=False,
not_an_ansible_option=False,
copy_comparison_from=None,
compare=None,
):
self.name = name
self.type = type
self.ansible_type = ansible_type or _get_ansible_type(type)
needs_elements = self.type in ('list', 'set')
needs_ansible_elements = self.ansible_type in ('list', )
if elements is not None and not needs_elements:
raise Exception('elements only allowed for lists/sets')
if elements is None and needs_elements:
raise Exception('elements required for lists/sets')
if ansible_elements is not None and not needs_ansible_elements:
raise Exception('Ansible elements only allowed for Ansible lists')
if (elements is None and ansible_elements is None) and needs_ansible_elements:
raise Exception('Ansible elements required for Ansible lists')
self.elements = elements if needs_elements else None
self.ansible_elements = (ansible_elements or _get_ansible_type(elements)) if needs_ansible_elements else None
needs_suboptions = (self.ansible_type == 'list' and self.ansible_elements == 'dict') or (self.ansible_type == 'dict')
if ansible_suboptions is not None and not needs_suboptions:
raise Exception('suboptions only allowed for Ansible lists with dicts, or Ansible dicts')
if ansible_suboptions is None and needs_suboptions and not needs_no_suboptions and not not_an_ansible_option:
raise Exception('suboptions required for Ansible lists with dicts, or Ansible dicts')
self.ansible_suboptions = ansible_suboptions if needs_suboptions else None
self.ansible_aliases = ansible_aliases or []
self.ansible_choices = ansible_choices
comparison_type = self.type
if comparison_type == 'set' and self.elements == 'dict':
comparison_type = 'set(dict)'
elif comparison_type not in ('set', 'list', 'dict'):
comparison_type = 'value'
self.comparison_type = comparison_type
if default_comparison is not None:
self.comparison = default_comparison
elif comparison_type in ('list', 'value'):
self.comparison = 'strict'
else:
self.comparison = 'allow_more_present'
self.not_a_container_option = not_a_container_option
self.not_an_ansible_option = not_an_ansible_option
self.copy_comparison_from = copy_comparison_from
self.compare = (
lambda param_value, container_value: compare(self, param_value, container_value)
) if compare else (
lambda param_value, container_value: compare_generic(param_value, container_value, self.comparison, self.comparison_type)
)
class OptionGroup(object):
def __init__(
self,
preprocess=None,
ansible_mutually_exclusive=None,
ansible_required_together=None,
ansible_required_one_of=None,
ansible_required_if=None,
ansible_required_by=None,
):
if preprocess is None:
def preprocess(module, values):
return values
self.preprocess = preprocess
self.options = []
self.all_options = []
self.engines = {}
self.ansible_mutually_exclusive = ansible_mutually_exclusive or []
self.ansible_required_together = ansible_required_together or []
self.ansible_required_one_of = ansible_required_one_of or []
self.ansible_required_if = ansible_required_if or []
self.ansible_required_by = ansible_required_by or {}
self.argument_spec = {}
def add_option(self, *args, **kwargs):
option = Option(*args, owner=self, **kwargs)
if not option.not_a_container_option:
self.options.append(option)
self.all_options.append(option)
if not option.not_an_ansible_option:
ansible_option = {
'type': option.ansible_type,
}
if option.ansible_elements is not None:
ansible_option['elements'] = option.ansible_elements
if option.ansible_suboptions is not None:
ansible_option['options'] = option.ansible_suboptions
if option.ansible_aliases:
ansible_option['aliases'] = option.ansible_aliases
if option.ansible_choices is not None:
ansible_option['choices'] = option.ansible_choices
self.argument_spec[option.name] = ansible_option
return self
def supports_engine(self, engine_name):
return engine_name in self.engines
def get_engine(self, engine_name):
return self.engines[engine_name]
def add_engine(self, engine_name, engine):
self.engines[engine_name] = engine
return self
class Engine(object):
min_api_version = None # string or None
min_api_version_obj = None # LooseVersion object or None
@abc.abstractmethod
def get_value(self, module, container, api_version, options, image, host_info):
pass
def compare_value(self, option, param_value, container_value):
return option.compare(param_value, container_value)
@abc.abstractmethod
def set_value(self, module, data, api_version, options, values):
pass
@abc.abstractmethod
def get_expected_values(self, module, client, api_version, options, image, values, host_info):
pass
@abc.abstractmethod
def ignore_mismatching_result(self, module, client, api_version, option, image, container_value, expected_value):
pass
@abc.abstractmethod
def preprocess_value(self, module, client, api_version, options, values):
pass
@abc.abstractmethod
def update_value(self, module, data, api_version, options, values):
pass
@abc.abstractmethod
def can_set_value(self, api_version):
pass
@abc.abstractmethod
def can_update_value(self, api_version):
pass
@abc.abstractmethod
def needs_container_image(self, values):
pass
@abc.abstractmethod
def needs_host_info(self, values):
pass
class EngineDriver(object):
name = None # string
@abc.abstractmethod
def setup(self, argument_spec, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
# Return (module, active_options, client)
pass
@abc.abstractmethod
def get_host_info(self, client):
pass
@abc.abstractmethod
def get_api_version(self, client):
pass
@abc.abstractmethod
def get_container_id(self, container):
pass
@abc.abstractmethod
def get_image_from_container(self, container):
pass
@abc.abstractmethod
def get_image_name_from_container(self, container):
pass
@abc.abstractmethod
def is_container_removing(self, container):
pass
@abc.abstractmethod
def is_container_running(self, container):
pass
@abc.abstractmethod
def is_container_paused(self, container):
pass
@abc.abstractmethod
def inspect_container_by_name(self, client, container_name):
pass
@abc.abstractmethod
def inspect_container_by_id(self, client, container_id):
pass
@abc.abstractmethod
def inspect_image_by_id(self, client, image_id):
pass
@abc.abstractmethod
def inspect_image_by_name(self, client, repository, tag):
pass
@abc.abstractmethod
def pull_image(self, client, repository, tag, platform=None):
pass
@abc.abstractmethod
def pause_container(self, client, container_id):
pass
@abc.abstractmethod
def unpause_container(self, client, container_id):
pass
@abc.abstractmethod
def disconnect_container_from_network(self, client, container_id, network_id):
pass
@abc.abstractmethod
def connect_container_to_network(self, client, container_id, network_id, parameters=None):
pass
@abc.abstractmethod
def create_container(self, client, container_name, create_parameters):
pass
@abc.abstractmethod
def start_container(self, client, container_id):
pass
@abc.abstractmethod
def wait_for_container(self, client, container_id, timeout=None):
pass
@abc.abstractmethod
def get_container_output(self, client, container_id):
pass
@abc.abstractmethod
def update_container(self, client, container_id, update_parameters):
pass
@abc.abstractmethod
def restart_container(self, client, container_id, timeout=None):
pass
@abc.abstractmethod
def kill_container(self, client, container_id, kill_signal=None):
pass
@abc.abstractmethod
def stop_container(self, client, container_id, timeout=None):
pass
@abc.abstractmethod
def remove_container(self, client, container_id, remove_volumes=False, link=False, force=False):
pass
@abc.abstractmethod
def run(self, runner, client):
pass
def _is_volume_permissions(mode):
for part in mode.split(','):
if part not in ('rw', 'ro', 'z', 'Z', 'consistent', 'delegated', 'cached', 'rprivate', 'private', 'rshared', 'shared', 'rslave', 'slave', 'nocopy'):
return False
return True
def _parse_port_range(range_or_port, module):
'''
Parses a string containing either a single port or a range of ports.
Returns a list of integers for each port in the list.
'''
if '-' in range_or_port:
try:
start, end = [int(port) for port in range_or_port.split('-')]
except Exception:
module.fail_json(msg='Invalid port range: "{0}"'.format(range_or_port))
if end < start:
module.fail_json(msg='Invalid port range: "{0}"'.format(range_or_port))
return list(range(start, end + 1))
else:
try:
return [int(range_or_port)]
except Exception:
module.fail_json(msg='Invalid port: "{0}"'.format(range_or_port))
def _split_colon_ipv6(text, module):
'''
Split string by ':', while keeping IPv6 addresses in square brackets in one component.
'''
if '[' not in text:
return text.split(':')
start = 0
result = []
while start < len(text):
i = text.find('[', start)
if i < 0:
result.extend(text[start:].split(':'))
break
j = text.find(']', i)
if j < 0:
module.fail_json(msg='Cannot find closing "]" in input "{0}" for opening "[" at index {1}!'.format(text, i + 1))
result.extend(text[start:i].split(':'))
k = text.find(':', j)
if k < 0:
result[-1] += text[i:]
start = len(text)
else:
result[-1] += text[i:k]
if k == len(text):
result.append('')
break
start = k + 1
return result
def _preprocess_command(module, values):
if 'command' not in values:
return values
value = values['command']
if module.params['command_handling'] == 'correct':
if value is not None:
if not isinstance(value, list):
# convert from str to list
value = shlex.split(to_text(value, errors='surrogate_or_strict'))
value = [to_text(x, errors='surrogate_or_strict') for x in value]
elif value:
# convert from list to str
if isinstance(value, list):
value = shlex.split(' '.join([to_text(x, errors='surrogate_or_strict') for x in value]))
value = [to_text(x, errors='surrogate_or_strict') for x in value]
else:
value = shlex.split(to_text(value, errors='surrogate_or_strict'))
value = [to_text(x, errors='surrogate_or_strict') for x in value]
else:
return {}
return {
'command': value,
}
def _preprocess_entrypoint(module, values):
if 'entrypoint' not in values:
return values
value = values['entrypoint']
if module.params['command_handling'] == 'correct':
if value is not None:
value = [to_text(x, errors='surrogate_or_strict') for x in value]
elif value:
# convert from list to str.
value = shlex.split(' '.join([to_text(x, errors='surrogate_or_strict') for x in value]))
value = [to_text(x, errors='surrogate_or_strict') for x in value]
else:
return {}
return {
'entrypoint': value,
}
def _preprocess_env(module, values):
if not values:
return {}
final_env = {}
if 'env_file' in values:
parsed_env_file = parse_env_file(values['env_file'])
for name, value in parsed_env_file.items():
final_env[name] = to_text(value, errors='surrogate_or_strict')
if 'env' in values:
for name, value in values['env'].items():
if not isinstance(value, string_types):
module.fail_json(msg='Non-string value found for env option. Ambiguous env options must be '
'wrapped in quotes to avoid them being interpreted. Key: %s' % (name, ))
final_env[name] = to_text(value, errors='surrogate_or_strict')
formatted_env = []
for key, value in final_env.items():
formatted_env.append('%s=%s' % (key, value))
return {
'env': formatted_env,
}
def _preprocess_healthcheck(module, values):
if not values:
return {}
return {
'healthcheck': normalize_healthcheck(values['healthcheck'], normalize_test=False),
}
def _preprocess_convert_to_bytes(module, values, name, unlimited_value=None):
if name not in values:
return values
try:
value = values[name]
if unlimited_value is not None and value in ('unlimited', str(unlimited_value)):
value = unlimited_value
else:
value = human_to_bytes(value)
values[name] = value
return values
except ValueError as exc:
module.fail_json(msg='Failed to convert %s to bytes: %s' % (name, to_native(exc)))
def _preprocess_mac_address(module, values):
if 'mac_address' not in values:
return values
return {
'mac_address': values['mac_address'].replace('-', ':'),
}
def _preprocess_networks(module, values):
if module.params['networks_cli_compatible'] is True and values.get('networks') and 'network_mode' not in values:
# Same behavior as Docker CLI: if networks are specified, use the name of the first network as the value for network_mode
# (assuming no explicit value is specified for network_mode)
values['network_mode'] = values['networks'][0]['name']
if 'networks' in values:
for network in values['networks']:
if network['links']:
parsed_links = []
for link in network['links']:
parsed_link = link.split(':', 1)
if len(parsed_link) == 1:
parsed_link = (link, link)
parsed_links.append(tuple(parsed_link))
network['links'] = parsed_links
return values
def _preprocess_sysctls(module, values):
if 'sysctls' in values:
for key, value in values['sysctls'].items():
values['sysctls'][key] = to_text(value, errors='surrogate_or_strict')
return values
def _preprocess_tmpfs(module, values):
if 'tmpfs' not in values:
return values
result = {}
for tmpfs_spec in values['tmpfs']:
split_spec = tmpfs_spec.split(":", 1)
if len(split_spec) > 1:
result[split_spec[0]] = split_spec[1]
else:
result[split_spec[0]] = ""
return {
'tmpfs': result
}
def _preprocess_ulimits(module, values):
if 'ulimits' not in values:
return values
result = []
for limit in values['ulimits']:
limits = dict()
pieces = limit.split(':')
if len(pieces) >= 2:
limits['Name'] = pieces[0]
limits['Soft'] = int(pieces[1])
limits['Hard'] = int(pieces[1])
if len(pieces) == 3:
limits['Hard'] = int(pieces[2])
result.append(limits)
return {
'ulimits': result,
}
def _preprocess_mounts(module, values):
last = dict()
def check_collision(t, name):
if t in last:
if name == last[t]:
module.fail_json(msg='The mount point "{0}" appears twice in the {1} option'.format(t, name))
else:
module.fail_json(msg='The mount point "{0}" appears both in the {1} and {2} option'.format(t, name, last[t]))
last[t] = name
if 'mounts' in values:
mounts = []
for mount in values['mounts']:
target = mount['target']
mount_type = mount['type']
check_collision(target, 'mounts')
mount_dict = dict(mount)
# Sanity checks
if mount['source'] is None and mount_type not in ('tmpfs', 'volume'):
module.fail_json(msg='source must be specified for mount "{0}" of type "{1}"'.format(target, mount_type))
for option, req_mount_type in _MOUNT_OPTION_TYPES.items():
if mount[option] is not None and mount_type != req_mount_type:
module.fail_json(
msg='{0} cannot be specified for mount "{1}" of type "{2}" (needs type "{3}")'.format(option, target, mount_type, req_mount_type)
)
# Streamline options
volume_options = mount_dict.pop('volume_options')
if mount_dict['volume_driver'] and volume_options:
mount_dict['volume_options'] = clean_dict_booleans_for_docker_api(volume_options)
if mount_dict['labels']:
mount_dict['labels'] = clean_dict_booleans_for_docker_api(mount_dict['labels'])
if mount_dict['tmpfs_size'] is not None:
try:
mount_dict['tmpfs_size'] = human_to_bytes(mount_dict['tmpfs_size'])
except ValueError as exc:
module.fail_json(msg='Failed to convert tmpfs_size of mount "{0}" to bytes: {1}'.format(target, to_native(exc)))
if mount_dict['tmpfs_mode'] is not None:
try:
mount_dict['tmpfs_mode'] = int(mount_dict['tmpfs_mode'], 8)
except Exception as dummy:
module.fail_json(msg='tmp_fs mode of mount "{0}" is not an octal string!'.format(target))
# Add result to list
mounts.append(omit_none_from_dict(mount_dict))
values['mounts'] = mounts
if 'volumes' in values:
new_vols = []
for vol in values['volumes']:
parts = vol.split(':')
if ':' in vol:
if len(parts) == 3:
host, container, mode = parts
if not _is_volume_permissions(mode):
module.fail_json(msg='Found invalid volumes mode: {0}'.format(mode))
if re.match(r'[.~]', host):
host = os.path.abspath(os.path.expanduser(host))
check_collision(container, 'volumes')
new_vols.append("%s:%s:%s" % (host, container, mode))
continue
elif len(parts) == 2:
if not _is_volume_permissions(parts[1]) and re.match(r'[.~]', parts[0]):
host = os.path.abspath(os.path.expanduser(parts[0]))
check_collision(parts[1], 'volumes')
new_vols.append("%s:%s:rw" % (host, parts[1]))
continue
check_collision(parts[min(1, len(parts) - 1)], 'volumes')
new_vols.append(vol)
values['volumes'] = new_vols
new_binds = []
for vol in new_vols:
host = None
if ':' in vol:
parts = vol.split(':')
if len(parts) == 3:
host, container, mode = parts
if not _is_volume_permissions(mode):
module.fail_json(msg='Found invalid volumes mode: {0}'.format(mode))
elif len(parts) == 2:
if not _is_volume_permissions(parts[1]):
host, container, mode = (parts + ['rw'])
if host is not None:
new_binds.append('%s:%s:%s' % (host, container, mode))
values['volume_binds'] = new_binds
return values
def _preprocess_log(module, values):
result = {}
if 'log_driver' not in values:
return result
result['log_driver'] = values['log_driver']
if 'log_options' in values:
options = {}
for k, v in values['log_options'].items():
if not isinstance(v, string_types):
module.warn(
"Non-string value found for log_options option '%s'. The value is automatically converted to '%s'. "
"If this is not correct, or you want to avoid such warnings, please quote the value." % (
k, to_text(v, errors='surrogate_or_strict'))
)
v = to_text(v, errors='surrogate_or_strict')
options[k] = v
result['log_options'] = options
return result
def _preprocess_ports(module, values):
if 'published_ports' in values:
if 'all' in values['published_ports']:
module.fail_json(
msg='Specifying "all" in published_ports is no longer allowed. Set publish_all_ports to "true" instead '
'to randomly assign port mappings for those not specified by published_ports.')
binds = {}
for port in values['published_ports']:
parts = _split_colon_ipv6(to_text(port, errors='surrogate_or_strict'), module)
container_port = parts[-1]
protocol = ''
if '/' in container_port:
container_port, protocol = parts[-1].split('/')
container_ports = _parse_port_range(container_port, module)
p_len = len(parts)
if p_len == 1:
port_binds = len(container_ports) * [(_DEFAULT_IP_REPLACEMENT_STRING, )]
elif p_len == 2:
if len(container_ports) == 1:
port_binds = [(_DEFAULT_IP_REPLACEMENT_STRING, parts[0])]
else:
port_binds = [(_DEFAULT_IP_REPLACEMENT_STRING, port) for port in _parse_port_range(parts[0], module)]
elif p_len == 3:
# We only allow IPv4 and IPv6 addresses for the bind address
ipaddr = parts[0]
if not re.match(r'^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$', parts[0]) and not re.match(r'^\[[0-9a-fA-F:]+(?:|%[^\]/]+)\]$', ipaddr):
module.fail_json(
msg='Bind addresses for published ports must be IPv4 or IPv6 addresses, not hostnames. '
'Use the dig lookup to resolve hostnames. (Found hostname: {0})'.format(ipaddr)
)
if re.match(r'^\[[0-9a-fA-F:]+\]$', ipaddr):
ipaddr = ipaddr[1:-1]
if parts[1]:
if len(container_ports) == 1:
port_binds = [(ipaddr, parts[1])]
else:
port_binds = [(ipaddr, port) for port in _parse_port_range(parts[1], module)]
else:
port_binds = len(container_ports) * [(ipaddr,)]
else:
module.fail_json(
msg='Invalid port description "%s" - expected 1 to 3 colon-separated parts, but got %d. '
'Maybe you forgot to use square brackets ([...]) around an IPv6 address?' % (port, p_len)
)
for bind, container_port in zip(port_binds, container_ports):
idx = '{0}/{1}'.format(container_port, protocol) if protocol else container_port
if idx in binds:
old_bind = binds[idx]
if isinstance(old_bind, list):
old_bind.append(bind)
else:
binds[idx] = [old_bind, bind]
else:
binds[idx] = bind
values['published_ports'] = binds
exposed = []
if 'exposed_ports' in values:
for port in values['exposed_ports']:
port = to_text(port, errors='surrogate_or_strict').strip()
protocol = 'tcp'
match = re.search(r'(/.+$)', port)
if match:
protocol = match.group(1).replace('/', '')
port = re.sub(r'/.+$', '', port)
exposed.append((port, protocol))
if 'published_ports' in values:
# Any published port should also be exposed
for publish_port in values['published_ports']:
match = False
if isinstance(publish_port, string_types) and '/' in publish_port:
port, protocol = publish_port.split('/')
port = int(port)
else:
protocol = 'tcp'
port = int(publish_port)
for exposed_port in exposed:
if exposed_port[1] != protocol:
continue
if isinstance(exposed_port[0], string_types) and '-' in exposed_port[0]:
start_port, end_port = exposed_port[0].split('-')
if int(start_port) <= port <= int(end_port):
match = True
elif exposed_port[0] == port:
match = True
if not match:
exposed.append((port, protocol))
values['ports'] = exposed
return values
def _compare_platform(option, param_value, container_value):
if option.comparison == 'ignore':
return True
try:
return compare_platform_strings(param_value, container_value)
except ValueError:
return param_value == container_value
OPTION_AUTO_REMOVE = (
OptionGroup()
.add_option('auto_remove', type='bool')
)
OPTION_BLKIO_WEIGHT = (
OptionGroup()
.add_option('blkio_weight', type='int')
)
OPTION_CAPABILITIES = (
OptionGroup()
.add_option('capabilities', type='set', elements='str')
)
OPTION_CAP_DROP = (
OptionGroup()
.add_option('cap_drop', type='set', elements='str')
)
OPTION_CGROUP_NS_MODE = (
OptionGroup()
.add_option('cgroupns_mode', type='str', ansible_choices=['private', 'host'])
)
OPTION_CGROUP_PARENT = (
OptionGroup()
.add_option('cgroup_parent', type='str')
)
OPTION_COMMAND = (
OptionGroup(preprocess=_preprocess_command)
.add_option('command', type='list', elements='str', ansible_type='raw')
)
OPTION_CPU_PERIOD = (
OptionGroup()
.add_option('cpu_period', type='int')
)
OPTION_CPU_QUOTA = (
OptionGroup()
.add_option('cpu_quota', type='int')
)
OPTION_CPUSET_CPUS = (
OptionGroup()
.add_option('cpuset_cpus', type='str')
)
OPTION_CPUSET_MEMS = (
OptionGroup()
.add_option('cpuset_mems', type='str')
)
OPTION_CPU_SHARES = (
OptionGroup()
.add_option('cpu_shares', type='int')
)
OPTION_ENTRYPOINT = (
OptionGroup(preprocess=_preprocess_entrypoint)
.add_option('entrypoint', type='list', elements='str')
)
OPTION_CPUS = (
OptionGroup()
.add_option('cpus', type='int', ansible_type='float')
)
OPTION_DETACH_INTERACTIVE = (
OptionGroup()
.add_option('detach', type='bool')
.add_option('interactive', type='bool')
)
OPTION_DEVICES = (
OptionGroup()
.add_option('devices', type='set', elements='dict', ansible_elements='str')
)
OPTION_DEVICE_READ_BPS = (
OptionGroup()
.add_option('device_read_bps', type='set', elements='dict', ansible_suboptions=dict(
path=dict(required=True, type='str'),
rate=dict(required=True, type='str'),
))
)
OPTION_DEVICE_WRITE_BPS = (
OptionGroup()
.add_option('device_write_bps', type='set', elements='dict', ansible_suboptions=dict(
path=dict(required=True, type='str'),
rate=dict(required=True, type='str'),
))
)
OPTION_DEVICE_READ_IOPS = (
OptionGroup()
.add_option('device_read_iops', type='set', elements='dict', ansible_suboptions=dict(
path=dict(required=True, type='str'),
rate=dict(required=True, type='int'),
))
)
OPTION_DEVICE_WRITE_IOPS = (
OptionGroup()
.add_option('device_write_iops', type='set', elements='dict', ansible_suboptions=dict(
path=dict(required=True, type='str'),
rate=dict(required=True, type='int'),
))
)
OPTION_DEVICE_REQUESTS = (
OptionGroup()
.add_option('device_requests', type='set', elements='dict', ansible_suboptions=dict(
capabilities=dict(type='list', elements='list'),
count=dict(type='int'),
device_ids=dict(type='list', elements='str'),
driver=dict(type='str'),
options=dict(type='dict'),
))
)
OPTION_DNS_SERVERS = (
OptionGroup()
.add_option('dns_servers', type='list', elements='str')
)
OPTION_DNS_OPTS = (
OptionGroup()
.add_option('dns_opts', type='set', elements='str')
)
OPTION_DNS_SEARCH_DOMAINS = (
OptionGroup()
.add_option('dns_search_domains', type='list', elements='str')
)
OPTION_DOMAINNAME = (
OptionGroup()
.add_option('domainname', type='str')
)
OPTION_ENVIRONMENT = (
OptionGroup(preprocess=_preprocess_env)
.add_option('env', type='set', ansible_type='dict', elements='str', needs_no_suboptions=True)
.add_option('env_file', type='set', ansible_type='path', elements='str', not_a_container_option=True)
)
OPTION_ETC_HOSTS = (
OptionGroup()
.add_option('etc_hosts', type='set', ansible_type='dict', elements='str', needs_no_suboptions=True)
)
OPTION_GROUPS = (
OptionGroup()
.add_option('groups', type='set', elements='str')
)
OPTION_HEALTHCHECK = (
OptionGroup(preprocess=_preprocess_healthcheck)
.add_option('healthcheck', type='dict', ansible_suboptions=dict(
test=dict(type='raw'),
interval=dict(type='str'),
timeout=dict(type='str'),
start_period=dict(type='str'),
retries=dict(type='int'),
))
)
OPTION_HOSTNAME = (
OptionGroup()
.add_option('hostname', type='str')
)
OPTION_IMAGE = (
OptionGroup(preprocess=_preprocess_networks)
.add_option('image', type='str')
)
OPTION_INIT = (
OptionGroup()
.add_option('init', type='bool')
)
OPTION_IPC_MODE = (
OptionGroup()
.add_option('ipc_mode', type='str')
)
OPTION_KERNEL_MEMORY = (
OptionGroup(preprocess=partial(_preprocess_convert_to_bytes, name='kernel_memory'))
.add_option('kernel_memory', type='int', ansible_type='str')
)
OPTION_LABELS = (
OptionGroup()
.add_option('labels', type='dict', needs_no_suboptions=True)
)
OPTION_LINKS = (
OptionGroup()
.add_option('links', type='set', elements='list', ansible_elements='str')
)
OPTION_LOG_DRIVER_OPTIONS = (
OptionGroup(preprocess=_preprocess_log, ansible_required_by={'log_options': ['log_driver']})
.add_option('log_driver', type='str')
.add_option('log_options', type='dict', ansible_aliases=['log_opt'], needs_no_suboptions=True)
)
OPTION_MAC_ADDRESS = (
OptionGroup(preprocess=_preprocess_mac_address)
.add_option('mac_address', type='str')
)
OPTION_MEMORY = (
OptionGroup(preprocess=partial(_preprocess_convert_to_bytes, name='memory'))
.add_option('memory', type='int', ansible_type='str')
)
OPTION_MEMORY_RESERVATION = (
OptionGroup(preprocess=partial(_preprocess_convert_to_bytes, name='memory_reservation'))
.add_option('memory_reservation', type='int', ansible_type='str')
)
OPTION_MEMORY_SWAP = (
OptionGroup(preprocess=partial(_preprocess_convert_to_bytes, name='memory_swap', unlimited_value=-1))
.add_option('memory_swap', type='int', ansible_type='str')
)
OPTION_MEMORY_SWAPPINESS = (
OptionGroup()
.add_option('memory_swappiness', type='int')
)
OPTION_STOP_TIMEOUT = (
OptionGroup()
.add_option('stop_timeout', type='int', default_comparison='ignore')
)
OPTION_NETWORK = (
OptionGroup(preprocess=_preprocess_networks)
.add_option('network_mode', type='str')
.add_option('networks', type='set', elements='dict', ansible_suboptions=dict(
name=dict(type='str', required=True),
ipv4_address=dict(type='str'),
ipv6_address=dict(type='str'),
aliases=dict(type='list', elements='str'),
links=dict(type='list', elements='str'),
))
)
OPTION_OOM_KILLER = (
OptionGroup()
.add_option('oom_killer', type='bool')
)
OPTION_OOM_SCORE_ADJ = (
OptionGroup()
.add_option('oom_score_adj', type='int')
)
OPTION_PID_MODE = (
OptionGroup()
.add_option('pid_mode', type='str')
)
OPTION_PIDS_LIMIT = (
OptionGroup()
.add_option('pids_limit', type='int')
)
OPTION_PLATFORM = (
OptionGroup()
.add_option('platform', type='str', compare=_compare_platform)
)
OPTION_PRIVILEGED = (
OptionGroup()
.add_option('privileged', type='bool')
)
OPTION_READ_ONLY = (
OptionGroup()
.add_option('read_only', type='bool')
)
OPTION_RESTART_POLICY = (
OptionGroup(ansible_required_by={'restart_retries': ['restart_policy']})
.add_option('restart_policy', type='str', ansible_choices=['no', 'on-failure', 'always', 'unless-stopped'])
.add_option('restart_retries', type='int')
)
OPTION_RUNTIME = (
OptionGroup()
.add_option('runtime', type='str')
)
OPTION_SECURITY_OPTS = (
OptionGroup()
.add_option('security_opts', type='set', elements='str')
)
OPTION_SHM_SIZE = (
OptionGroup(preprocess=partial(_preprocess_convert_to_bytes, name='shm_size'))
.add_option('shm_size', type='int', ansible_type='str')
)
OPTION_STOP_SIGNAL = (
OptionGroup()
.add_option('stop_signal', type='str')
)
OPTION_STORAGE_OPTS = (
OptionGroup()
.add_option('storage_opts', type='dict', needs_no_suboptions=True)
)
OPTION_SYSCTLS = (
OptionGroup(preprocess=_preprocess_sysctls)
.add_option('sysctls', type='dict', needs_no_suboptions=True)
)
OPTION_TMPFS = (
OptionGroup(preprocess=_preprocess_tmpfs)
.add_option('tmpfs', type='dict', ansible_type='list', ansible_elements='str')
)
OPTION_TTY = (
OptionGroup()
.add_option('tty', type='bool')
)
OPTION_ULIMITS = (
OptionGroup(preprocess=_preprocess_ulimits)
.add_option('ulimits', type='set', elements='dict', ansible_elements='str')
)
OPTION_USER = (
OptionGroup()
.add_option('user', type='str')
)
OPTION_USERNS_MODE = (
OptionGroup()
.add_option('userns_mode', type='str')
)
OPTION_UTS = (
OptionGroup()
.add_option('uts', type='str')
)
OPTION_VOLUME_DRIVER = (
OptionGroup()
.add_option('volume_driver', type='str')
)
OPTION_VOLUMES_FROM = (
OptionGroup()
.add_option('volumes_from', type='set', elements='str')
)
OPTION_WORKING_DIR = (
OptionGroup()
.add_option('working_dir', type='str')
)
OPTION_MOUNTS_VOLUMES = (
OptionGroup(preprocess=_preprocess_mounts)
.add_option('mounts', type='set', elements='dict', ansible_suboptions=dict(
target=dict(type='str', required=True),
source=dict(type='str'),
type=dict(type='str', choices=['bind', 'volume', 'tmpfs', 'npipe'], default='volume'),
read_only=dict(type='bool'),
consistency=dict(type='str', choices=['default', 'consistent', 'cached', 'delegated']),
propagation=dict(type='str', choices=['private', 'rprivate', 'shared', 'rshared', 'slave', 'rslave']),
no_copy=dict(type='bool'),
labels=dict(type='dict'),
volume_driver=dict(type='str'),
volume_options=dict(type='dict'),
tmpfs_size=dict(type='str'),
tmpfs_mode=dict(type='str'),
))
.add_option('volumes', type='set', elements='str')
.add_option('volume_binds', type='set', elements='str', not_an_ansible_option=True, copy_comparison_from='volumes')
)
OPTION_PORTS = (
OptionGroup(preprocess=_preprocess_ports)
.add_option('exposed_ports', type='set', elements='str', ansible_aliases=['exposed', 'expose'])
.add_option('publish_all_ports', type='bool')
.add_option('published_ports', type='dict', ansible_type='list', ansible_elements='str', ansible_aliases=['ports'])
.add_option('ports', type='set', elements='str', not_an_ansible_option=True, default_comparison='ignore')
)
OPTIONS = [
OPTION_AUTO_REMOVE,
OPTION_BLKIO_WEIGHT,
OPTION_CAPABILITIES,
OPTION_CAP_DROP,
OPTION_CGROUP_NS_MODE,
OPTION_CGROUP_PARENT,
OPTION_COMMAND,
OPTION_CPU_PERIOD,
OPTION_CPU_QUOTA,
OPTION_CPUSET_CPUS,
OPTION_CPUSET_MEMS,
OPTION_CPU_SHARES,
OPTION_ENTRYPOINT,
OPTION_CPUS,
OPTION_DETACH_INTERACTIVE,
OPTION_DEVICES,
OPTION_DEVICE_READ_BPS,
OPTION_DEVICE_WRITE_BPS,
OPTION_DEVICE_READ_IOPS,
OPTION_DEVICE_WRITE_IOPS,
OPTION_DEVICE_REQUESTS,
OPTION_DNS_SERVERS,
OPTION_DNS_OPTS,
OPTION_DNS_SEARCH_DOMAINS,
OPTION_DOMAINNAME,
OPTION_ENVIRONMENT,
OPTION_ETC_HOSTS,
OPTION_GROUPS,
OPTION_HEALTHCHECK,
OPTION_HOSTNAME,
OPTION_IMAGE,
OPTION_INIT,
OPTION_IPC_MODE,
OPTION_KERNEL_MEMORY,
OPTION_LABELS,
OPTION_LINKS,
OPTION_LOG_DRIVER_OPTIONS,
OPTION_MAC_ADDRESS,
OPTION_MEMORY,
OPTION_MEMORY_RESERVATION,
OPTION_MEMORY_SWAP,
OPTION_MEMORY_SWAPPINESS,
OPTION_STOP_TIMEOUT,
OPTION_NETWORK,
OPTION_OOM_KILLER,
OPTION_OOM_SCORE_ADJ,
OPTION_PID_MODE,
OPTION_PIDS_LIMIT,
OPTION_PLATFORM,
OPTION_PRIVILEGED,
OPTION_READ_ONLY,
OPTION_RESTART_POLICY,
OPTION_RUNTIME,
OPTION_SECURITY_OPTS,
OPTION_SHM_SIZE,
OPTION_STOP_SIGNAL,
OPTION_STORAGE_OPTS,
OPTION_SYSCTLS,
OPTION_TMPFS,
OPTION_TTY,
OPTION_ULIMITS,
OPTION_USER,
OPTION_USERNS_MODE,
OPTION_UTS,
OPTION_VOLUME_DRIVER,
OPTION_VOLUMES_FROM,
OPTION_WORKING_DIR,
OPTION_MOUNTS_VOLUMES,
OPTION_PORTS,
]