diff --git a/plugins/modules/docker_compose.py b/plugins/modules/docker_compose.py deleted file mode 100644 index e02cb7e..0000000 --- a/plugins/modules/docker_compose.py +++ /dev/null @@ -1,1169 +0,0 @@ -# -*- coding: utf-8 -*-# -# Copyright 2016 Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""Module for Docker Compose.""" - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = """ - -module: docker_compose - -short_description: Manage multi-container Docker applications with Docker Compose. - - -author: "Chris Houseknecht (@chouseknecht)" - -description: - - Uses Docker Compose to start, shutdown and scale services. - - Works with compose versions 1 and 2. - - Configuration can be read from a C(docker-compose.yml) or C(docker-compose.yaml) file or inline using the I(definition) option. - - See the examples for more details. - - Supports check mode. - - This module was called C(docker_service) before Ansible 2.8. The usage did not change. - -options: - project_src: - description: - - Path to a directory containing a C(docker-compose.yml) or C(docker-compose.yaml) file. - - Mutually exclusive with I(definition). - - Required when no I(definition) is provided. - type: path - project_name: - description: - - Provide a project name. If not provided, the project name is taken from the basename of I(project_src). - - Required when I(definition) is provided. - type: str - files: - description: - - List of Compose file names relative to I(project_src). Overrides C(docker-compose.yml) or C(docker-compose.yaml). - - Files are loaded and merged in the order given. - type: list - elements: path - state: - description: - - Desired state of the project. - - Specifying C(present) is the same as running C(docker-compose up) resp. C(docker-compose stop) (with I(stopped)) resp. C(docker-compose restart) - (with I(restarted)). - - Specifying C(absent) is the same as running C(docker-compose down). - type: str - default: present - choices: - - absent - - present - services: - description: - - When I(state) is C(present) run C(docker-compose up) resp. C(docker-compose stop) (with I(stopped)) resp. C(docker-compose restart) (with I(restarted)) - on a subset of services. - - If empty, which is the default, the operation will be performed on all services defined in the Compose file (or inline I(definition)). - type: list - elements: str - scale: - description: - - When I(state) is C(present) scale services. Provide a dictionary of key/value pairs where the key - is the name of the service and the value is an integer count for the number of containers. - type: dict - dependencies: - description: - - When I(state) is C(present) specify whether or not to include linked services. - type: bool - default: yes - definition: - description: - - Compose file describing one or more services, networks and volumes. - - Mutually exclusive with I(project_src) and I(files). - type: dict - hostname_check: - description: - - Whether or not to check the Docker daemon"s hostname against the name provided in the client certificate. - type: bool - default: no - recreate: - description: - - By default containers will be recreated when their configuration differs from the service definition. - - Setting to C(never) ignores configuration differences and leaves existing containers unchanged. - - Setting to C(always) forces recreation of all existing containers. - type: str - default: smart - choices: - - always - - never - - smart - build: - description: - - Use with I(state) C(present) to always build images prior to starting the application. - - Same as running C(docker-compose build) with the pull option. - - Images will only be rebuilt if Docker detects a change in the Dockerfile or build directory contents. - - Use the I(nocache) option to ignore the image cache when performing the build. - - If an existing image is replaced, services using the image will be recreated unless I(recreate) is C(never). - type: bool - default: no - pull: - description: - - Use with I(state) C(present) to always pull images prior to starting the application. - - Same as running C(docker-compose pull). - - When a new image is pulled, services using the image will be recreated unless I(recreate) is C(never). - type: bool - default: no - nocache: - description: - - Use with the I(build) option to ignore the cache during the image build process. - type: bool - default: no - remove_images: - description: - - Use with I(state) C(absent) to remove all images or only local images. - type: str - choices: - - "all" - - "local" - remove_volumes: - description: - - Use with I(state) C(absent) to remove data volumes. - type: bool - default: no - stopped: - description: - - Use with I(state) C(present) to stop all containers defined in the Compose file. - - If I(services) is defined, only the containers listed there will be stopped. - type: bool - default: no - restarted: - description: - - Use with I(state) C(present) to restart all containers defined in the Compose file. - - If I(services) is defined, only the containers listed there will be restarted. - type: bool - default: no - remove_orphans: - description: - - Remove containers for services not defined in the Compose file. - type: bool - default: no - timeout: - description: - - Timeout in seconds for container shutdown when attached or when containers are already running. - type: int - default: 10 - use_ssh_client: - description: - - Currently ignored for this module, but might suddenly be supported later on. - -extends_documentation_fragment: - - community.docker.docker - - community.docker.docker.docker_py_1_documentation - - -requirements: - - "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)" - - "docker-compose >= 1.7.0" - - "Docker API >= 1.20" - - "PyYAML >= 3.11" -""" # noqa - -EXAMPLES = """ -# Examples use the django example at https://docs.docker.com/compose/django. Follow it to create the -# flask directory - -- name: Run using a project directory - hosts: localhost - gather_facts: no - tasks: - - name: Tear down existing services - community.docker.docker_compose: - project_src: flask - state: absent - - - name: Create and start services - community.docker.docker_compose: - project_src: flask - register: output - - - ansible.builtin.debug: - var: output - - - name: Run `docker-compose up` again - community.docker.docker_compose: - project_src: flask - build: no - register: output - - - ansible.builtin.debug: - var: output - - - ansible.builtin.assert: - that: "not output.changed " - - - name: Stop all services - community.docker.docker_compose: - project_src: flask - build: no - stopped: yes - register: output - - - ansible.builtin.debug: - var: output - - - ansible.builtin.assert: - that: - - "not web.flask_web_1.state.running" - - "not db.flask_db_1.state.running" - - - name: Restart services - community.docker.docker_compose: - project_src: flask - build: no - restarted: yes - register: output - - - ansible.builtin.debug: - var: output - - - ansible.builtin.assert: - that: - - "web.flask_web_1.state.running" - - "db.flask_db_1.state.running" - -- name: Scale the web service to 2 - hosts: localhost - gather_facts: no - tasks: - - community.docker.docker_compose: - project_src: flask - scale: - web: 2 - register: output - - - ansible.builtin.debug: - var: output - -- name: Run with inline v2 compose - hosts: localhost - gather_facts: no - tasks: - - community.docker.docker_compose: - project_src: flask - state: absent - - - community.docker.docker_compose: - project_name: flask - definition: - version: "2" - services: - db: - image: postgres - web: - build: "{{ playbook_dir }}/flask" - command: "python manage.py runserver 0.0.0.0:8000" - volumes: - - "{{ playbook_dir }}/flask:/code" - ports: - - "8000:8000" - depends_on: - - db - register: output - - - ansible.builtin.debug: - var: output - - - ansible.builtin.assert: - that: - - "web.flask_web_1.state.running" - - "db.flask_db_1.state.running" - -- name: Run with inline v1 compose - hosts: localhost - gather_facts: no - tasks: - - community.docker.docker_compose: - project_src: flask - state: absent - - - community.docker.docker_compose: - project_name: flask - definition: - db: - image: postgres - web: - build: "{{ playbook_dir }}/flask" - command: "python manage.py runserver 0.0.0.0:8000" - volumes: - - "{{ playbook_dir }}/flask:/code" - ports: - - "8000:8000" - links: - - db - register: output - - - ansible.builtin.debug: - var: output - - - ansible.builtin.assert: - that: - - "web.flask_web_1.state.running" - - "db.flask_db_1.state.running" -""" # noqa - -RETURN = """ -services: - description: - - A dictionary mapping the service"s name to a dictionary of containers. - returned: success - type: complex - contains: - container_name: - description: Name of the container. Format is C(project_service_#). - returned: success - type: complex - contains: - cmd: - description: One or more commands to be executed in the container. - returned: success - type: list - elements: str - example: ["postgres"] - image: - description: Name of the image from which the container was built. - returned: success - type: str - example: postgres - labels: - description: Meta data assigned to the container. - returned: success - type: dict - example: {...} - networks: - description: Contains a dictionary for each network to which the container is a member. - returned: success - type: list - elements: dict - contains: - IPAddress: - description: The IP address assigned to the container. - returned: success - type: str - example: 172.17.0.2 - IPPrefixLen: - description: Number of bits used by the subnet. - returned: success - type: int - example: 16 - aliases: - description: Aliases assigned to the container by the network. - returned: success - type: list - elements: str - example: ["db"] - globalIPv6: - description: IPv6 address assigned to the container. - returned: success - type: str - example: "" - globalIPv6PrefixLen: - description: IPv6 subnet length. - returned: success - type: int - example: 0 - links: - description: List of container names to which this container is linked. - returned: success - type: list - elements: str - example: null - macAddress: - description: Mac Address assigned to the virtual NIC. - returned: success - type: str - example: "02:42:ac:11:00:02" - state: - description: Information regarding the current disposition of the container. - returned: success - type: dict - contains: - running: - description: Whether or not the container is up with a running process. - returned: success - type: bool - example: true - status: - description: Description of the running state. - returned: success - type: str - example: running - -actions: - description: Provides the actions to be taken on each service as determined by compose. - returned: when in check mode or I(debug) is C(yes) - type: complex - contains: - service_name: - description: Name of the service. - returned: always - type: complex - contains: - pulled_image: - description: Provides image details when a new image is pulled for the service. - returned: on image pull - type: complex - contains: - name: - description: name of the image - returned: always - type: str - id: - description: image hash - returned: always - type: str - built_image: - description: Provides image details when a new image is built for the service. - returned: on image build - type: complex - contains: - name: - description: name of the image - returned: always - type: str - id: - description: image hash - returned: always - type: str - - action: - description: A descriptive name of the action to be performed on the service"s containers. - returned: always - type: list - elements: str - contains: - id: - description: the container"s long ID - returned: always - type: str - name: - description: the container"s name - returned: always - type: str - short_id: - description: the container"s short ID - returned: always - type: str -""" # noqa - -import os -import re -import sys -import tempfile -import traceback -from contextlib import contextmanager -from distutils.version import LooseVersion - -try: - import yaml - HAS_YAML = True - HAS_YAML_EXC = None -except ImportError: - HAS_YAML = False - HAS_YAML_EXC = traceback.format_exc() - -try: - from docker.errors import DockerException -except ImportError: - # missing Docker SDK for Python handled in ansible.module_utils.docker.common - pass - -try: - from compose import __version__ as compose_version - from compose.cli.command import project_from_options - from compose.service import NoSuchImageError - from compose.cli.main import convergence_strategy_from_opts - from compose.cli.main import build_action_from_opts - from compose.cli.main import image_type_from_opt - from compose.const import DEFAULT_TIMEOUT, LABEL_SERVICE, LABEL_PROJECT, LABEL_ONE_OFF - HAS_COMPOSE = True - HAS_COMPOSE_EXC = None - MINIMUM_COMPOSE_VERSION = "1.7.0" -except ImportError: - HAS_COMPOSE = False - HAS_COMPOSE_EXC = traceback.format_exc() - DEFAULT_TIMEOUT = 10 - -from ansible.module_utils._text import to_native - -from ansible_collections.community.docker.plugins.module_utils.common import ( - AnsibleDockerClient, - DockerBaseClass, - RequestException, -) - -AUTH_PARAM_MAPPING = { - u"docker_host": u"--host", - u"tls": u"--tls", - u"cacert_path": u"--tlscacert", - u"cert_path": u"--tlscert", - u"key_path": u"--tlskey", - u"tls_verify": u"--tlsverify" -} - - -@contextmanager -def stdout_redirector(path_name): - old_stdout = sys.stdout - fd = open(path_name, "w") - sys.stdout = fd - try: - yield - finally: - sys.stdout = old_stdout - - -@contextmanager -def stderr_redirector(path_name): - old_fh = sys.stderr - fd = open(path_name, "w") - sys.stderr = fd - try: - yield - finally: - sys.stderr = old_fh - - -def make_redirection_tempfiles(): - dummy, out_redir_name = tempfile.mkstemp(prefix="ansible") - dummy, err_redir_name = tempfile.mkstemp(prefix="ansible") - return (out_redir_name, err_redir_name) - - -def cleanup_redirection_tempfiles(out_name, err_name): - for i in [out_name, err_name]: - os.remove(i) - - -def get_redirected_output(path_name): - output = [] - with open(path_name, "r") as fd: - for line in fd: - # strip terminal format/color chars - new_line = re.sub(r"\x1b\[.+m", "", line) - output.append(new_line) - os.remove(path_name) - return output - - -def attempt_extract_errors(exc_str, stdout, stderr): - errors = [line.strip() for line in stderr if line.strip().startswith("ERROR:")] - errors.extend([line.strip() for line in stdout if line.strip().startswith("ERROR:")]) - - warnings = [line.strip() for line in stderr if line.strip().startswith("WARNING:")] - warnings.extend([line.strip() for line in stdout if line.strip().startswith("WARNING:")]) - - # assume either the exception body (if present) or the last warning was the "most" - # fatal. - - if exc_str.strip(): - msg = exc_str.strip() - elif errors: - msg = errors[-1].encode("utf-8") - else: - msg = "unknown cause" - - return { - "warnings": [w.encode("utf-8") for w in warnings], - "errors": [e.encode("utf-8") for e in errors], - "msg": msg, - "module_stderr": "".join(stderr), - "module_stdout": "".join(stdout) - } - - -def get_failure_info(exc, out_name, err_name=None, msg_format="{}"): - if err_name is None: - stderr = [] - else: - stderr = get_redirected_output(err_name) - stdout = get_redirected_output(out_name) - - reason = attempt_extract_errors(str(exc), stdout, stderr) - reason["msg"] = msg_format.format(reason["msg"]) - return reason - - -class ContainerManager(DockerBaseClass): - - def __init__(self, client): - - super(ContainerManager, self).__init__() - - self.client = client - self.project_src = None - self.files = None - self.project_name = None - self.state = None - self.definition = None - self.hostname_check = None - self.timeout = None - self.remove_images = None - self.remove_orphans = None - self.remove_volumes = None - self.stopped = None - self.restarted = None - self.recreate = None - self.build = None - self.dependencies = None - self.services = None - self.scale = None - self.debug = None - self.pull = None - self.nocache = None - - for key, value in client.module.params.items(): - setattr(self, key, value) - - self.check_mode = client.check_mode - - if not self.debug: - self.debug = client.module._debug - - self.options = dict() - self.options.update(self._get_auth_options()) - self.options[u"--skip-hostname-check"] = (not self.hostname_check) - - if self.project_name: - self.options[u"--project-name"] = self.project_name - - if self.files: - self.options[u"--file"] = self.files - - if not HAS_COMPOSE: - self.client.fail( - "Unable to load docker-compose. Try `pip install docker-compose`. Error: {}". - format(to_native(HAS_COMPOSE_EXC)) - ) - - if LooseVersion(compose_version) < LooseVersion(MINIMUM_COMPOSE_VERSION): - self.client.fail( - "Found docker-compose version {}. Minimum required version is {}. " - "Upgrade docker-compose to a min version of {}.".format( - compose_version, MINIMUM_COMPOSE_VERSION, MINIMUM_COMPOSE_VERSION - ) - ) - - if self.restarted and self.stopped: - self.client.fail("Cannot use restarted and stopped at the same time.") - - self.log("options: ") - self.log(self.options, pretty_print=True) - - if self.definition: - if not HAS_YAML: - self.client.fail( - "Unable to load yaml. Try `pip install PyYAML`. Error: {}".format( - to_native(HAS_YAML_EXC) - ) - ) - - if not self.project_name: - self.client.fail( - "Parameter error - project_name required when providing definition." - ) - - self.project_src = tempfile.mkdtemp(prefix="ansible") - compose_file = os.path.join(self.project_src, "docker-compose.yml") - try: - self.log("writing: ") - self.log(yaml.dump(self.definition, default_flow_style=False)) - with open(compose_file, "w") as f: - f.write(yaml.dump(self.definition, default_flow_style=False)) - except Exception as exc: - self.client.fail("Error writing to {} - {}".format(compose_file, to_native(exc))) - else: - if not self.project_src: - self.client.fail("Parameter error - project_src required.") - - try: - self.log("project_src: {}".format(self.project_src)) - self.project = project_from_options(self.project_src, self.options) - except Exception as exc: - self.client.fail("Configuration error - {}".format(to_native(exc))) - - def exec_module(self): - result = dict() - - if self.state == "present": - result = self.cmd_up() - elif self.state == "absent": - result = self.cmd_down() - - if self.definition: - compose_file = os.path.join(self.project_src, "docker-compose.yml") - self.log("removing {}".format(compose_file)) - os.remove(compose_file) - self.log("removing {}".format(self.project_src)) - os.rmdir(self.project_src) - - if not self.check_mode and not self.debug and result.get("actions"): - result.pop("actions") - - return result - - def _get_auth_options(self): - options = dict() - for key, value in self.client.auth_params.items(): - if value is not None: - option = AUTH_PARAM_MAPPING.get(key) - if option: - options[option] = value - return options - - def cmd_up(self): - - start_deps = self.dependencies - service_names = self.services - detached = True - result = dict(changed=False, actions=[], services=dict()) - - up_options = { - u"--no-recreate": False, - u"--build": False, - u"--no-build": False, - u"--no-deps": False, - u"--force-recreate": False, - } - - if self.recreate == "never": - up_options[u"--no-recreate"] = True - elif self.recreate == "always": - up_options[u"--force-recreate"] = True - - if self.remove_orphans: - up_options[u"--remove-orphans"] = True - - converge = convergence_strategy_from_opts(up_options) - self.log("convergence strategy: {}".format(converge)) - - if self.pull: - pull_output = self.cmd_pull() - result["changed"] = pull_output["changed"] - result["actions"] += pull_output["actions"] - - if self.build: - build_output = self.cmd_build() - result["changed"] = build_output["changed"] - result["actions"] += build_output["actions"] - - if self.remove_orphans: - containers = self.client.containers( - filters={ - "label": [ - "{0}={1}".format(LABEL_PROJECT, self.project.name), - "{0}={1}".format(LABEL_ONE_OFF, "False") - ], - } - ) - - orphans = [] - for container in containers: - service_name = container.get("Labels", {}).get(LABEL_SERVICE) - if service_name not in self.project.service_names: - orphans.append(service_name) - - if orphans: - result["changed"] = True - - for service in self.project.services: - if not service_names or service.name in service_names: - plan = service.convergence_plan(strategy=converge) - if plan.action != "noop": - result["changed"] = True - result_action = dict(service=service.name) - result_action[plan.action] = [] - for container in plan.containers: - result_action[plan.action].append( - dict( - id=container.id, - name=container.name, - short_id=container.short_id, - ) - ) - result["actions"].append(result_action) - - if not self.check_mode and result["changed"] and not self.stopped: - out_redir_name, err_redir_name = make_redirection_tempfiles() - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - do_build = build_action_from_opts(up_options) - self.log("Setting do_build to {}".format(do_build)) - self.project.up( - service_names=service_names, - start_deps=start_deps, - strategy=converge, - do_build=do_build, - detached=detached, - remove_orphans=self.remove_orphans, - timeout=self.timeout - ) - except Exception as exc: - fail_reason = get_failure_info( - exc, out_redir_name, err_redir_name, msg_format="Error starting project {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - - if self.stopped: - stop_output = self.cmd_stop(service_names) - result["changed"] = stop_output["changed"] - result["actions"] += stop_output["actions"] - - if self.restarted: - restart_output = self.cmd_restart(service_names) - result["changed"] = restart_output["changed"] - result["actions"] += restart_output["actions"] - - if self.scale: - scale_output = self.cmd_scale() - result["changed"] = scale_output["changed"] - result["actions"] += scale_output["actions"] - - for service in self.project.services: - service_facts = dict() - result["services"][service.name] = service_facts - for container in service.containers(stopped=True): - inspection = container.inspect() - # pare down the inspection data to the most useful bits - facts = dict( - cmd=[], - labels=dict(), - image=None, - state=dict(running=None, status=None), - networks=dict() - ) - if inspection["Config"].get("Cmd", None) is not None: - facts["cmd"] = inspection["Config"]["Cmd"] - if inspection["Config"].get("Labels", None) is not None: - facts["labels"] = inspection["Config"]["Labels"] - if inspection["Config"].get("Image", None) is not None: - facts["image"] = inspection["Config"]["Image"] - if inspection["State"].get("Running", None) is not None: - facts["state"]["running"] = inspection["State"]["Running"] - if inspection["State"].get("Status", None) is not None: - facts["state"]["status"] = inspection["State"]["Status"] - - if ( - inspection.get("NetworkSettings") - and inspection["NetworkSettings"].get("Networks") - ): - networks = inspection["NetworkSettings"]["Networks"] - for key in networks: - facts["networks"][key] = dict( - aliases=[], - globalIPv6=None, - globalIPv6PrefixLen=0, - IPAddress=None, - IPPrefixLen=0, - links=None, - macAddress=None, - ) - if networks[key].get("Aliases", None) is not None: - facts["networks"][key]["aliases"] = networks[key]["Aliases"] - if networks[key].get("GlobalIPv6Address", None) is not None: - facts["networks"][key]["globalIPv6"] = networks[key][ - "GlobalIPv6Address"] - if networks[key].get("GlobalIPv6PrefixLen", None) is not None: - facts["networks"][key]["globalIPv6PrefixLen"] = networks[key][ - "GlobalIPv6PrefixLen"] - if networks[key].get("IPAddress", None) is not None: - facts["networks"][key]["IPAddress"] = networks[key]["IPAddress"] - if networks[key].get("IPPrefixLen", None) is not None: - facts["networks"][key]["IPPrefixLen"] = networks[key]["IPPrefixLen"] - if networks[key].get("Links", None) is not None: - facts["networks"][key]["links"] = networks[key]["Links"] - if networks[key].get("MacAddress", None) is not None: - facts["networks"][key]["macAddress"] = networks[key]["MacAddress"] - - service_facts[container.name] = facts - - return result - - def cmd_pull(self): - result = dict( - changed=False, - actions=[], - ) - - if not self.check_mode: - for service in self.project.get_services(self.services, include_deps=False): - if "image" not in service.options: - continue - - self.log("Pulling image for service {}".format(service.name)) - # store the existing image ID - old_image_id = "" - try: - image = service.image() - if image and image.get("Id"): - old_image_id = image["Id"] - except NoSuchImageError: - pass - except Exception as exc: - self.client.fail( - "Error: service image lookup failed - {}".format(to_native(exc)) - ) - - out_redir_name, err_redir_name = make_redirection_tempfiles() - # pull the image - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - service.pull(ignore_pull_failures=False) - except Exception as exc: - fail_reason = get_failure_info( - exc, - out_redir_name, - err_redir_name, - msg_format="Error: pull failed with {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - - # store the new image ID - new_image_id = "" - try: - image = service.image() - if image and image.get("Id"): - new_image_id = image["Id"] - except NoSuchImageError as exc: - self.client.fail( - "Error: service image lookup failed after pull - {}".format( - to_native(exc) - ) - ) - - if new_image_id != old_image_id: - # if a new image was pulled - result["changed"] = True - result["actions"].append( - dict( - service=service.name, - pulled_image=dict(name=service.image_name, id=new_image_id) - ) - ) - return result - - def cmd_build(self): - result = dict(changed=False, actions=[]) - if not self.check_mode: - for service in self.project.get_services(self.services, include_deps=False): - if service.can_be_built(): - self.log("Building image for service {}".format(service.name)) - # store the existing image ID - old_image_id = "" - try: - image = service.image() - if image and image.get("Id"): - old_image_id = image["Id"] - except NoSuchImageError: - pass - except Exception as exc: - self.client.fail( - "Error: service image lookup failed - {}".format(to_native(exc)) - ) - - out_redir_name, err_redir_name = make_redirection_tempfiles() - # build the image - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - new_image_id = service.build(pull=self.pull, no_cache=self.nocache) - except Exception as exc: - fail_reason = get_failure_info( - exc, - out_redir_name, - err_redir_name, - msg_format="Error: build failed with {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - - if new_image_id not in old_image_id: - # if a new image was built - result["changed"] = True - result["actions"].append( - dict( - service=service.name, - built_image=dict(name=service.image_name, id=new_image_id) - ) - ) - return result - - def cmd_down(self): - result = dict(changed=False, actions=[]) - for service in self.project.services: - containers = service.containers(stopped=True) - if len(containers): - result["changed"] = True - result["actions"].append( - dict(service=service.name, deleted=[container.name for container in containers]) - ) - if not self.check_mode and result["changed"]: - image_type = image_type_from_opt("--rmi", self.remove_images) - out_redir_name, err_redir_name = make_redirection_tempfiles() - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - self.project.down(image_type, self.remove_volumes, self.remove_orphans) - except Exception as exc: - fail_reason = get_failure_info( - exc, out_redir_name, err_redir_name, msg_format="Error stopping project - {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - return result - - def cmd_stop(self, service_names): - result = dict(changed=False, actions=[]) - for service in self.project.services: - if not service_names or service.name in service_names: - service_res = dict(service=service.name, stop=[]) - for container in service.containers(stopped=False): - result["changed"] = True - service_res["stop"].append( - dict(id=container.id, name=container.name, short_id=container.short_id) - ) - result["actions"].append(service_res) - if not self.check_mode and result["changed"]: - out_redir_name, err_redir_name = make_redirection_tempfiles() - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - self.project.stop(service_names=service_names, timeout=self.timeout) - except Exception as exc: - fail_reason = get_failure_info( - exc, out_redir_name, err_redir_name, msg_format="Error stopping project {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - return result - - def cmd_restart(self, service_names): - result = dict(changed=False, actions=[]) - - for service in self.project.services: - if not service_names or service.name in service_names: - service_res = dict(service=service.name, restart=[]) - for container in service.containers(stopped=True): - result["changed"] = True - service_res["restart"].append( - dict(id=container.id, name=container.name, short_id=container.short_id) - ) - result["actions"].append(service_res) - - if not self.check_mode and result["changed"]: - out_redir_name, err_redir_name = make_redirection_tempfiles() - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - self.project.restart(service_names=service_names, timeout=self.timeout) - except Exception as exc: - fail_reason = get_failure_info( - exc, out_redir_name, err_redir_name, msg_format="Error restarting project {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - return result - - def cmd_scale(self): - result = dict(changed=False, actions=[]) - for service in self.project.services: - if service.name in self.scale: - service_res = dict(service=service.name, scale=0) - containers = service.containers(stopped=True) - scale = self.parse_scale(service.name) - if len(containers) != scale: - result["changed"] = True - service_res["scale"] = scale - len(containers) - if not self.check_mode: - out_redir_name, err_redir_name = make_redirection_tempfiles() - try: - with stdout_redirector(out_redir_name): - with stderr_redirector(err_redir_name): - service.scale(scale) - except Exception as exc: - fail_reason = get_failure_info( - exc, - out_redir_name, - err_redir_name, - msg_format="Error scaling {}".format(service.name) + " - {}" - ) - self.client.fail(**fail_reason) - else: - cleanup_redirection_tempfiles(out_redir_name, err_redir_name) - result["actions"].append(service_res) - return result - - def parse_scale(self, service_name): - try: - return int(self.scale[service_name]) - except ValueError: - self.client.fail( - "Error scaling {} - expected int, got {}", service_name, - to_native(type(self.scale[service_name])) - ) - - -def main(): - argument_spec = dict( - project_src=dict(type="path"), - project_name=dict(type="str",), - files=dict(type="list", elements="path"), - state=dict(type="str", default="present", choices=["absent", "present"]), - definition=dict(type="dict"), - hostname_check=dict(type="bool", default=False), - recreate=dict(type="str", default="smart", choices=["always", "never", "smart"]), - build=dict(type="bool", default=False), - remove_images=dict(type="str", choices=["all", "local"]), - remove_volumes=dict(type="bool", default=False), - remove_orphans=dict(type="bool", default=False), - stopped=dict(type="bool", default=False), - restarted=dict(type="bool", default=False), - scale=dict(type="dict"), - services=dict(type="list", elements="str"), - dependencies=dict(type="bool", default=True), - pull=dict(type="bool", default=False), - nocache=dict(type="bool", default=False), - debug=dict(type="bool", default=False), - timeout=dict(type="int", default=DEFAULT_TIMEOUT) - ) - - mutually_exclusive = [("definition", "project_src"), ("definition", "files")] - - client = AnsibleDockerClient( - argument_spec=argument_spec, - mutually_exclusive=mutually_exclusive, - supports_check_mode=True, - min_docker_api_version="1.20", - ) - - try: - result = ContainerManager(client).exec_module() - client.module.exit_json(**result) - except DockerException as e: - client.fail( - "An unexpected docker error occurred: {0}".format(to_native(e)), - exception=traceback.format_exc() - ) - except RequestException as e: - client.fail( - "An unexpected requests error occurred when docker-py" - "tried to talk to the docker daemon: {0}".format(to_native(e)), - exception=traceback.format_exc() - ) - - -if __name__ == "__main__": - main() diff --git a/test/unit/requirements.txt b/test/unit/requirements.txt index 1ce2049..9afe0a6 100644 --- a/test/unit/requirements.txt +++ b/test/unit/requirements.txt @@ -9,6 +9,3 @@ corenetworks # requirement for the openssl_pkcs12 module pyOpenSSL - -# requirement for the docker_compose module -docker-compose