ansible/cloud/docker/docker.py

980 lines
34 KiB
Python
Raw Normal View History

#!/usr/bin/python
# (c) 2013, Cove Schneider
# (c) 2014, Joshua Conner <joshua.conner@gmail.com>
# (c) 2014, Pavel Antonov <antonov@adwz.ru>
2013-10-14 00:07:27 +02:00
#
# This file is part of Ansible,
2013-10-14 00:07:27 +02:00
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
2013-10-14 00:07:27 +02:00
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
2013-10-14 00:07:27 +02:00
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################################
2013-10-14 00:07:27 +02:00
DOCUMENTATION = '''
---
module: docker
version_added: "1.4"
2013-10-14 00:07:27 +02:00
short_description: manage docker containers
description:
2013-10-14 06:37:42 +02:00
- Manage the life cycle of docker containers.
2013-10-14 00:07:27 +02:00
options:
count:
description:
- Set number of containers to run
required: False
default: 1
aliases: []
image:
description:
- Set container image to use
required: true
default: null
aliases: []
command:
description:
- Set command to run in a container on startup
required: false
default: null
aliases: []
name:
description:
- Set name for container (used to find single container or to provide links)
required: false
default: null
aliases: []
version_added: "1.5"
2013-10-14 00:07:27 +02:00
ports:
description:
- Set private to public port mapping specification using docker CLI-style syntax [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp]
2013-10-14 00:07:27 +02:00
required: false
default: null
aliases: []
version_added: "1.5"
expose:
description:
- Set container ports to expose for port mappings or links. (If the port is already exposed using EXPOSE in a Dockerfile, you don't need to expose it again.)
required: false
default: null
aliases: []
version_added: "1.5"
publish_all_ports:
description:
- Publish all exposed ports to the host interfaces
required: false
default: false
aliases: []
version_added: "1.5"
2013-10-14 00:07:27 +02:00
volumes:
description:
- Set volume(s) to mount on the container
required: false
default: null
aliases: []
volumes_from:
description:
- Set shared volume(s) from another container
required: false
default: null
aliases: []
links:
description:
- Link container(s) to other container(s) (e.g. links=redis,postgresql:db)
required: false
default: null
aliases: []
version_added: "1.5"
2013-10-14 00:07:27 +02:00
memory_limit:
description:
- Set RAM allocated to container
required: false
default: null
aliases: []
default: 256MB
docker_url:
description:
- URL of docker host to issue commands to
required: false
default: unix://var/run/docker.sock
aliases: []
docker_api_version:
description:
- Remote API version to use. This defaults to the current default as specified by docker-py.
required: false
default: docker-py default remote API version
aliases: []
version_added: "1.8"
2013-10-14 00:07:27 +02:00
username:
description:
- Set remote API username
required: false
default: null
aliases: []
password:
description:
- Set remote API password
required: false
default: null
aliases: []
email:
description:
- Set remote API email
required: false
default: null
aliases: []
2013-10-14 00:07:27 +02:00
hostname:
description:
- Set container hostname
required: false
default: null
aliases: []
env:
description:
- Set environment variables (e.g. env="PASSWORD=sEcRe7,WORKERS=4")
2013-10-14 00:07:27 +02:00
required: false
default: null
aliases: []
dns:
description:
- Set custom DNS servers for the container
required: false
default: null
aliases: []
detach:
description:
- Enable detached mode on start up, leaves container running in background
required: false
default: true
aliases: []
state:
description:
- Set the state of the container
required: false
default: present
choices: [ "present", "running", "stopped", "absent", "killed", "restarted" ]
2013-10-14 00:07:27 +02:00
aliases: []
privileged:
description:
- Set whether the container should run in privileged mode
required: false
default: false
aliases: []
lxc_conf:
description:
- LXC config parameters, e.g. lxc.aa_profile:unconfined
required: false
default:
aliases: []
name:
description:
- Set the name of the container (cannot use with count)
required: false
default: null
aliases: []
version_added: "1.5"
stdin_open:
description:
- Keep stdin open
required: false
default: false
aliases: []
version_added: "1.6"
tty:
description:
- Allocate a pseudo-tty
required: false
default: false
aliases: []
version_added: "1.6"
net:
description:
- Set Network mode for the container (bridge, none, container:<name|id>, host). Requires docker >= 0.11.
required: false
default: false
aliases: []
version_added: "1.8"
registry:
description:
- The remote registry URL to use for pulling images.
required: false
default: ''
aliases: []
version_added: "1.8"
restart_policy:
description:
- Set the container restart policy
required: false
default: false
aliases: []
version_added: "1.9"
restart_policy_retry:
description:
- Set the retry limit for container restart policy
required: false
default: false
aliases: []
version_added: "1.9"
insecure_registry:
description:
- Use insecure private registry by HTTP instead of HTTPS (needed for docker-py >= 0.5.0).
required: false
default: false
aliases: []
version_added: "1.9"
author: Cove Schneider, Joshua Conner, Pavel Antonov
requirements: [ "docker-py >= 0.3.0", "docker >= 0.10.0" ]
2013-10-14 00:07:27 +02:00
'''
2013-10-14 05:34:14 +02:00
EXAMPLES = '''
Start one docker container running tomcat in each host of the web group and bind tomcat's listening port to 8080
on the host:
- hosts: web
sudo: yes
tasks:
- name: run tomcat servers
docker: image=centos command="service tomcat6 start" ports=8080
2013-10-14 05:34:14 +02:00
The tomcat server's port is NAT'ed to a dynamic port on the host, but you can determine which port the server was
mapped to using docker_containers:
2013-10-14 05:34:14 +02:00
- hosts: web
sudo: yes
tasks:
- name: run tomcat servers
docker: image=centos command="service tomcat6 start" ports=8080 count=5
- name: Display IP address and port mappings for containers
debug: msg={{inventory_hostname}}:{{item['HostConfig']['PortBindings']['8080/tcp'][0]['HostPort']}}
with_items: docker_containers
2013-10-14 05:34:14 +02:00
Just as in the previous example, but iterates over the list of docker containers with a sequence:
- hosts: web
sudo: yes
vars:
start_containers_count: 5
tasks:
- name: run tomcat servers
docker: image=centos command="service tomcat6 start" ports=8080 count={{start_containers_count}}
- name: Display IP address and port mappings for containers
debug: msg="{{inventory_hostname}}:{{docker_containers[{{item}}]['HostConfig']['PortBindings']['8080/tcp'][0]['HostPort']}}"
2013-10-14 05:34:14 +02:00
with_sequence: start=0 end={{start_containers_count - 1}}
Stop, remove all of the running tomcat containers and list the exit code from the stopped containers:
2013-10-14 05:34:14 +02:00
- hosts: web
sudo: yes
tasks:
- name: stop tomcat servers
docker: image=centos command="service tomcat6 start" state=absent
- name: Display return codes from stopped containers
debug: msg="Returned {{inventory_hostname}}:{{item}}"
with_items: docker_containers
Create a named container:
- hosts: web
sudo: yes
tasks:
- name: run tomcat server
docker: image=centos name=tomcat command="service tomcat6 start" ports=8080
Create multiple named containers:
- hosts: web
sudo: yes
tasks:
- name: run tomcat servers
docker: image=centos name={{item}} command="service tomcat6 start" ports=8080
with_items:
- crookshank
- snowbell
- heathcliff
- felix
- sylvester
Create containers named in a sequence:
- hosts: web
sudo: yes
tasks:
- name: run tomcat servers
docker: image=centos name={{item}} command="service tomcat6 start" ports=8080
with_sequence: start=1 end=5 format=tomcat_%d.example.com
Create two linked containers:
- hosts: web
sudo: yes
tasks:
- name: ensure redis container is running
docker: image=crosbymichael/redis name=redis
- name: ensure redis_ambassador container is running
docker: image=svendowideit/ambassador ports=6379:6379 links=redis:redis name=redis_ambassador_ansible
Create containers with options specified as key-value pairs and lists:
- hosts: web
sudo: yes
tasks:
- docker:
image: namespace/image_name
links:
- postgresql:db
- redis:redis
Create containers with options specified as strings and lists as comma-separated strings:
- hosts: web
sudo: yes
tasks:
docker: image=namespace/image_name links=postgresql:db,redis:redis
Create a container with no networking:
- hosts: web
sudo: yes
tasks:
docker: image=namespace/image_name net=none
2013-10-14 05:34:14 +02:00
'''
HAS_DOCKER_PY = True
import sys
from urlparse import urlparse
2013-10-14 00:07:27 +02:00
try:
import docker.client
import docker.utils
2013-10-14 00:07:27 +02:00
from requests.exceptions import *
except ImportError, e:
HAS_DOCKER_PY = False
if HAS_DOCKER_PY:
try:
from docker.errors import APIError as DockerAPIError
except ImportError:
from docker.client import APIError as DockerAPIError
2013-10-14 00:07:27 +02:00
def _human_to_bytes(number):
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
if isinstance(number, int):
return number
if number[-1] == suffixes[0] and number[-2].isdigit():
return number[:-1]
i = 1
for each in suffixes[1:]:
if number[-len(each):] == suffixes[i]:
return int(number[:-len(each)]) * (1024 ** i)
i = i + 1
print "failed=True msg='Could not convert %s to integer'" % (number)
sys.exit(1)
def _ansible_facts(container_list):
return {"docker_containers": container_list}
2013-10-14 00:07:27 +02:00
def _docker_id_quirk(inspect):
# XXX: some quirk in docker
if 'ID' in inspect:
inspect['Id'] = inspect['ID']
del inspect['ID']
return inspect
def get_split_image_tag(image):
# If image contains a host or org name, omit that from our check
if '/' in image:
registry, resource = image.rsplit('/', 1)
else:
registry, resource = None, image
# now we can determine if image has a tag
if ':' in resource:
resource, tag = resource.split(':', 1)
if registry:
resource = '/'.join((registry, resource))
else:
tag = "latest"
resource = image
return resource, tag
def get_docker_py_versioninfo():
if hasattr(docker, '__version__'):
# a '__version__' attribute was added to the module but not until
# after 0.3.0 was pushed to pypi. If it's there, use it.
version = []
for part in docker.__version__.split('.'):
try:
version.append(int(part))
except ValueError:
for idx, char in enumerate(part):
if not char.isdigit():
nondigit = part[idx:]
digit = part[:idx]
if digit:
version.append(int(digit))
if nondigit:
version.append(nondigit)
elif hasattr(docker.Client, '_get_raw_response_socket'):
# HACK: if '__version__' isn't there, we check for the existence of
# `_get_raw_response_socket` in the docker.Client class, which was
# added in 0.3.0
version = (0, 3, 0)
else:
# This is untrue but this module does not function with a version less
# than 0.3.0 so it's okay to lie here.
version = (0,)
2014-12-24 03:04:04 +01:00
return tuple(version)
def check_dependencies(module):
"""
Ensure `docker-py` >= 0.3.0 is installed, and call module.fail_json with a
helpful error message if it isn't.
"""
if not HAS_DOCKER_PY:
module.fail_json(msg="`docker-py` doesn't seem to be installed, but is required for the Ansible Docker module.")
else:
versioninfo = get_docker_py_versioninfo()
if versioninfo < (0, 3, 0):
module.fail_json(msg="The Ansible Docker module requires `docker-py` >= 0.3.0.")
2014-12-18 22:43:55 +01:00
class DockerManager(object):
2013-10-14 00:07:27 +02:00
counters = {'created':0, 'started':0, 'stopped':0, 'killed':0, 'removed':0, 'restarted':0, 'pull':0}
_capabilities = set()
# Map optional parameters to minimum (docker-py version, server APIVersion)
# docker-py version is a tuple of ints because we have to compare them
# server APIVersion is passed to a docker-py function that takes strings
_cap_ver_req = {
'dns': ((0, 3, 0), '1.10'),
'volumes_from': ((0, 3, 0), '1.10'),
'restart_policy': ((0, 5, 0), '1.14'),
# Clientside only
'insecure_registry': ((0, 5, 0), '0.0')
}
2013-10-14 00:07:27 +02:00
def __init__(self, module):
self.module = module
self.binds = None
self.volumes = None
if self.module.params.get('volumes'):
self.binds = {}
self.volumes = {}
vols = self.module.params.get('volumes')
for vol in vols:
parts = vol.split(":")
# host mount (e.g. /mnt:/tmp, bind mounts host's /tmp to /mnt in the container)
if len(parts) == 2:
self.volumes[parts[1]] = {}
self.binds[parts[0]] = parts[1]
2014-05-27 05:20:15 +02:00
# with bind mode
elif len(parts) == 3:
if parts[2] not in ['ro', 'rw']:
2014-05-28 00:14:54 +02:00
self.module.fail_json(msg='bind mode needs to either be "ro" or "rw"')
2014-05-27 05:20:15 +02:00
ro = parts[2] == 'ro'
self.volumes[parts[1]] = {}
self.binds[parts[0]] = {'bind': parts[1], 'ro': ro}
# docker mount (e.g. /www, mounts a docker volume /www on the container at the same location)
else:
self.volumes[parts[0]] = {}
self.lxc_conf = None
if self.module.params.get('lxc_conf'):
self.lxc_conf = []
options = self.module.params.get('lxc_conf')
for option in options:
parts = option.split(':')
self.lxc_conf.append({"Key": parts[0], "Value": parts[1]})
self.exposed_ports = None
if self.module.params.get('expose'):
self.exposed_ports = self.get_exposed_ports(self.module.params.get('expose'))
self.port_bindings = None
if self.module.params.get('ports'):
self.port_bindings = self.get_port_bindings(self.module.params.get('ports'))
self.links = None
if self.module.params.get('links'):
self.links = self.get_links(self.module.params.get('links'))
self.env = self.module.params.get('env', None)
2013-10-14 00:07:27 +02:00
# connect to docker server
docker_url = urlparse(module.params.get('docker_url'))
docker_api_version = module.params.get('docker_api_version')
self.client = docker.Client(base_url=docker_url.geturl(), version=docker_api_version)
self.docker_py_versioninfo = get_docker_py_versioninfo()
def _check_capabilties(self):
"""
Create a list of available capabilities
"""
api_version = self.client.version()['ApiVersion']
for cap, req_vers in self._cap_ver_req.items():
if (self.docker_py_versioninfo >= req_vers[0] and
docker.utils.compare_version(req_vers[1], api_version) >= 0):
self._capabilities.add(cap)
def ensure_capability(self, capability, fail=True):
"""
Some of the functionality this ansible module implements are only
available in newer versions of docker. Ensure that the capability
is available here.
If fail is set to False then return True or False depending on whether
we have the capability. Otherwise, simply fail and exit the module if
we lack the capability.
"""
if not self._capabilities:
self._check_capabilties()
if capability in self._capabilities:
return True
if not fail:
return False
api_version = self.client.version()['ApiVersion']
self.module.fail_json(msg='Specifying the `%s` parameter requires'
' docker-py: %s, docker server apiversion %s; found'
' docker-py: %s, server: %s' % (
capability,
'.'.join(self._cap_ver_req[capability][0]),
self._cap_ver_req[capability][1],
'.'.join(self.docker_py_versioninfo),
api_version))
def get_links(self, links):
"""
Parse the links passed, if a link is specified without an alias then just create the alias of the same name as the link
"""
processed_links = {}
for link in links:
parsed_link = link.split(':', 1)
if(len(parsed_link) == 2):
processed_links[parsed_link[0]] = parsed_link[1]
else:
processed_links[parsed_link[0]] = parsed_link[0]
return processed_links
def get_exposed_ports(self, expose_list):
"""
Parse the ports and protocols (TCP/UDP) to expose in the docker-py `create_container` call from the docker CLI-style syntax.
"""
if expose_list:
exposed = []
for port in expose_list:
port = str(port).strip()
if port.endswith('/tcp') or port.endswith('/udp'):
port_with_proto = tuple(port.split('/'))
else:
# assume tcp protocol if not specified
port_with_proto = (port, 'tcp')
exposed.append(port_with_proto)
return exposed
else:
return None
def get_port_bindings(self, ports):
"""
Parse the `ports` string into a port bindings dict for the `start_container` call.
"""
binds = {}
for port in ports:
# ports could potentially be an array like [80, 443], so we make sure they're strings
# before splitting
parts = str(port).split(':')
container_port = parts[-1]
if '/' not in container_port:
container_port = int(parts[-1])
p_len = len(parts)
if p_len == 1:
# Bind `container_port` of the container to a dynamically
# allocated TCP port on all available interfaces of the host
# machine.
bind = ('0.0.0.0',)
elif p_len == 2:
# Bind `container_port` of the container to port `parts[0]` on
# all available interfaces of the host machine.
bind = ('0.0.0.0', int(parts[0]))
elif p_len == 3:
# Bind `container_port` of the container to port `parts[1]` on
# IP `parts[0]` of the host machine. If `parts[1]` empty bind
# to a dynamically allocacted port of IP `parts[0]`.
bind = (parts[0], int(parts[1])) if parts[1] else (parts[0],)
if container_port in binds:
old_bind = binds[container_port]
if isinstance(old_bind, list):
# append to list if it already exists
old_bind.append(bind)
else:
# otherwise create list that contains the old and new binds
binds[container_port] = [binds[container_port], bind]
else:
binds[container_port] = bind
return binds
2013-10-14 00:07:27 +02:00
def get_summary_counters_msg(self):
msg = ""
for k, v in self.counters.iteritems():
msg = msg + "%s %d " % (k, v)
return msg
2013-10-14 00:07:27 +02:00
def increment_counter(self, name):
self.counters[name] = self.counters[name] + 1
def has_changed(self):
for k, v in self.counters.iteritems():
if v > 0:
return True
return False
def get_inspect_containers(self, containers):
inspect = []
for i in containers:
details = self.client.inspect_container(i['Id'])
details = _docker_id_quirk(details)
inspect.append(details)
return inspect
2013-10-14 00:07:27 +02:00
def get_deployed_containers(self):
"""determine which images/commands are running already"""
image = self.module.params.get('image')
command = self.module.params.get('command')
if command:
command = command.strip()
name = self.module.params.get('name')
if name and not name.startswith('/'):
name = '/' + name
deployed = []
2013-10-14 00:07:27 +02:00
# if we weren't given a tag with the image, we need to only compare on the image name, as that
# docker will give us back the full image name including a tag in the container list if one exists.
image, tag = get_split_image_tag(image)
for i in self.client.containers(all=True):
running_image, running_tag = get_split_image_tag(i['Image'])
running_command = i['Command'].strip()
name_matches = False
if i["Names"]:
name_matches = (name and name in i['Names'])
image_matches = (running_image == image)
tag_matches = (not tag or running_tag == tag)
# if a container has an entrypoint, `command` will actually equal
# '{} {}'.format(entrypoint, command)
command_matches = (not command or running_command.endswith(command))
2014-05-15 16:04:30 +02:00
if name_matches or (name is None and image_matches and tag_matches and command_matches):
2013-10-14 00:07:27 +02:00
details = self.client.inspect_container(i['Id'])
details = _docker_id_quirk(details)
2013-10-14 00:07:27 +02:00
deployed.append(details)
return deployed
def get_running_containers(self):
running = []
for i in self.get_deployed_containers():
if i['State']['Running'] == True and i['State'].get('Ghost', False) == False:
2013-10-14 00:07:27 +02:00
running.append(i)
return running
def create_containers(self, count=1):
params = {'image': self.module.params.get('image'),
'command': self.module.params.get('command'),
'ports': self.exposed_ports,
'volumes': self.volumes,
2013-10-14 00:07:27 +02:00
'mem_limit': _human_to_bytes(self.module.params.get('memory_limit')),
'environment': self.env,
2013-10-14 00:07:27 +02:00
'hostname': self.module.params.get('hostname'),
'detach': self.module.params.get('detach'),
'name': self.module.params.get('name'),
'stdin_open': self.module.params.get('stdin_open'),
'tty': self.module.params.get('tty'),
2014-12-24 04:03:22 +01:00
'dns': self.module.params.get('dns'),
'volumes_from': self.module.params.get('volumes_from'),
2013-10-14 00:07:27 +02:00
}
if params['dns'] is not None:
self.ensure_capability('dns')
if params['volumes_from'] is not None:
self.ensure_capability('volumes_from')
extra_params = {}
if self.module.params.get('insecure_registry'):
if self.ensure_capability('insecure_registry', fail=False):
extra_params['insecure_registry'] = self.module.params.get('insecure_registry')
2013-10-14 00:07:27 +02:00
def do_create(count, params):
results = []
2013-10-14 06:34:58 +02:00
for _ in range(count):
2013-10-14 00:07:27 +02:00
result = self.client.create_container(**params)
self.increment_counter('created')
results.append(result)
return results
try:
containers = do_create(count, params)
except:
resource = self.module.params.get('image')
image, tag = get_split_image_tag(resource)
if self.module.params.get('username'):
try:
self.client.login(
self.module.params.get('username'),
password=self.module.params.get('password'),
email=self.module.params.get('email'),
registry=self.module.params.get('registry')
)
except:
self.module.fail_json(msg="failed to login to the remote registry, check your username/password.")
try:
self.client.pull(image, tag=tag, **extra_params)
except:
self.module.fail_json(msg="failed to pull the specified image: %s" % resource)
2013-10-14 00:07:27 +02:00
self.increment_counter('pull')
containers = do_create(count, params)
return containers
def start_containers(self, containers):
params = {
'lxc_conf': self.lxc_conf,
'binds': self.binds,
'port_bindings': self.port_bindings,
'publish_all_ports': self.module.params.get('publish_all_ports'),
'privileged': self.module.params.get('privileged'),
'links': self.links,
'network_mode': self.module.params.get('net'),
}
2014-12-24 04:03:22 +01:00
optionals = {}
for optional_param in ('dns', 'volumes_from', 'restart_policy', 'restart_policy_retry'):
optionals[optional_param] = self.module.params.get(optional_param)
if optionals['dns'] is not None:
self.ensure_capability('dns')
params['dns'] = optionals['dns']
if optionals['volumes_from'] is not None:
self.ensure_capability('volumes_from')
params['volumes_from'] = optionals['volumes_from']
if optionals['restart_policy'] is not None:
self.ensure_capability('restart_policy')
params['restart_policy'] = { 'Name': optionals['restart_policy'] }
if params['restart_policy']['Name'] == 'on-failure':
params['restart_policy']['MaximumRetryCount'] = optionals['restart_policy_retry']
2013-10-14 00:07:27 +02:00
for i in containers:
self.client.start(i['Id'], **params)
self.increment_counter('started')
2013-10-14 00:07:27 +02:00
def stop_containers(self, containers):
for i in containers:
self.client.stop(i['Id'])
self.increment_counter('stopped')
return [self.client.wait(i['Id']) for i in containers]
def remove_containers(self, containers):
for i in containers:
self.client.remove_container(i['Id'])
self.increment_counter('removed')
2013-10-14 00:07:27 +02:00
def kill_containers(self, containers):
for i in containers:
self.client.kill(i['Id'])
self.increment_counter('killed')
def restart_containers(self, containers):
for i in containers:
self.client.restart(i['Id'])
self.increment_counter('restarted')
2013-10-14 00:07:27 +02:00
def main():
module = AnsibleModule(
argument_spec = dict(
count = dict(default=1),
image = dict(required=True),
command = dict(required=False, default=None),
expose = dict(required=False, default=None, type='list'),
ports = dict(required=False, default=None, type='list'),
publish_all_ports = dict(default=False, type='bool'),
volumes = dict(default=None, type='list'),
2013-10-14 00:07:27 +02:00
volumes_from = dict(default=None),
links = dict(default=None, type='list'),
2013-10-14 00:07:27 +02:00
memory_limit = dict(default=0),
memory_swap = dict(default=0),
docker_url = dict(default='unix://var/run/docker.sock'),
docker_api_version = dict(default=docker.client.DEFAULT_DOCKER_API_VERSION),
username = dict(default=None),
2013-10-14 00:07:27 +02:00
password = dict(),
email = dict(),
registry = dict(),
2013-10-14 00:07:27 +02:00
hostname = dict(default=None),
env = dict(type='dict'),
2013-10-14 00:07:27 +02:00
dns = dict(),
detach = dict(default=True, type='bool'),
state = dict(default='running', choices=['absent', 'present', 'running', 'stopped', 'killed', 'restarted']),
restart_policy = dict(default=None, choices=['always', 'on-failure', 'no']),
restart_policy_retry = dict(default=0, type='int'),
2013-10-14 00:07:27 +02:00
debug = dict(default=False, type='bool'),
privileged = dict(default=False, type='bool'),
stdin_open = dict(default=False, type='bool'),
tty = dict(default=False, type='bool'),
lxc_conf = dict(default=None, type='list'),
name = dict(default=None),
net = dict(default=None),
insecure_registry = dict(default=False, type='bool'),
2013-10-14 00:07:27 +02:00
)
)
check_dependencies(module)
2013-10-14 00:07:27 +02:00
try:
manager = DockerManager(module)
2013-10-14 00:07:27 +02:00
state = module.params.get('state')
count = int(module.params.get('count'))
name = module.params.get('name')
image = module.params.get('image')
if count < 0:
module.fail_json(msg="Count must be greater than zero")
if count > 1 and name:
module.fail_json(msg="Count and name must not be used together")
2013-10-14 00:07:27 +02:00
running_containers = manager.get_running_containers()
2013-10-14 00:07:27 +02:00
running_count = len(running_containers)
delta = count - running_count
deployed_containers = manager.get_deployed_containers()
2013-10-14 00:07:27 +02:00
facts = None
failed = False
changed = False
# start/stop containers
if state in [ "running", "present" ]:
# make sure a container with `name` exists, if not create and start it
if name:
# first determine if a container with this name exists
existing_container = None
for deployed_container in deployed_containers:
if deployed_container.get('Name') == '/%s' % name:
existing_container = deployed_container
break
# the named container is running, but with a
# different image or tag, so we stop it first
if existing_container and existing_container.get('Config', dict()).get('Image') != image:
manager.stop_containers([existing_container])
manager.remove_containers([existing_container])
running_containers = manager.get_running_containers()
deployed_containers = manager.get_deployed_containers()
existing_container = None
# if the container isn't running (or if we stopped the
# old version above), create and (maybe) start it up now
if not existing_container:
containers = manager.create_containers(1)
if state == "present": # otherwise it get (re)started later anyways..
manager.start_containers(containers)
running_containers = manager.get_running_containers()
deployed_containers = manager.get_deployed_containers()
if state == "running":
# make sure a container with `name` is running
if name and "/" + name not in map(lambda x: x.get('Name'), running_containers):
manager.start_containers(deployed_containers)
# start more containers if we don't have enough
elif delta > 0:
containers = manager.create_containers(delta)
manager.start_containers(containers)
# stop containers if we have too many
elif delta < 0:
containers_to_stop = running_containers[0:abs(delta)]
containers = manager.stop_containers(containers_to_stop)
manager.remove_containers(containers_to_stop)
facts = manager.get_running_containers()
else:
2014-04-21 17:06:00 +02:00
facts = manager.get_deployed_containers()
2013-10-14 00:07:27 +02:00
# stop and remove containers
elif state == "absent":
facts = manager.stop_containers(deployed_containers)
manager.remove_containers(deployed_containers)
2013-10-14 00:07:27 +02:00
# stop containers
elif state == "stopped":
facts = manager.stop_containers(running_containers)
2013-10-14 00:07:27 +02:00
# kill containers
elif state == "killed":
manager.kill_containers(running_containers)
2013-10-14 00:07:27 +02:00
# restart containers
elif state == "restarted":
manager.restart_containers(running_containers)
facts = manager.get_inspect_containers(running_containers)
2013-10-14 00:07:27 +02:00
msg = "%s container(s) running image %s with command %s" % \
(manager.get_summary_counters_msg(), module.params.get('image'), module.params.get('command'))
changed = manager.has_changed()
2013-10-14 00:07:27 +02:00
module.exit_json(failed=failed, changed=changed, msg=msg, ansible_facts=_ansible_facts(facts))
except DockerAPIError, e:
changed = manager.has_changed()
2013-10-14 00:07:27 +02:00
module.exit_json(failed=True, changed=changed, msg="Docker API error: " + e.explanation)
except RequestException, e:
changed = manager.has_changed()
2013-10-14 00:07:27 +02:00
module.exit_json(failed=True, changed=changed, msg=repr(e))
2013-12-02 21:13:49 +01:00
# import module snippets
from ansible.module_utils.basic import *
2013-10-14 00:07:27 +02:00
if __name__ == '__main__':
main()