From 1fe45c4f6c76215a3e890fee6fae420e8c4335b9 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 13 Oct 2013 15:07:27 -0700 Subject: [PATCH 1/9] add docker module --- library/cloud/docker | 390 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 library/cloud/docker diff --git a/library/cloud/docker b/library/cloud/docker new file mode 100644 index 0000000000..196170484c --- /dev/null +++ b/library/cloud/docker @@ -0,0 +1,390 @@ +#!/usr/bin/env python +# +# The MIT License (MIT) +# +# Copyright (c) 2013 Cove Schneider +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +DOCUMENTATION = ''' +--- +module: docker +short_description: manage docker containers +description: + - Manage the life cycle of docker containers. This module has a dependency on the docker-py python module. +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: [] + ports: + description: + - Set private to public port mapping specification (e.g. ports=22,80 or ports=:8080 maps 8080 directly to host) + required: false + default: null + aliases: [] + 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: [] + 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 + 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", "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: [] +author: Cove Schneider +''' + +try: + import sys + import json + import docker.client + from requests.exceptions import * + from urlparse import urlparse +except ImportError, e: + print "failed=True msg='failed to import python module: %s'" % e + sys.exit(1) + +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 {"DockerContainers": container_list} + +class AnsibleDocker: + + counters = {'created':0, 'started':0, 'stopped':0, 'killed':0, 'removed':0, 'restarted':0, 'pull':0} + + def __init__(self, module): + self.module = module + + # connect to docker server + docker_url = urlparse(module.params.get('docker_url')) + self.client = docker.Client(base_url=docker_url.geturl()) + + 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_deployed_containers(self): + # determine which images/commands are running already + containers = self.client.containers() + image = self.module.params.get('image') + command = self.module.params.get('command') + deployed = [] + + for i in containers: + if i["Image"].split(":")[0] == image.split(":")[0] and (not command or i["Command"].strip() == command.strip()): + details = self.client.inspect_container(i['Id']) + # XXX: some quirk in docker + if 'ID' in details: + details['Id'] = details['ID'] + del details['ID'] + deployed.append(details) + + return deployed + + def get_running_containers(self): + running = [] + for i in self.get_deployed_containers(): + if i['State']['Running'] == True: + running.append(i) + + return running + + def create_containers(self, count=1): + params = {'image': self.module.params.get('image'), + 'command': self.module.params.get('command'), + 'volumes_from': self.module.params.get('volumes_from'), + 'mem_limit': _human_to_bytes(self.module.params.get('memory_limit')), + 'environment': self.module.params.get('env'), + 'dns': self.module.params.get('dns'), + 'hostname': self.module.params.get('hostname'), + 'detach': self.module.params.get('detach'), + 'privileged': self.module.params.get('privileged'), + } + + if self.module.params.get('ports'): + params['ports'] = self.module.params.get('ports').split(",") + + def do_create(count, params): + results = [] + for i 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): + binds = None + if self.module.params.get('volumes'): + binds = {} + vols = self.module.params.get('volumes').split(" ") + for vol in vols: + parts = vol.split(":") + binds[parts[0]] = parts[1] + + lxc_conf = None + if self.module.params.get('lxc_conf'): + lxc_conf = [] + options = self.module.params.get('lxc_conf').split(" ") + for option in options: + parts = option.split(':') + lxc_conf.append({"Key": parts[0], "Value": parts[1]}) + + for i in containers: + self.client.start(i['Id'], lxc_conf=lxc_conf, binds=binds) + 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 main(): + module = AnsibleModule( + argument_spec = dict( + count = dict(default=1), + image = dict(required=True), + command = dict(required=False, default=None), + ports = dict(required=False, default=None), + volumes = dict(default=None), + volumes_from = dict(default=None), + 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(), + dns = dict(), + detach = dict(default=True, type='bool'), + state = dict(default='present', choices=['absent', 'present', 'stopped', 'killed', 'restarted']), + debug = dict(default=False, type='bool'), + privileged = dict(default=False, type='bool'), + lxc_conf = dict(default=None) + ) + ) + + try: + docker_client = AnsibleDocker(module) + state = module.params.get('state') + count = int(module.params.get('count')) + + if count < 1: + module.fail_json(msg="Count must be positive number") + + running_containers = docker_client.get_running_containers() + running_count = len(running_containers) + delta = count - running_count + deployed_containers = docker_client.get_deployed_containers() + facts = None + failed = False + changed = False + + # start/stop containers + if state == "present": + + # start more containers if we don't have enough + if delta > 0: + containers = docker_client.create_containers(delta) + docker_client.start_containers(containers) + + # stop containers if we have too many + elif delta < 0: + docker_client.stop_containers(running_containers[0:abs(delta)]) + docker_client.remove_containers(running_containers[0:abs(delta)]) + + facts = docker_client.get_running_containers() + + # stop and remove containers + elif state == "absent": + facts = docker_client.stop_containers(deployed_containers) + docker_client.remove_containers(containers) + + # stop containers + elif state == "stopped": + facts = docker_client.stop_containers(running_containers) + + # kill containers + elif state == "killed": + docker_client.kill_containers(running_containers) + + # restart containers + elif state == "restarted": + docker_client.restart_containers(running_containers) + + msg = "%s container(s) running image %s with command %s" % \ + (docker_client.get_summary_counters_msg(), module.params.get('image'), module.params.get('command')) + changed = docker_client.has_changed() + + module.exit_json(failed=failed, changed=changed, msg=msg, ansible_facts=_ansible_facts(facts)) + + except docker.client.APIError as e: + changed = docker_client.has_changed() + module.exit_json(failed=True, changed=changed, msg="Docker API error: " + e.explanation) + + except RequestException as e: + changed = docker_client.has_changed() + module.exit_json(failed=True, changed=changed, msg=repr(e)) + +# this is magic, see lib/ansible/module_common.py +#<> + +main() From fbca1ebc047c43301c973ef3e8b6bb14eddb7014 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 13 Oct 2013 20:34:14 -0700 Subject: [PATCH 2/9] add example docs --- library/cloud/docker | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/library/cloud/docker b/library/cloud/docker index 196170484c..38b6a66420 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -136,6 +136,50 @@ options: author: Cove Schneider ''' +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 $DockerContainers: + +- 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="Mapped to {{inventory_hostname}}:{{item.NetworkSettings.PortMapping.Tcp['8080']}}" + with_items: $DockerContainers + +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="Mapped to {{inventory_hostname}}:{{DockerContainers[{{item}}].NetworkSettings.PortMapping.Tcp['8080']}}" + with_sequence: start=0 end={{start_containers_count - 1}} + +Stop and remove all of the running tomcat containers: + +- hosts: web + sudo: yes + tasks: + - name: stop tomcat servers + docker: image=centos command="service tomcat6 start" state=absent +''' + try: import sys import json From c51981b5b7d56c6a9ecfa1984584990c5e392fb5 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 13 Oct 2013 21:14:27 -0700 Subject: [PATCH 3/9] add python requirements to docs --- library/cloud/docker | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/cloud/docker b/library/cloud/docker index 38b6a66420..17de1fd36d 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -27,7 +27,7 @@ DOCUMENTATION = ''' module: docker short_description: manage docker containers description: - - Manage the life cycle of docker containers. This module has a dependency on the docker-py python module. + - Manage the life cycle of docker containers. options: count: description: @@ -133,6 +133,7 @@ options: required: false default: aliases: [] +requirements: [ "docker-py" ] author: Cove Schneider ''' From 618004f87bcf64e3744e7b9fae71b6cb856f737f Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 13 Oct 2013 21:34:58 -0700 Subject: [PATCH 4/9] clean up a few warnings --- library/cloud/docker | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/cloud/docker b/library/cloud/docker index 17de1fd36d..5862f6b842 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -27,7 +27,7 @@ DOCUMENTATION = ''' module: docker short_description: manage docker containers description: - - Manage the life cycle of docker containers. + - Manage the life cycle of docker containers. This module has a dependency on the docker-py python module. options: count: description: @@ -133,7 +133,6 @@ options: required: false default: aliases: [] -requirements: [ "docker-py" ] author: Cove Schneider ''' @@ -183,7 +182,6 @@ Stop and remove all of the running tomcat containers: try: import sys - import json import docker.client from requests.exceptions import * from urlparse import urlparse @@ -282,7 +280,7 @@ class AnsibleDocker: def do_create(count, params): results = [] - for i in range(count): + for _ in range(count): result = self.client.create_container(**params) self.increment_counter('created') results.append(result) From a4922bc74ccc3697f89b78492d7a1c06b4365195 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 13 Oct 2013 21:36:42 -0700 Subject: [PATCH 5/9] add requirements back --- library/cloud/docker | 1 + 1 file changed, 1 insertion(+) diff --git a/library/cloud/docker b/library/cloud/docker index 5862f6b842..6414b29ed6 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -134,6 +134,7 @@ options: default: aliases: [] author: Cove Schneider +requirements: [ "docker-py" ] ''' EXAMPLES = ''' From 565db64a0471ac8f352ec164b248b107120fc669 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 13 Oct 2013 21:37:42 -0700 Subject: [PATCH 6/9] remove comment about requirements --- library/cloud/docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/cloud/docker b/library/cloud/docker index 6414b29ed6..1c9e6c61e6 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -27,7 +27,7 @@ DOCUMENTATION = ''' module: docker short_description: manage docker containers description: - - Manage the life cycle of docker containers. This module has a dependency on the docker-py python module. + - Manage the life cycle of docker containers. options: count: description: From 5f7e1fc3069442eb46ec34ca4214364781e7b627 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Mon, 14 Oct 2013 13:49:53 -0700 Subject: [PATCH 7/9] Un-camelcase DockerContainers, rename class to DockerManager(). --- library/cloud/docker | 49 +++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/library/cloud/docker b/library/cloud/docker index 1c9e6c61e6..e1a9b27f61 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -148,7 +148,7 @@ on the host: 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 $DockerContainers: +mapped to using docker_containers: - hosts: web sudo: yes @@ -157,7 +157,7 @@ mapped to using $DockerContainers: docker: image=centos command="service tomcat6 start" ports=8080 count=5 - name: Display IP address and port mappings for containers debug: msg="Mapped to {{inventory_hostname}}:{{item.NetworkSettings.PortMapping.Tcp['8080']}}" - with_items: $DockerContainers + with_items: docker_containers Just as in the previous example, but iterates over the list of docker containers with a sequence: @@ -169,16 +169,19 @@ Just as in the previous example, but iterates over the list of docker containers - 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="Mapped to {{inventory_hostname}}:{{DockerContainers[{{item}}].NetworkSettings.PortMapping.Tcp['8080']}}" + debug: msg="Mapped to {{inventory_hostname}}:{{docker_containers[{{item}}].NetworkSettings.PortMapping.Tcp['8080']}}" with_sequence: start=0 end={{start_containers_count - 1}} -Stop and remove all of the running tomcat containers: +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 ''' try: @@ -208,9 +211,9 @@ def _human_to_bytes(number): sys.exit(1) def _ansible_facts(container_list): - return {"DockerContainers": container_list} + return {"docker_containers": container_list} -class AnsibleDocker: +class DockerManager: counters = {'created':0, 'started':0, 'stopped':0, 'killed':0, 'removed':0, 'restarted':0, 'pull':0} @@ -367,17 +370,17 @@ def main(): ) try: - docker_client = AnsibleDocker(module) + manager = DockerManager(module) state = module.params.get('state') count = int(module.params.get('count')) if count < 1: module.fail_json(msg="Count must be positive number") - running_containers = docker_client.get_running_containers() + running_containers = manager.get_running_containers() running_count = len(running_containers) delta = count - running_count - deployed_containers = docker_client.get_deployed_containers() + deployed_containers = manager.get_deployed_containers() facts = None failed = False changed = False @@ -387,45 +390,45 @@ def main(): # start more containers if we don't have enough if delta > 0: - containers = docker_client.create_containers(delta) - docker_client.start_containers(containers) + containers = manager.create_containers(delta) + manager.start_containers(containers) # stop containers if we have too many elif delta < 0: - docker_client.stop_containers(running_containers[0:abs(delta)]) - docker_client.remove_containers(running_containers[0:abs(delta)]) + manager.stop_containers(running_containers[0:abs(delta)]) + manager.remove_containers(running_containers[0:abs(delta)]) - facts = docker_client.get_running_containers() + facts = manager.get_running_containers() # stop and remove containers elif state == "absent": - facts = docker_client.stop_containers(deployed_containers) - docker_client.remove_containers(containers) + facts = manager.stop_containers(deployed_containers) + manager.remove_containers(containers) # stop containers elif state == "stopped": - facts = docker_client.stop_containers(running_containers) + facts = manager.stop_containers(running_containers) # kill containers elif state == "killed": - docker_client.kill_containers(running_containers) + manager.kill_containers(running_containers) # restart containers elif state == "restarted": - docker_client.restart_containers(running_containers) + manager.restart_containers(running_containers) msg = "%s container(s) running image %s with command %s" % \ - (docker_client.get_summary_counters_msg(), module.params.get('image'), module.params.get('command')) - changed = docker_client.has_changed() + (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 as e: - changed = docker_client.has_changed() + changed = manager.has_changed() module.exit_json(failed=True, changed=changed, msg="Docker API error: " + e.explanation) except RequestException as e: - changed = docker_client.has_changed() + changed = manager.has_changed() module.exit_json(failed=True, changed=changed, msg=repr(e)) # this is magic, see lib/ansible/module_common.py From 94574f72966fee4d61bc36d889f7c45e55109fbb Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Wed, 23 Oct 2013 22:56:02 -0700 Subject: [PATCH 8/9] update with current fixes from docker-ansible --- library/cloud/docker | 108 ++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 33 deletions(-) diff --git a/library/cloud/docker b/library/cloud/docker index e1a9b27f61..dbfb4bed8f 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -213,6 +213,13 @@ def _human_to_bytes(number): 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} @@ -220,10 +227,46 @@ class DockerManager: 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').split(" ") + 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').split(" ") + for option in options: + parts = option.split(':') + self.lxc_conf.append({"Key": parts[0], "Value": parts[1]}) + + self.ports = None + if self.module.params.get('ports'): + self.ports = self.module.params.get('ports').split(",") + # connect to docker server docker_url = urlparse(module.params.get('docker_url')) self.client = docker.Client(base_url=docker_url.geturl()) + + def get_split_image_tag(self, image): + 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(): @@ -240,21 +283,36 @@ class DockerManager: 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() image = self.module.params.get('image') command = self.module.params.get('command') + if command: + command = command.strip() 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: - if i["Image"].split(":")[0] == image.split(":")[0] and (not command or i["Command"].strip() == command.strip()): + running_image, running_tag = self.get_split_image_tag(image) + running_command = i['Command'].strip() + + if running_image == image and (not tag or tag == running_tag) and (not command or running_command == command): details = self.client.inspect_container(i['Id']) - # XXX: some quirk in docker - if 'ID' in details: - details['Id'] = details['ID'] - del details['ID'] + details = _docker_id_quirk(details) deployed.append(details) return deployed @@ -262,7 +320,7 @@ class DockerManager: def get_running_containers(self): running = [] for i in self.get_deployed_containers(): - if i['State']['Running'] == True: + if i['State']['Running'] == True and i['State']['Ghost'] == False: running.append(i) return running @@ -270,6 +328,8 @@ class DockerManager: def create_containers(self, count=1): params = {'image': self.module.params.get('image'), 'command': self.module.params.get('command'), + 'ports': self.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.module.params.get('env'), @@ -279,9 +339,6 @@ class DockerManager: 'privileged': self.module.params.get('privileged'), } - if self.module.params.get('ports'): - params['ports'] = self.module.params.get('ports').split(",") - def do_create(count, params): results = [] for _ in range(count): @@ -301,24 +358,8 @@ class DockerManager: return containers def start_containers(self, containers): - binds = None - if self.module.params.get('volumes'): - binds = {} - vols = self.module.params.get('volumes').split(" ") - for vol in vols: - parts = vol.split(":") - binds[parts[0]] = parts[1] - - lxc_conf = None - if self.module.params.get('lxc_conf'): - lxc_conf = [] - options = self.module.params.get('lxc_conf').split(" ") - for option in options: - parts = option.split(':') - lxc_conf.append({"Key": parts[0], "Value": parts[1]}) - for i in containers: - self.client.start(i['Id'], lxc_conf=lxc_conf, binds=binds) + self.client.start(i['Id'], lxc_conf=self.lxc_conf, binds=self.binds) self.increment_counter('started') def stop_containers(self, containers): @@ -387,7 +428,7 @@ def main(): # start/stop containers if state == "present": - + # start more containers if we don't have enough if delta > 0: containers = manager.create_containers(delta) @@ -395,15 +436,15 @@ def main(): # stop containers if we have too many elif delta < 0: - manager.stop_containers(running_containers[0:abs(delta)]) - manager.remove_containers(running_containers[0:abs(delta)]) - + containers = manager.stop_containers(running_containers[0:abs(delta)]) + manager.remove_containers(containers) + facts = manager.get_running_containers() # stop and remove containers elif state == "absent": facts = manager.stop_containers(deployed_containers) - manager.remove_containers(containers) + manager.remove_containers(deployed_containers) # stop containers elif state == "stopped": @@ -415,8 +456,9 @@ def main(): # restart containers elif state == "restarted": - manager.restart_containers(running_containers) - + 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() From 1ea17dee117b8ddd4549c844e44ba35cd222a353 Mon Sep 17 00:00:00 2001 From: Cove Schneider Date: Sun, 3 Nov 2013 12:48:03 -0800 Subject: [PATCH 9/9] Fixes from docker-ansible, update license to be same as other modules --- library/cloud/docker | 51 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/library/cloud/docker b/library/cloud/docker index dbfb4bed8f..b9afb093d3 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -1,26 +1,24 @@ #!/usr/bin/env python # -# The MIT License (MIT) + +# (c) 2013, Cove Schneider # -# Copyright (c) 2013 Cove Schneider +# This file is part of Ansible, # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: +# 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. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +###################################################################### DOCUMENTATION = ''' --- @@ -98,7 +96,7 @@ options: aliases: [] env: description: - - Set environment variables + - Set environment variables (e.g. env="PASSWORD=sEcRe7,WORKERS=4") required: false default: null aliases: [] @@ -156,7 +154,7 @@ mapped to using docker_containers: - 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="Mapped to {{inventory_hostname}}:{{item.NetworkSettings.PortMapping.Tcp['8080']}}" + debug: msg={{inventory_hostname}}:{{item.NetworkSettings.Ports['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: @@ -169,7 +167,7 @@ Just as in the previous example, but iterates over the list of docker containers - 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="Mapped to {{inventory_hostname}}:{{docker_containers[{{item}}].NetworkSettings.PortMapping.Tcp['8080']}}" + debug: msg={{inventory_hostname}}:{{docker_containers[{{item}}].NetworkSettings.Ports['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: @@ -255,6 +253,10 @@ class DockerManager: if self.module.params.get('ports'): self.ports = self.module.params.get('ports').split(",") + self.env = None + if self.module.params.get('env'): + self.env = dict(map(lambda x: x.split("="), self.module.params.get('env').split(","))) + # connect to docker server docker_url = urlparse(module.params.get('docker_url')) self.client = docker.Client(base_url=docker_url.geturl()) @@ -307,7 +309,7 @@ class DockerManager: image, tag = self.get_split_image_tag(image) for i in containers: - running_image, running_tag = self.get_split_image_tag(image) + running_image, running_tag = self.get_split_image_tag(i['Image']) running_command = i['Command'].strip() if running_image == image and (not tag or tag == running_tag) and (not command or running_command == command): @@ -332,7 +334,7 @@ class DockerManager: 'volumes': self.volumes, 'volumes_from': self.module.params.get('volumes_from'), 'mem_limit': _human_to_bytes(self.module.params.get('memory_limit')), - 'environment': self.module.params.get('env'), + 'environment': self.env, 'dns': self.module.params.get('dns'), 'hostname': self.module.params.get('hostname'), 'detach': self.module.params.get('detach'), @@ -436,8 +438,9 @@ def main(): # stop containers if we have too many elif delta < 0: - containers = manager.stop_containers(running_containers[0:abs(delta)]) - manager.remove_containers(containers) + 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()