mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2024-09-14 20:13:21 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			736 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			736 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python
 | 
						|
 | 
						|
# (c) 2013, Cove Schneider
 | 
						|
# (c) 2014, Joshua Conner <joshua.conner@gmail.com>
 | 
						|
# (c) 2014, Pavel Antonov <antonov@adwz.ru>
 | 
						|
#
 | 
						|
# 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: []
 | 
						|
  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"
 | 
						|
author: Cove Schneider, Joshua Conner, Pavel Antonov
 | 
						|
requirements: [ "docker-py >= 0.3.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
 | 
						|
'''
 | 
						|
 | 
						|
HAS_DOCKER_PY = True
 | 
						|
 | 
						|
import sys
 | 
						|
from urlparse import urlparse
 | 
						|
try:
 | 
						|
    import docker.client
 | 
						|
    from requests.exceptions import *
 | 
						|
except ImportError, e:
 | 
						|
    HAS_DOCKER_PY = False
 | 
						|
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
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]
 | 
						|
                # 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 = dict(map(lambda x: x.split(':'), self.module.params.get('links')))
 | 
						|
 | 
						|
        self.env = None
 | 
						|
        if self.module.params.get('env'):
 | 
						|
            self.env = dict(map(lambda x: x.split("="), self.module.params.get('env')))
 | 
						|
 | 
						|
        # connect to docker server
 | 
						|
        docker_url = urlparse(module.params.get('docker_url'))
 | 
						|
        self.client = docker.Client(base_url=docker_url.geturl())
 | 
						|
 | 
						|
 | 
						|
    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:
 | 
						|
                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_split_image_tag(self, image):
 | 
						|
        if '/' in image:
 | 
						|
            image = image.split('/')[1]
 | 
						|
        tag = None
 | 
						|
        if image.find(':') > 0:
 | 
						|
            return image.split(':')
 | 
						|
        else:
 | 
						|
            return image, tag
 | 
						|
 | 
						|
    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
 | 
						|
        containers = self.client.containers(all=True)
 | 
						|
        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 = self.get_split_image_tag(image)
 | 
						|
 | 
						|
        for i in containers:
 | 
						|
            running_image, running_tag = self.get_split_image_tag(i['Image'])
 | 
						|
            running_command = i['Command'].strip()
 | 
						|
 | 
						|
            if (name and name in i['Names']) or \
 | 
						|
                    (not name and running_image == image and (not tag or tag == running_tag) and
 | 
						|
                    (not command or running_command == command)):
 | 
						|
                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']['Ghost'] == 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,
 | 
						|
                  'volumes_from': self.module.params.get('volumes_from'),
 | 
						|
                  'mem_limit':    _human_to_bytes(self.module.params.get('memory_limit')),
 | 
						|
                  'environment':  self.env,
 | 
						|
                  'dns':          self.module.params.get('dns'),
 | 
						|
                  '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'),
 | 
						|
                  }
 | 
						|
 | 
						|
        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:
 | 
						|
            self.client.pull(params['image'])
 | 
						|
            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,
 | 
						|
        }
 | 
						|
        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'),
 | 
						|
            user            = dict(default=None),
 | 
						|
            password        = dict(),
 | 
						|
            email           = dict(),
 | 
						|
            hostname        = dict(default=None),
 | 
						|
            env             = dict(type='list'),
 | 
						|
            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)
 | 
						|
        )
 | 
						|
    )
 | 
						|
 | 
						|
    check_dependencies(module)
 | 
						|
 | 
						|
    try:
 | 
						|
        manager = DockerManager(module)
 | 
						|
        state = module.params.get('state')
 | 
						|
        count = int(module.params.get('count'))
 | 
						|
        name = module.params.get('name')
 | 
						|
 | 
						|
        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 and "/" + name not in map(lambda x: x.get('Name'), deployed_containers):
 | 
						|
                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:
 | 
						|
                acts = 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 docker.client.APIError, 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 *
 | 
						|
 | 
						|
main()
 |