Created
February 26, 2015 00:23
-
-
Save dguerri/6b8002dc12e3badfa052 to your computer and use it in GitHub Desktop.
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) 2013, Cove Schneider | |
# (c) 2014, Joshua Conner <joshua.conner@gmail.com> | |
# (c) 2014, Pavel Antonov <antonov@adwz.ru> | |
# (c) 2015, Davide Guerri <davide.guerri@gmail.com> | |
# | |
# This file is part of Ansible, | |
# | |
# 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. | |
# | |
# 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. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. | |
###################################################################### | |
DOCUMENTATION = ''' | |
--- | |
module: docker | |
version_added: "1.4" | |
short_description: manage docker containers | |
description: | |
- Manage the life cycle of docker containers. | |
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" | |
ports: | |
description: | |
- Set private to public port mapping specification using docker CLI-style syntax [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp] | |
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" | |
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" | |
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: [] | |
tls_verify: | |
description: | |
- Whether verify tls certs (bool). | |
required: false | |
default: null | |
aliases: [] | |
tls_assert_hostname: | |
description: | |
- Whether verify hostname in the server certificate (bool). | |
required: false | |
default: null | |
aliases: [] | |
tls_ca_cert: | |
description: | |
- Path to the CA certificate (PEM encoded) used to sign client/server certificates. | |
required: false | |
default: null | |
aliases: [] | |
tls_client_cert: | |
description: | |
- Path to the certificate used to authenticate docker client. If specified tls_client_key is also required. | |
required: false | |
default: null | |
aliases: [] | |
tls_client_key: | |
description: | |
- Path to the key used to authenticate docker client. If specified tls_client_cert is also required. | |
required: false | |
default: null | |
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" | |
username: | |
description: | |
- Set remote API username | |
required: false | |
default: null | |
aliases: [] | |
password: | |
description: | |
- Set remote API password | |
required: false | |
default: null | |
aliases: [] | |
hostname: | |
description: | |
- Set container hostname | |
required: false | |
default: null | |
aliases: [] | |
env: | |
description: | |
- Set environment variables (e.g. env="PASSWORD=sEcRe7,WORKERS=4") | |
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" ] | |
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" | |
author: Cove Schneider, Joshua Conner, Pavel Antonov | |
requirements: [ "docker-py >= 0.3.0", "docker >= 0.10.0" ] | |
''' | |
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 | |
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: | |
- 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 | |
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']}}" | |
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: | |
- 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 | |
Create a container using TLS, with mutual authentication, verifying server certificate with a CA certificate | |
- hosts: web | |
tasks: | |
- docker: > | |
image={{ base_image }} | |
name={{ docker_hostname }} | |
detach=yes | |
tty=yes | |
command=bash | |
tls_client_cert=/Users/davide/Developer/docker/.docker/cert.pem | |
tls_client_key=/Users/davide/Developer/docker/.docker/key.pem | |
tls_ca_cert=/Users/davide/Developer/docker/.docker/ca.pem | |
tls_verify=true | |
tls_assert_hostname=false | |
docker_url=https://{{inventory_hostname}}:2376 | |
Create a container using TLS, verifying server certificate with a CA certificate | |
- hosts: web | |
tasks: | |
- docker: > | |
image={{ base_image }} | |
name={{ docker_hostname }} | |
detach=yes | |
tty=yes | |
command=bash | |
tls_ca_cert=/Users/davide/Developer/docker/.docker/ca.pem | |
docker_url=https://{{inventory_hostname}}:2376 | |
''' | |
HAS_DOCKER_PY = True | |
import os | |
import sys | |
from urlparse import urlparse | |
try: | |
import docker.client | |
import docker.utils | |
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 | |
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} | |
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 | |
class DockerManager: | |
counters = {'created':0, 'started':0, 'stopped':0, 'killed':0, 'removed':0, 'restarted':0, 'pull':0} | |
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] | |
# with bind mode | |
elif len(parts) == 3: | |
if parts[2] not in ['ro', 'rw']: | |
self.module.fail_json(msg='bind mode needs to either be "ro" or "rw"') | |
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) | |
# connect to docker server | |
docker_url = urlparse(module.params.get('docker_url')) | |
docker_api_version = module.params.get('docker_api_version') | |
# Set TLS parameters if needed | |
tls_verify = module.params.get('tls_verify') | |
tls_ca_cert = module.params.get('tls_ca_cert') | |
tls_client_cert = module.params.get('tls_client_cert') | |
tls_client_key = module.params.get('tls_client_key') | |
tls_assert_hostname = module.params.get('tls_assert_hostname') | |
if (tls_client_cert is not None and tls_client_key is not None): | |
if not os.path.isfile(tls_client_cert): | |
self.module.fail_json( | |
msg="client certificate '%s' doesn't exist" % tls_client_cert) | |
if not os.path.isfile(tls_client_key): | |
self.module.fail_json( | |
msg="client key '%s' doesn't exist" % tls_client_key) | |
client_cert = (tls_client_cert, tls_client_key) | |
elif tls_client_cert is not None or tls_client_key is not None: | |
self.module.fail_json( | |
msg="both tls_client_cert and tls_client_key must be specified") | |
else: | |
client_cert = None | |
if tls_ca_cert is not None: | |
if not os.path.isfile(tls_ca_cert): | |
self.module.fail_json( | |
msg="ca certificate '%s' doesn't exist" % tls_ca_cert) | |
if tls_ca_cert is not None or client_cert is not None: | |
tls_config = docker.tls.TLSConfig( | |
client_cert=client_cert, | |
ca_cert=tls_ca_cert, | |
verify=tls_verify, | |
assert_hostname=tls_assert_hostname | |
) | |
else: | |
tls_config = tls_verify | |
self.client = docker.Client(base_url=docker_url.geturl(), | |
version=docker_api_version, | |
tls=tls_config) | |
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 | |
def get_summary_counters_msg(self): | |
msg = "" | |
for k, v in self.counters.iteritems(): | |
msg = msg + "%s %d " % (k, v) | |
return msg | |
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 | |
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 = [] | |
# 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)) | |
if name_matches or (name is None and image_matches and tag_matches and command_matches): | |
details = self.client.inspect_container(i['Id']) | |
details = _docker_id_quirk(details) | |
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: | |
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, | |
'mem_limit': _human_to_bytes(self.module.params.get('memory_limit')), | |
'environment': self.env, | |
'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'), | |
} | |
if docker.utils.compare_version('1.10', self.client.version()['ApiVersion']) < 0: | |
params['dns'] = self.module.params.get('dns') | |
params['volumes_from'] = self.module.params.get('volumes_from') | |
def do_create(count, params): | |
results = [] | |
for _ in range(count): | |
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) | |
except: | |
self.module.fail_json(msg="failed to pull the specified image: %s" % resource) | |
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'), | |
} | |
if docker.utils.compare_version('1.10', self.client.version()['ApiVersion']) >= 0 and hasattr(docker, '__version__') and docker.__version__ > '0.3.0': | |
params['dns'] = self.module.params.get('dns') | |
params['volumes_from'] = self.module.params.get('volumes_from') | |
for i in containers: | |
self.client.start(i['Id'], **params) | |
self.increment_counter('started') | |
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') | |
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') | |
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: | |
HAS_NEW_ENOUGH_DOCKER_PY = False | |
if hasattr(docker, '__version__'): | |
# a '__version__' attribute was added to the module but not until | |
# after 0.3.0 was added pushed to pip. If it's there, use it. | |
if docker.__version__ >= '0.3.0': | |
HAS_NEW_ENOUGH_DOCKER_PY = True | |
else: | |
# 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 | |
if hasattr(docker.Client, '_get_raw_response_socket'): | |
HAS_NEW_ENOUGH_DOCKER_PY = True | |
if not HAS_NEW_ENOUGH_DOCKER_PY: | |
module.fail_json(msg="The Ansible Docker module requires `docker-py` >= 0.3.0.") | |
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'), | |
volumes_from = dict(default=None), | |
links = dict(default=None, type='list'), | |
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), | |
tls_client_cert = dict(default=None), | |
tls_client_key = dict(default=None), | |
tls_verify = dict(default=None, type='bool'), | |
tls_ca_cert = dict(default=None), | |
tls_assert_hostname = dict(default=None, type='bool'), | |
username = dict(default=None), | |
password = dict(), | |
email = dict(), | |
registry = dict(), | |
hostname = dict(default=None), | |
env = dict(type='dict'), | |
dns = dict(), | |
detach = dict(default=True, type='bool'), | |
state = dict(default='running', choices=['absent', 'present', 'running', 'stopped', 'killed', 'restarted']), | |
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) | |
) | |
) | |
check_dependencies(module) | |
try: | |
manager = DockerManager(module) | |
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") | |
running_containers = manager.get_running_containers() | |
running_count = len(running_containers) | |
delta = count - running_count | |
deployed_containers = manager.get_deployed_containers() | |
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: | |
facts = manager.get_deployed_containers() | |
# stop and remove containers | |
elif state == "absent": | |
facts = manager.stop_containers(deployed_containers) | |
manager.remove_containers(deployed_containers) | |
# stop containers | |
elif state == "stopped": | |
facts = manager.stop_containers(running_containers) | |
# kill containers | |
elif state == "killed": | |
manager.kill_containers(running_containers) | |
# restart containers | |
elif state == "restarted": | |
manager.restart_containers(running_containers) | |
facts = manager.get_inspect_containers(running_containers) | |
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() | |
module.exit_json(failed=failed, changed=changed, msg=msg, ansible_facts=_ansible_facts(facts)) | |
except DockerAPIError, e: | |
changed = manager.has_changed() | |
module.exit_json(failed=True, changed=changed, msg="Docker API error: " + e.explanation) | |
except RequestException, e: | |
changed = manager.has_changed() | |
module.exit_json(failed=True, changed=changed, msg=repr(e)) | |
# import module snippets | |
from ansible.module_utils.basic import * | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment