Created
May 18, 2018 19:13
-
-
Save jwitko/6f6e9723ad74b56d2b3b3e31f5550e4e to your computer and use it in GitHub Desktop.
ansible docker_swarm_service module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# | |
# (c) 2017, Dario Zanzico (git@dariozanzico.com) | |
# 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 | |
ANSIBLE_METADATA = {'status': ['preview'], | |
'supported_by': 'community', | |
'metadata_version': '1.1'} | |
DOCUMENTATION = ''' | |
--- | |
module: docker_swarm_service | |
author: "Dario Zanzico (@dariko)" | |
short_description: docker swarm service | |
description: | | |
Manage docker services. Allows live altering of already defined services | |
(see examples) | |
version_added: "2.5" | |
options: | |
name: | |
required: true | |
description: | |
- Service name | |
image: | |
required: true | |
description: | |
- Service image path and tag. | |
Maps docker service IMAGE parameter. | |
state: | |
required: true | |
description: | |
- Service state. | |
choices: | |
- present | |
- absent | |
args: | |
required: false | |
default: [] | |
description: | |
- List comprised of the command and the arguments to be run inside | |
- the container | |
constraints: | |
required: false | |
default: [] | |
description: | |
- List of the service constraints. | |
- Maps docker service --constraint option. | |
hostname: | |
required: false | |
default: "" | |
description: | |
- Container hostname | |
- Maps docker service --hostname option. | |
- Requires api_version >= 1.25 | |
tty: | |
required: false | |
default: False | |
description: | |
- Allocate a pseudo-TTY | |
- Maps docker service --tty option. | |
- Requires api_version >= 1.25 | |
dns: | |
required: false | |
default: [] | |
description: | |
- List of custom DNS servers. | |
- Maps docker service --dns option. | |
- Requires api_version >= 1.25 | |
dns_search: | |
required: false | |
default: [] | |
description: | |
- List of custom DNS search domains. | |
- Maps docker service --dns-search option. | |
- Requires api_version >= 1.25 | |
dns_options: | |
required: false | |
default: [] | |
description: | |
- List of custom DNS options. | |
- Maps docker service --dns-option option. | |
- Requires api_version >= 1.25 | |
labels: | |
required: false | |
description: | |
- List of the service labels. | |
- Maps docker service --label option. | |
container_labels: | |
required: false | |
description: | |
- List of the service containers labels. | |
- Maps docker service --container-label option. | |
default: [] | |
endpoint_mode: | |
required: false | |
description: | |
- Service endpoint mode. | |
- Maps docker service --endpoint-mode option. | |
default: vip | |
choices: | |
- vip | |
- dnsrr | |
env: | |
required: false | |
default: [] | |
description: | |
- List of the service environment variables. | |
- Maps docker service --env option. | |
limit_cpu: | |
required: false | |
default: 0.000 | |
description: | |
- Service CPU limit. 0 equals no limit. | |
- Maps docker service --limit-cpu option. | |
reserve_cpu: | |
required: false | |
default: 0.000 | |
description: | |
- Service CPU reservation. 0 equals no reservation. | |
- Maps docker service --reserve-cpu option. | |
limit_memory: | |
required: false | |
default: 0 | |
description: | |
- Service memory limit in MB. 0 equals no limit. | |
- Maps docker service --limit-memory option. | |
reserve_memory: | |
required: false | |
default: 0 | |
description: | |
- Service memory reservation in MB. 0 equals no reservation. | |
- Maps docker service --reserve-memory option. | |
mode: | |
required: false | |
default: replicated | |
description: | |
- Service replication mode. | |
- Maps docker service --mode option. | |
mounts: | |
required: false | |
description: | |
- List of dictionaries describing the service mounts. | |
- Every item must be a dictionary exposing the keys source, target, type (defaults to 'bind'), readonly (defaults to false) | |
- Maps docker service --mount option. | |
default: [] | |
networks: | |
required: false | |
default: [] | |
description: | |
- List of the service networks names. | |
- Maps docker service --network option. | |
publish: | |
default: [] | |
required: false | |
description: | |
- List of dictionaries describing the service published ports. | |
- Every item must be a dictionary exposing the keys published_port, target_port, protocol (defaults to 'tcp'), mode <ingress|host>, default to ingress. | |
- Only used with api_version >= 1.25 | |
replicas: | |
required: false | |
default: -1 | |
description: | |
- Number of containers instantiated in the service. Valid only if ``mode=='replicated'``. | |
- If set to -1, and service is not present, service replicas will be set to 1. | |
- If set to -1, and service is present, service replicas will be unchanged. | |
- Maps docker service --replicas option. | |
restart_policy: | |
required: false | |
default: none | |
description: | |
- Restart condition of the service. | |
- Maps docker service --restart-condition option. | |
choices: | |
- none | |
- on-failure | |
- any | |
restart_policy_attempts: | |
required: false | |
default: 0 | |
description: | |
- Maximum number of service restarts. | |
- Maps docker service --restart-max-attempts option. | |
restart_policy_delay: | |
required: false | |
default: 0 | |
description: | |
- Delay between restarts. | |
- Maps docker service --restart-delay option. | |
restart_policy_window: | |
required: false | |
default: 0 | |
description: | |
- Restart policy evaluation window. | |
- Maps docker service --restart-window option. | |
user: | |
required: false | |
default: root | |
description: username or UID | |
requirements: | |
- "docker-py >= 2.0" | |
''' | |
RETURN = ''' | |
ansible_swarm_service: | |
returned: always | |
type: dict | |
description: | |
- Dictionary of variables representing the current state of the service. | |
Matches the module parameters format. | |
- Note that facts are not part of registered vars but accessible directly. | |
sample: '{ | |
"args": [ | |
"sleep", | |
"3600" | |
], | |
"constraints": [], | |
"container_labels": {}, | |
"endpoint_mode": "vip", | |
"env": [ | |
"ENVVAR1=envvar1" | |
], | |
"image": "alpine", | |
"labels": {}, | |
"limit_cpu": 0.0, | |
"limit_memory": 0, | |
"mode": "replicated", | |
"mounts": [ | |
{ | |
"source": "/tmp/", | |
"target": "/remote_tmp/", | |
"type": "bind" | |
} | |
], | |
"networks": [], | |
"publish": [], | |
"replicas": 1, | |
"reserve_cpu": 0.0, | |
"reserve_memory": 0, | |
"restart_policy": "any", | |
"restart_policy_attempts": 5, | |
"restart_policy_delay": 0, | |
"restart_policy_window": 30 | |
}' | |
changes: | |
returned: always | |
description: | |
- List of changed service attributes if a service has been altered, | |
[] otherwhise | |
type: list | |
sample: ['container_labels', 'replicas'] | |
rebuilt: | |
returned: always | |
description: | |
- True if the service has been recreated (removed and created) | |
type: bool | |
sample: True | |
''' | |
EXAMPLES = ''' | |
- name: define myservice | |
docker_swarm_service: | |
name: myservice | |
image: "alpine" | |
args: | |
- "sleep" | |
- "3600" | |
mounts: | |
- source: /tmp/ | |
target: /remote_tmp/ | |
type: bind | |
env: | |
- "ENVVAR1=envvar1" | |
restart_policy: any | |
restart_policy_attempts: 5 | |
restart_policy_window: 30 | |
register: dss_out1 | |
- name: change myservice.env | |
docker_swarm_service: | |
name: myservice | |
image: "alpine" | |
args: | |
- "sleep" | |
- "7200" | |
mounts: | |
- source: /tmp/ | |
target: /remote_tmp/ | |
type: bind | |
env: | |
- "ENVVAR1=envvar1" | |
restart_policy: any | |
restart_policy_attempts: 5 | |
restart_policy_window: 30 | |
register: dss_out2 | |
- name: test for changed myservice facts | |
fail: | |
msg: unchanged service | |
when: "{{ dss_out1 == dss_out2 }}" | |
- name: change myservice.image | |
docker_swarm_service: | |
name: myservice | |
image: "alpine:edge" | |
args: | |
- "sleep" | |
- "7200" | |
mounts: | |
- source: /tmp/ | |
target: /remote_tmp/ | |
type: bind | |
env: | |
- "ENVVAR1=envvar1" | |
restart_policy: any | |
restart_policy_attempts: 5 | |
restart_policy_window: 30 | |
register: dss_out3 | |
- name: test for changed myservice facts | |
fail: | |
msg: unchanged service | |
when: "{{ dss_out2 == dss_out3 }}" | |
- name: remove mount | |
docker_swarm_service: | |
name: myservice | |
image: "alpine:edge" | |
args: | |
- "sleep" | |
- "7200" | |
env: | |
- "ENVVAR1=envvar1" | |
restart_policy: any | |
restart_policy_attempts: 5 | |
restart_policy_window: 30 | |
register: dss_out4 | |
- name: test for changed myservice facts | |
fail: | |
msg: unchanged service | |
when: "{{ dss_out3 == dss_out4 }}" | |
- name: keep service as it is | |
docker_swarm_service: | |
name: myservice | |
image: "alpine:edge" | |
args: | |
- "sleep" | |
- "7200" | |
env: | |
- "ENVVAR1=envvar1" | |
restart_policy: any | |
restart_policy_attempts: 5 | |
restart_policy_window: 30 | |
register: dss_out5 | |
- name: test for changed service facts | |
fail: | |
msg: changed service | |
when: "{{ dss_out5 != dss_out5 }}" | |
- name: remove myservice | |
docker_swarm_service: | |
name: myservice | |
state: absent | |
''' | |
from ansible.module_utils.docker_common import DockerBaseClass | |
from ansible.module_utils.docker_common import AnsibleDockerClient | |
from ansible.module_utils.basic import human_to_bytes | |
from ansible.module_utils._text import to_text | |
try: | |
from distutils.version import LooseVersion | |
from docker import utils | |
from docker import types | |
from docker import __version__ as docker_version | |
if LooseVersion(docker_version) >= LooseVersion('2.0.0'): | |
from docker.types import Ulimit, LogConfig | |
HAS_DOCKER_PY_2 = True | |
else: | |
from docker.utils.types import Ulimit, LogConfig | |
except: | |
# missing docker-py handled in ansible.module_utils.docker | |
pass | |
class DockerService(DockerBaseClass): | |
def __init__(self): | |
self.constraints = [] | |
self.image = "" | |
self.args = [] | |
self.endpoint_mode = "vip" | |
self.dns = [] | |
self.hostname = "" | |
self.tty = False | |
self.dns_search = [] | |
self.dns_options = [] | |
self.env = [] | |
self.labels = {} | |
self.container_labels = {} | |
self.limit_cpu = 0.000 | |
self.limit_memory = 0 | |
self.reserve_cpu = 0.000 | |
self.reserve_memory = 0 | |
self.mode = "replicated" | |
self.user = "root" | |
self.mounts = [] | |
self.constraints = [] | |
self.networks = [] | |
self.publish = [] | |
self.replicas = -1 | |
self.service_id = False | |
self.service_version = False | |
self.restart_policy = None | |
self.restart_policy_attempts = None | |
self.restart_policy_delay = None | |
self.restart_policy_window = None | |
def get_facts(self): | |
return { | |
'image': self.image, | |
'mounts': self.mounts, | |
'networks': self.networks, | |
'args': self.args, | |
'tty': self.tty, | |
'dns': self.dns, | |
'dns_search': self.dns_search, | |
'dns_options': self.dns_options, | |
'hostname': self.hostname, | |
'env': self.env, | |
'publish': self.publish, | |
'constraints': self.constraints, | |
'labels': self.labels, | |
'container_labels': self.container_labels, | |
'mode': self.mode, | |
'replicas': self.replicas, | |
'endpoint_mode': self.endpoint_mode, | |
'restart_policy': self.restart_policy, | |
'limit_cpu': self.limit_cpu, | |
'limit_memory': self.limit_memory, | |
'reserve_cpu': self.reserve_cpu, | |
'reserve_memory': self.reserve_memory, | |
'restart_policy_delay': self.restart_policy_delay, | |
'restart_policy_attempts': self.restart_policy_attempts, | |
'restart_policy_window': self.restart_policy_window} | |
@staticmethod | |
def from_ansible_params(ap, old_service): | |
s = DockerService() | |
s.constraints = ap['constraints'] | |
s.image = ap['image'] | |
s.args = ap['args'] | |
s.endpoint_mode = ap['endpoint_mode'] | |
s.dns = ap['dns'] | |
s.dns_search = ap['dns_search'] | |
s.dns_options = ap['dns_options'] | |
s.hostname = ap['hostname'] | |
s.tty = ap['tty'] | |
s.env = ap['env'] | |
s.labels = ap['labels'] | |
s.container_labels = ap['container_labels'] | |
s.limit_cpu = ap['limit_cpu'] | |
s.limit_memory = ap['limit_memory'] | |
s.reserve_cpu = ap['reserve_cpu'] | |
s.reserve_memory = ap['reserve_memory'] | |
s.mode = ap['mode'] | |
s.mounts = ap['mounts'] | |
s.constraints = ap['constraints'] | |
s.networks = ap['networks'] | |
s.publish = ap['publish'] | |
s.restart_policy = ap['restart_policy'] | |
s.restart_policy_attempts = ap['restart_policy_attempts'] | |
s.restart_policy_delay = ap['restart_policy_delay'] | |
s.restart_policy_window = ap['restart_policy_window'] | |
s.user = ap['user'] | |
if ap['replicas'] == -1: | |
if old_service: | |
s.replicas = old_service.replicas | |
else: | |
s.replicas = 1 | |
else: | |
s.replicas = ap['replicas'] | |
for param_name in ['reverve_memory', 'reserve_cpu', | |
'limit_memory', 'limit_cpu']: | |
if ap.get(param_name): | |
try: | |
setattr(s, param_name, human_to_bytes(ap[param_name])) | |
except ValueError as exc: | |
raise Exception("Failed to convert %s to bytes: %s" % (param_name, exc)) | |
s.publish = [] | |
for param_p in ap['publish']: | |
service_p = {} | |
service_p['protocol'] = param_p.get('protocol', 'tcp') | |
service_p['mode'] = param_p.get('mode', 'ingress') | |
service_p['published_port'] = str(param_p['published_port']) | |
service_p['target_port'] = str(param_p['target_port']) | |
if service_p['protocol'] not in ['tcp', 'udp']: | |
raise ValueError("got publish.protocol '%s', valid values:'tcp', 'udp'" % | |
service_p['protocol']) | |
if service_p['mode'] not in ['ingress', 'host']: | |
raise ValueError("got publish.mode '%s', valid values:'ingress', 'host'" % | |
service_p['mode']) | |
s.publish.append(service_p) | |
s.mounts = [] | |
for param_m in ap['mounts']: | |
service_m = {} | |
service_m['readonly'] = bool(param_m.get('readonly', False)) | |
service_m['type'] = param_m.get('type', 'bind') | |
service_m['source'] = param_m['source'] | |
service_m['target'] = param_m['target'] | |
s.mounts.append(service_m) | |
return s | |
def compare(self, os): | |
differences = [] | |
needs_rebuild = False | |
if self.endpoint_mode != os.endpoint_mode: | |
differences.append('endpoint_mode') | |
if self.env != os.env: | |
differences.append('env') | |
if self.mode != os.mode: | |
needs_rebuild = True | |
differences.append('mode') | |
if self.mounts != os.mounts: | |
differences.append('mounts') | |
if self.networks != os.networks: | |
differences.append('networks') | |
needs_rebuild = True | |
if self.replicas != os.replicas: | |
differences.append('replicas') | |
if self.args != os.args: | |
differences.append('args') | |
if self.constraints != os.constraints: | |
differences.append('constraints') | |
if self.labels != os.labels: | |
differences.append('labels') | |
if self.limit_cpu != os.limit_cpu: | |
differences.append('limit_cpu') | |
if self.limit_memory != os.limit_memory: | |
differences.append('limit_memory') | |
if self.reserve_cpu != os.reserve_cpu: | |
differences.append('reserve_cpu') | |
if self.reserve_memory != os.reserve_memory: | |
differences.append('reserve_memory') | |
if self.container_labels != os.container_labels: | |
differences.append('container_labels') | |
if self.publish != os.publish: | |
differences.append('publish') | |
if self.restart_policy != os.restart_policy: | |
differences.append('restart_policy') | |
if self.restart_policy_attempts != os.restart_policy_attempts: | |
differences.append('restart_policy_attempts') | |
if self.restart_policy_delay != os.restart_policy_delay: | |
differences.append('restart_policy_delay') | |
if self.restart_policy_window != os.restart_policy_window: | |
differences.append('restart_policy_window') | |
if self.image != os.image: | |
differences.append('image') | |
if self.user != os.user: | |
differences.append('user') | |
if self.dns != os.dns: | |
differences.append('dns') | |
if self.dns_search != os.dns_search: | |
differences.append('dns_search') | |
if self.dns_options != os.dns_options: | |
differences.append('dns_options') | |
if self.hostname != os.hostname: | |
differences.append('hostname') | |
if self.tty != os.tty: | |
differences.append('tty') | |
return len(differences) > 0, differences, needs_rebuild | |
def __str__(self): | |
return str({ | |
'mode': self.mode, | |
'env': self.env, | |
'endpoint_mode': self.endpoint_mode, | |
'mounts': self.mounts, | |
'networks': self.networks, | |
'replicas': self.replicas}) | |
def generate_docker_py_service_description(self, name, docker_networks): | |
cspec = {'Image': self.image, | |
'User': self.user} | |
cspec['Mounts'] = [] | |
for mount_config in self.mounts: | |
cspec['Mounts'].append({ | |
'Target': mount_config['target'], | |
'Source': mount_config['source'], | |
'Type': mount_config['type'], | |
'ReadOnly': mount_config['readonly'] | |
}) | |
cspec['DNSConfig'] = {"Nameservers": self.dns, | |
"Search": self.dns_search, | |
"Options": self.dns_options} | |
cspec['Args'] = self.args | |
cspec['Env'] = self.env | |
cspec['TTY'] = self.tty | |
cspec['Hostname'] = self.hostname | |
cspec['Labels'] = self.container_labels | |
restart_policy = types.RestartPolicy( | |
condition=self.restart_policy, | |
delay=self.restart_policy_delay, | |
max_attempts=self.restart_policy_attempts, | |
window=self.restart_policy_window) | |
resources = { | |
'Limits': { | |
'NanoCPUs': int(self.limit_cpu * 1000000000), | |
'MemoryBytes': self.limit_memory * 1024 * 1024}, | |
'Reservations': { | |
'NanoCPUs': int(self.reserve_cpu * 1000000000), | |
'MemoryBytes': self.reserve_memory * 1024 * 1024}} | |
task_template = types.TaskTemplate( | |
container_spec=cspec, | |
restart_policy=restart_policy, | |
placement=self.constraints, | |
resources=resources) | |
mode = {'Replicated': {'Replicas': self.replicas}} | |
if self.mode == 'global': | |
mode = {'Global': {}} | |
networks = [] | |
for network_name in self.networks: | |
network_id = None | |
try: | |
network_id = filter(lambda n: n['name'] == network_name, docker_networks)[0]['id'] | |
except: | |
pass | |
if network_id: | |
networks.append({'Target': network_id}) | |
else: | |
raise Exception("no docker networks named: %s" % network_name) | |
endpoint_spec = {'Mode': self.endpoint_mode} | |
endpoint_spec['Ports'] = [] | |
for port in self.publish: | |
endpoint_spec['Ports'].append({ | |
'Protocol': port['protocol'], | |
'PublishMode': port['mode'], | |
'PublishedPort': int(port['published_port']), | |
'TargetPort': int(port['target_port'])}) | |
return task_template, networks, endpoint_spec, mode, self.labels | |
def fail(self, msg): | |
self.parameters.client.module.fail_json(msg=msg) | |
@property | |
def exists(self): | |
return True if self.service else False | |
class DockerServiceManager(): | |
def get_networks_names_ids(self): | |
return [{'name': n['Name'], 'id': n['Id']} for n in self.client.networks()] | |
def get_service(self, name): | |
raw_data = self.client.services(filters={'name': name}) | |
if len(raw_data) == 0: | |
return None | |
raw_data = raw_data[0] | |
networks_names_ids = self.get_networks_names_ids() | |
ds = DockerService() | |
task_template_data = raw_data['Spec']['TaskTemplate'] | |
ds.image = task_template_data['ContainerSpec']['Image'] | |
ds.user = task_template_data['ContainerSpec'].get('User', 'root') | |
ds.env = task_template_data['ContainerSpec'].get('Env', []) | |
ds.args = task_template_data['ContainerSpec'].get('Args', []) | |
dns_config = task_template_data['ContainerSpec'].get('DNSConfig', None) | |
if dns_config: | |
if 'Nameservers' in dns_config.keys(): | |
ds.dns = dns_config['Nameservers'] | |
if 'Search' in dns_config.keys(): | |
ds.dns_search = dns_config['Search'] | |
if 'Options' in dns_config.keys(): | |
ds.dns_options = dns_config['Options'] | |
ds.hostname = task_template_data['ContainerSpec'].get('Hostname', '') | |
ds.tty = task_template_data['ContainerSpec'].get('TTY', False) | |
if 'Placement' in task_template_data.keys(): | |
ds.constraints = task_template_data['Placement'].get('Constraints', []) | |
restart_policy_data = task_template_data.get('RestartPolicy', None) | |
if restart_policy_data: | |
ds.restart_policy = restart_policy_data.get('Condition') | |
ds.restart_policy_delay = restart_policy_data.get('Delay') | |
ds.restart_policy_attempts = restart_policy_data.get('MaxAttempts') | |
ds.restart_policy_window = restart_policy_data.get('Window') | |
raw_data_endpoint = raw_data.get('Endpoint', None) | |
if raw_data_endpoint: | |
raw_data_endpoint_spec = raw_data_endpoint.get('Spec', None) | |
if raw_data_endpoint_spec: | |
ds.endpoint_mode = raw_data_endpoint_spec.get('Mode', 'vip') | |
for port in raw_data_endpoint_spec.get('Ports', []): | |
ds.publish.append({ | |
'protocol': port['Protocol'], | |
'mode': port.get('PublishMode', 'ingress'), | |
'published_port': str(port['PublishedPort']), | |
'target_port': str(port['TargetPort'])}) | |
if 'Resources' in task_template_data.keys(): | |
if 'Limits' in task_template_data['Resources'].keys(): | |
if 'NanoCPUs' in task_template_data['Resources']['Limits'].keys(): | |
ds.limit_cpu = float(task_template_data['Resources']['Limits']['NanoCPUs']) / 1000000000 | |
if 'MemoryBytes' in task_template_data['Resources']['Limits'].keys(): | |
ds.limit_memory = int(task_template_data['Resources']['Limits']['MemoryBytes']) / 1024 / 1024 | |
if 'Reservations' in task_template_data['Resources'].keys(): | |
if 'NanoCPUs' in task_template_data['Resources']['Reservations'].keys(): | |
ds.reserve_cpu = float(task_template_data['Resources']['Reservations']['NanoCPUs']) / 1000000000 | |
if 'MemoryBytes' in task_template_data['Resources']['Reservations'].keys(): | |
ds.reserve_memory = int(task_template_data['Resources']['Reservations']['MemoryBytes']) / 1024 / 1024 | |
ds.labels = raw_data['Spec'].get('Labels', {}) | |
ds.container_labels = task_template_data['ContainerSpec'].get('Labels', {}) | |
mode = raw_data['Spec']['Mode'] | |
if 'Replicated' in mode.keys(): | |
ds.mode = to_text('replicated', encoding='utf-8') | |
ds.replicas = mode['Replicated']['Replicas'] | |
elif 'Global' in mode.keys(): | |
ds.mode = 'global' | |
else: | |
raise Exception("Unknown service mode: %s" % mode) | |
for mount_data in raw_data['Spec']['TaskTemplate']['ContainerSpec'].get('Mounts', []): | |
ds.mounts.append({ | |
'source': mount_data['Source'], | |
'type': mount_data['Type'], | |
'target': mount_data['Target'], | |
'readonly': mount_data.get('ReadOnly', False)}) | |
for raw_network_data in raw_data['Spec'].get('Networks', []): | |
network_name = [network_name_id['name'] for network_name_id in networks_names_ids if network_name_id['id'] == raw_network_data['Target']] | |
if len(network_name) == 0: | |
ds.networks.append(raw_network_data['Target']) | |
else: | |
ds.networks.append(network_name[0]) | |
ds.service_version = raw_data['Version']['Index'] | |
ds.service_id = raw_data['ID'] | |
return ds | |
def update_service(self, name, old_service, new_service): | |
task_template, networks, endpoint_spec, mode, labels = new_service.generate_docker_py_service_description(name, self.get_networks_names_ids()) | |
self.client.update_service( | |
old_service.service_id, | |
old_service.service_version, | |
name=name, | |
endpoint_spec=endpoint_spec, | |
networks=networks, | |
mode=mode, | |
task_template=task_template, | |
labels=labels) | |
def create_service(self, name, service): | |
task_template, networks, endpoint_spec, mode, labels = service.generate_docker_py_service_description(name, self.get_networks_names_ids()) | |
self.client.create_service( | |
name=name, | |
endpoint_spec=endpoint_spec, | |
mode=mode, | |
networks=networks, | |
task_template=task_template, | |
labels=labels) | |
def remove_service(self, name): | |
self.client.remove_service(name) | |
def __init__(self, client): | |
self.client = client | |
def test_parameter_versions(self): | |
parameters_versions = [ | |
{'param': 'dns', 'attribute': 'dns', 'min_version': '1.25'}, | |
{'param': 'dns_options', 'attribute': 'dns_options', 'min_version': '1.25'}, | |
{'param': 'dns_search', 'attribute': 'dns_search', 'min_version': '1.25'}, | |
{'param': 'hostname', 'attribute': 'hostname', 'min_version': '1.25'}, | |
{'param': 'tty', 'attribute': 'tty', 'min_version': '1.25'}] | |
params = self.client.module.params | |
empty_service = DockerService() | |
for pv in parameters_versions: | |
if (params[pv['param']] != getattr(empty_service, pv['attribute']) and | |
(LooseVersion(self.client.version()['ApiVersion']) < | |
LooseVersion(pv['min_version']))): | |
self.client.module.fail_json( | |
msg=('%s parameter supported only with api_version>=%s' | |
% (pv['param'], pv['min_version']))) | |
for publish_def in self.client.module.params.get('publish', []): | |
if ('mode' in publish_def.keys() and | |
(LooseVersion(self.client.version()['ApiVersion']) < | |
LooseVersion('1.25'))): | |
self.client.module.fail_json(msg='publish.mode parameter supported only with api_version>=1.25') | |
def run(self): | |
self.test_parameter_versions() | |
module = self.client.module | |
try: | |
current_service = self.get_service(module.params['name']) | |
except Exception as e: | |
module.fail_json( | |
msg="Error looking for service named %s: %s" % | |
(module.params['name'], e)) | |
try: | |
new_service = DockerService.from_ansible_params(module.params, current_service) | |
except Exception as e: | |
module.fail_json( | |
msg="Error parsing module parameters: %s" % e) | |
changed = False | |
msg = 'noop' | |
rebuilt = False | |
changes = [] | |
facts = {} | |
if current_service: | |
if module.params['state'] == 'absent': | |
if not module.check_mode: | |
self.remove_service(module.params['name']) | |
msg = 'Service removed' | |
changed = True | |
else: | |
changed, changes, need_rebuild = new_service.compare(current_service) | |
if changed: | |
changed = True | |
if need_rebuild: | |
if not module.check_mode: | |
self.remove_service(module.params['name']) | |
self.create_service(module.params['name'], | |
new_service) | |
msg = 'Service rebuilt' | |
rebuilt = True | |
changes = changes | |
else: | |
if not module.check_mode: | |
self.update_service(module.params['name'], | |
current_service, | |
new_service) | |
msg = 'Service updated' | |
rebuilt = False | |
changes = changes | |
else: | |
msg = 'Service unchanged' | |
facts = new_service.get_facts() | |
else: | |
if module.params['state'] == 'absent': | |
msg = 'Service absent' | |
else: | |
if not module.check_mode: | |
service_id = self.create_service(module.params['name'], | |
new_service) | |
msg = 'Service created' | |
changed = True | |
facts = new_service.get_facts() | |
return msg, changed, rebuilt, changes, facts | |
def main(): | |
argument_spec = dict( | |
name=dict(required=True), | |
image=dict(type='str'), | |
state=dict(default="present", choices=['present', 'absent']), | |
mounts=dict(default=[], type='list'), | |
networks=dict(default=[], type='list'), | |
args=dict(default=[], type='list'), | |
env=dict(default=[], type='list'), | |
publish=dict(default=[], type='list'), | |
constraints=dict(default=[], type='list'), | |
tty=dict(default=False, type='bool'), | |
dns=dict(default=[], type='list'), | |
dns_search=dict(default=[], type='list'), | |
dns_options=dict(default=[], type='list'), | |
hostname=dict(default="", type='str'), | |
labels=dict(default={}, type='dict'), | |
container_labels=dict(default={}, type='dict'), | |
mode=dict(default="replicated"), | |
replicas=dict(default=-1, type='int'), | |
endpoint_mode=dict(default='vip', choices=['vip', 'dnsrr']), | |
restart_policy=dict(default='none', choices=['none', 'on-failure', 'any']), | |
limit_cpu=dict(default=0, type='float'), | |
limit_memory=dict(default=0, type='int'), | |
reserve_cpu=dict(default=0, type='float'), | |
reserve_memory=dict(default=0, type='int'), | |
restart_policy_delay=dict(default=0, type='int'), | |
restart_policy_attempts=dict(default=0, type='int'), | |
restart_policy_window=dict(default=0, type='int'), | |
user=dict(default='root')) | |
required_if = [ | |
('state', 'present', [ | |
'image'])] | |
client = AnsibleDockerClient( | |
argument_spec=argument_spec, | |
required_if=required_if, | |
supports_check_mode=True | |
) | |
if not HAS_DOCKER_PY_2: | |
client.module.fail_json( | |
msg=("docker python library version is %s. " + | |
"this module requires version 2.0.0 or greater") | |
% docker_version) | |
dsm = DockerServiceManager(client) | |
msg, changed, rebuilt, changes, facts = dsm.run() | |
client.module.exit_json(msg=msg, changed=changed, rebuilt=rebuilt, changes=changes, ansible_docker_service=facts) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ansible/ansible#19229