PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/dockercompose.py |
""" Module to import docker-compose via saltstack .. versionadded:: 2016.3.0 :maintainer: Jean Praloran <jeanpralo@gmail.com> :maturity: new :depends: docker-compose>=1.5 :platform: all Introduction ------------ This module allows one to deal with docker-compose file in a directory. This is a first version only, the following commands are missing at the moment but will be built later on if the community is interested in this module: - run - logs - port - scale Installation Prerequisites -------------------------- This execution module requires at least version 1.4.0 of both docker-compose_ and Docker_. docker-compose can easily be installed using :py:func:`pip.install <salt.modules.pip.install>`: .. code-block:: bash salt myminion pip.install docker-compose>=1.5.0 .. _docker-compose: https://pypi.python.org/pypi/docker-compose .. _Docker: https://www.docker.com/ How to use this module? ----------------------- In order to use the module if you have no docker-compose file on the server you can issue the command create, it takes two arguments the path where the docker-compose.yml will be stored and the content of this latter: .. code-block:: text # salt-call -l debug dockercompose.create /tmp/toto ' database: image: mongo:3.0 command: mongod --smallfiles --quiet --logpath=/dev/null ' Then you can execute a list of method defined at the bottom with at least one argument (the path where the docker-compose.yml will be read) and an optional python list which corresponds to the services names: .. code-block:: bash # salt-call -l debug dockercompose.up /tmp/toto # salt-call -l debug dockercompose.restart /tmp/toto '[database]' # salt-call -l debug dockercompose.stop /tmp/toto # salt-call -l debug dockercompose.rm /tmp/toto Docker-compose method supported ------------------------------- - up - restart - stop - start - pause - unpause - kill - rm - ps - pull - build Functions --------- - docker-compose.yml management - :py:func:`dockercompose.create <salt.modules.dockercompose.create>` - :py:func:`dockercompose.get <salt.modules.dockercompose.get>` - Manage containers - :py:func:`dockercompose.restart <salt.modules.dockercompose.restart>` - :py:func:`dockercompose.stop <salt.modules.dockercompose.stop>` - :py:func:`dockercompose.pause <salt.modules.dockercompose.pause>` - :py:func:`dockercompose.unpause <salt.modules.dockercompose.unpause>` - :py:func:`dockercompose.start <salt.modules.dockercompose.start>` - :py:func:`dockercompose.kill <salt.modules.dockercompose.kill>` - :py:func:`dockercompose.rm <salt.modules.dockercompose.rm>` - :py:func:`dockercompose.up <salt.modules.dockercompose.up>` - Manage containers image: - :py:func:`dockercompose.pull <salt.modules.dockercompose.pull>` - :py:func:`dockercompose.build <salt.modules.dockercompose.build>` - Gather information about containers: - :py:func:`dockercompose.ps <salt.modules.dockercompose.ps>` - Manage service definitions: - :py:func:`dockercompose.service_create <salt.modules.dockercompose.ps>` - :py:func:`dockercompose.service_upsert <salt.modules.dockercompose.ps>` - :py:func:`dockercompose.service_remove <salt.modules.dockercompose.ps>` - :py:func:`dockercompose.service_set_tag <salt.modules.dockercompose.ps>` Detailed Function Documentation ------------------------------- """ import inspect import logging import os import re from operator import attrgetter import salt.utils.files import salt.utils.stringutils from salt.serializers import json from salt.utils import yaml try: import compose from compose.cli.command import get_project from compose.service import ConvergenceStrategy HAS_DOCKERCOMPOSE = True except ImportError: HAS_DOCKERCOMPOSE = False try: from compose.project import OneOffFilter USE_FILTERCLASS = True except ImportError: USE_FILTERCLASS = False MIN_DOCKERCOMPOSE = (1, 5, 0) VERSION_RE = r"([\d.]+)" log = logging.getLogger(__name__) debug = False __virtualname__ = "dockercompose" DEFAULT_DC_FILENAMES = ("docker-compose.yml", "docker-compose.yaml") __deprecated__ = ( 3009, "docker", "https://github.com/saltstack/saltext-docker", ) def __virtual__(): if HAS_DOCKERCOMPOSE: match = re.match(VERSION_RE, str(compose.__version__)) if match: version = tuple(int(x) for x in match.group(1).split(".")) if version >= MIN_DOCKERCOMPOSE: return __virtualname__ return ( False, "The dockercompose execution module not loaded: " "compose python library not available.", ) def __standardize_result(status, message, data=None, debug_msg=None): """ Standardizes all responses :param status: :param message: :param data: :param debug_msg: :return: """ result = {"status": status, "message": message} if data is not None: result["return"] = data if debug_msg is not None and debug: result["debug"] = debug_msg return result def __get_docker_file_path(path): """ Determines the filepath to use :param path: :return: """ if os.path.isfile(path): return path for dc_filename in DEFAULT_DC_FILENAMES: file_path = os.path.join(path, dc_filename) if os.path.isfile(file_path): return file_path # implicitly return None def __read_docker_compose_file(file_path): """ Read the compose file if it exists in the directory :param file_path: :return: """ if not os.path.isfile(file_path): return __standardize_result( False, f"Path {file_path} is not present", None, None ) try: with salt.utils.files.fopen(file_path, "r") as fl: file_name = os.path.basename(file_path) result = {file_name: ""} for line in fl: result[file_name] += salt.utils.stringutils.to_unicode(line) except OSError: return __standardize_result(False, f"Could not read {file_path}", None, None) return __standardize_result(True, f"Reading content of {file_path}", result, None) def __load_docker_compose(path): """ Read the compose file and load its' contents :param path: :return: """ file_path = __get_docker_file_path(path) if file_path is None: msg = f"Could not find docker-compose file at {path}" return None, __standardize_result(False, msg, None, None) if not os.path.isfile(file_path): return ( None, __standardize_result(False, f"Path {file_path} is not present", None, None), ) try: with salt.utils.files.fopen(file_path, "r") as fl: loaded = yaml.load(fl) except OSError: return ( None, __standardize_result(False, f"Could not read {file_path}", None, None), ) except yaml.YAMLError as yerr: msg = f"Could not parse {file_path} {yerr}" return None, __standardize_result(False, msg, None, None) if not loaded: msg = f"Got empty compose file at {file_path}" return None, __standardize_result(False, msg, None, None) if "services" not in loaded: loaded["services"] = {} result = {"compose_content": loaded, "file_name": os.path.basename(file_path)} return result, None def __dump_docker_compose(path, content, already_existed): """ Dumps :param path: :param content: the not-yet dumped content :return: """ try: dumped = yaml.safe_dump(content, indent=2, default_flow_style=False) return __write_docker_compose(path, dumped, already_existed) except TypeError as t_err: msg = f"Could not dump {content} {t_err}" return __standardize_result(False, msg, None, None) def __write_docker_compose(path, docker_compose, already_existed): """ Write docker-compose to a path in order to use it with docker-compose ( config check ) :param path: docker_compose contains the docker-compose file :return: """ if path.lower().endswith((".yml", ".yaml")): file_path = path dir_name = os.path.dirname(path) else: dir_name = path file_path = os.path.join(dir_name, DEFAULT_DC_FILENAMES[0]) if os.path.isdir(dir_name) is False: os.mkdir(dir_name) try: with salt.utils.files.fopen(file_path, "w") as fl: fl.write(salt.utils.stringutils.to_str(docker_compose)) except OSError: return __standardize_result(False, f"Could not write {file_path}", None, None) project = __load_project_from_file_path(file_path) if isinstance(project, dict): if not already_existed: os.remove(file_path) return project return file_path def __load_project(path): """ Load a docker-compose project from path :param path: :return: """ file_path = __get_docker_file_path(path) if file_path is None: msg = f"Could not find docker-compose file at {path}" return __standardize_result(False, msg, None, None) return __load_project_from_file_path(file_path) def __load_project_from_file_path(file_path): """ Load a docker-compose project from file path :param path: :return: """ try: project = get_project( project_dir=os.path.dirname(file_path), config_path=[os.path.basename(file_path)], ) except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return project def __load_compose_definitions(path, definition): """ Will load the compose file located at path Then determines the format/contents of the sent definition err or results are only set if there were any :param path: :param definition: :return tuple(compose_result, loaded_definition, err): """ compose_result, err = __load_docker_compose(path) if err: return None, None, err if isinstance(definition, dict): return compose_result, definition, None elif definition.strip().startswith("{"): try: loaded_definition = json.deserialize(definition) except json.DeserializationError as jerr: msg = f"Could not parse {definition} {jerr}" return None, None, __standardize_result(False, msg, None, None) else: try: loaded_definition = yaml.load(definition) except yaml.YAMLError as yerr: msg = f"Could not parse {definition} {yerr}" return None, None, __standardize_result(False, msg, None, None) return compose_result, loaded_definition, None def __dump_compose_file(path, compose_result, success_msg, already_existed): """ Utility function to dump the compose result to a file. :param path: :param compose_result: :param success_msg: the message to give upon success :return: """ ret = __dump_docker_compose( path, compose_result["compose_content"], already_existed ) if isinstance(ret, dict): return ret return __standardize_result( True, success_msg, compose_result["compose_content"], None ) def __handle_except(inst): """ Handle exception and return a standard result :param inst: :return: """ return __standardize_result( False, f"Docker-compose command {inspect.stack()[1][3]} failed", f"{inst}", None, ) def _get_convergence_plans(project, service_names): """ Get action executed for each container :param project: :param service_names: :return: """ ret = {} plans = project._get_convergence_plans( project.get_services(service_names), ConvergenceStrategy.changed ) for cont in plans: (action, container) = plans[cont] if action == "create": ret[cont] = "Creating container" elif action == "recreate": ret[cont] = "Re-creating container" elif action == "start": ret[cont] = "Starting container" elif action == "noop": ret[cont] = "Container is up to date" return ret def get(path): """ Get the content of the docker-compose file into a directory path Path where the docker-compose file is stored on the server CLI Example: .. code-block:: bash salt myminion dockercompose.get /path/where/docker-compose/stored """ file_path = __get_docker_file_path(path) if file_path is None: return __standardize_result(False, f"Path {path} is not present", None, None) salt_result = __read_docker_compose_file(file_path) if not salt_result["status"]: return salt_result project = __load_project(path) if isinstance(project, dict): salt_result["return"]["valid"] = False else: salt_result["return"]["valid"] = True return salt_result def create(path, docker_compose): """ Create and validate a docker-compose file into a directory path Path where the docker-compose file will be stored on the server docker_compose docker_compose file CLI Example: .. code-block:: bash salt myminion dockercompose.create /path/where/docker-compose/stored content """ if docker_compose: ret = __write_docker_compose(path, docker_compose, already_existed=False) if isinstance(ret, dict): return ret else: return __standardize_result( False, "Creating a docker-compose project failed, you must send a valid" " docker-compose file", None, None, ) return __standardize_result( True, "Successfully created the docker-compose file", {"compose.base_dir": path}, None, ) def pull(path, service_names=None): """ Pull image for containers in the docker-compose file, service_names is a python list, if omitted pull all images path Path where the docker-compose file is stored on the server service_names If specified will pull only the image for the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.pull /path/where/docker-compose/stored salt myminion dockercompose.pull /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) if isinstance(project, dict): return project else: try: project.pull(service_names) except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Pulling containers images via docker-compose succeeded", None, None ) def build(path, service_names=None): """ Build image for containers in the docker-compose file, service_names is a python list, if omitted build images for all containers. Please note that at the moment the module does not allow you to upload your Dockerfile, nor any other file you could need with your docker-compose.yml, you will have to make sure the files you need are actually in the directory specified in the `build` keyword path Path where the docker-compose file is stored on the server service_names If specified will pull only the image for the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.build /path/where/docker-compose/stored salt myminion dockercompose.build /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) if isinstance(project, dict): return project else: try: project.build(service_names) except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Building containers images via docker-compose succeeded", None, None ) def restart(path, service_names=None): """ Restart container(s) in the docker-compose file, service_names is a python list, if omitted restart all containers path Path where the docker-compose file is stored on the server service_names If specified will restart only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.restart /path/where/docker-compose/stored salt myminion dockercompose.restart /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) debug_ret = {} result = {} if isinstance(project, dict): return project else: try: project.restart(service_names) if debug: for container in project.containers(): if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() result[container.get("Name")] = "restarted" except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Restarting containers via docker-compose", result, debug_ret ) def stop(path, service_names=None): """ Stop running containers in the docker-compose file, service_names is a python list, if omitted stop all containers path Path where the docker-compose file is stored on the server service_names If specified will stop only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.stop /path/where/docker-compose/stored salt myminion dockercompose.stop /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) debug_ret = {} result = {} if isinstance(project, dict): return project else: try: project.stop(service_names) if debug: for container in project.containers(stopped=True): if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() result[container.get("Name")] = "stopped" except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Stopping containers via docker-compose", result, debug_ret ) def pause(path, service_names=None): """ Pause running containers in the docker-compose file, service_names is a python list, if omitted pause all containers path Path where the docker-compose file is stored on the server service_names If specified will pause only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.pause /path/where/docker-compose/stored salt myminion dockercompose.pause /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) debug_ret = {} result = {} if isinstance(project, dict): return project else: try: project.pause(service_names) if debug: for container in project.containers(): if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() result[container.get("Name")] = "paused" except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Pausing containers via docker-compose", result, debug_ret ) def unpause(path, service_names=None): """ Un-Pause containers in the docker-compose file, service_names is a python list, if omitted unpause all containers path Path where the docker-compose file is stored on the server service_names If specified will un-pause only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.pause /path/where/docker-compose/stored salt myminion dockercompose.pause /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) debug_ret = {} result = {} if isinstance(project, dict): return project else: try: project.unpause(service_names) if debug: for container in project.containers(): if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() result[container.get("Name")] = "unpaused" except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Un-Pausing containers via docker-compose", result, debug_ret ) def start(path, service_names=None): """ Start containers in the docker-compose file, service_names is a python list, if omitted start all containers path Path where the docker-compose file is stored on the server service_names If specified will start only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.start /path/where/docker-compose/stored salt myminion dockercompose.start /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) debug_ret = {} result = {} if isinstance(project, dict): return project else: try: project.start(service_names) if debug: for container in project.containers(): if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() result[container.get("Name")] = "started" except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Starting containers via docker-compose", result, debug_ret ) def kill(path, service_names=None): """ Kill containers in the docker-compose file, service_names is a python list, if omitted kill all containers path Path where the docker-compose file is stored on the server service_names If specified will kill only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.kill /path/where/docker-compose/stored salt myminion dockercompose.kill /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) debug_ret = {} result = {} if isinstance(project, dict): return project else: try: project.kill(service_names) if debug: for container in project.containers(stopped=True): if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() result[container.get("Name")] = "killed" except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Killing containers via docker-compose", result, debug_ret ) def rm(path, service_names=None): """ Remove stopped containers in the docker-compose file, service_names is a python list, if omitted remove all stopped containers path Path where the docker-compose file is stored on the server service_names If specified will remove only the specified stopped services CLI Example: .. code-block:: bash salt myminion dockercompose.rm /path/where/docker-compose/stored salt myminion dockercompose.rm /path/where/docker-compose/stored '[janus]' """ project = __load_project(path) if isinstance(project, dict): return project else: try: project.remove_stopped(service_names) except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Removing stopped containers via docker-compose", None, None ) def ps(path): """ List all running containers and report some information about them path Path where the docker-compose file is stored on the server CLI Example: .. code-block:: bash salt myminion dockercompose.ps /path/where/docker-compose/stored """ project = __load_project(path) result = {} if isinstance(project, dict): return project else: if USE_FILTERCLASS: containers = sorted( project.containers(None, stopped=True) + project.containers(None, OneOffFilter.only), key=attrgetter("name"), ) else: containers = sorted( project.containers(None, stopped=True) + project.containers(None, one_off=True), key=attrgetter("name"), ) for container in containers: command = container.human_readable_command if len(command) > 30: command = f"{command[:26]} ..." result[container.name] = { "id": container.id, "name": container.name, "command": command, "state": container.human_readable_state, "ports": container.human_readable_ports, } return __standardize_result(True, "Listing docker-compose containers", result, None) def up(path, service_names=None): """ Create and start containers defined in the docker-compose.yml file located in path, service_names is a python list, if omitted create and start all containers path Path where the docker-compose file is stored on the server service_names If specified will create and start only the specified services CLI Example: .. code-block:: bash salt myminion dockercompose.up /path/where/docker-compose/stored salt myminion dockercompose.up /path/where/docker-compose/stored '[janus]' """ debug_ret = {} project = __load_project(path) if isinstance(project, dict): return project else: try: result = _get_convergence_plans(project, service_names) ret = project.up(service_names) if debug: for container in ret: if ( service_names is None or container.get("Name")[1:] in service_names ): container.inspect_if_not_inspected() debug_ret[container.get("Name")] = container.inspect() except Exception as inst: # pylint: disable=broad-except return __handle_except(inst) return __standardize_result( True, "Adding containers via docker-compose", result, debug_ret ) def service_create(path, service_name, definition): """ Create the definition of a docker-compose service This fails when the service already exists This does not pull or up the service This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces path Path where the docker-compose file is stored on the server service_name Name of the service to create definition Service definition as yaml or json string CLI Example: .. code-block:: bash salt myminion dockercompose.service_create /path/where/docker-compose/stored service_name definition """ compose_result, loaded_definition, err = __load_compose_definitions( path, definition ) if err: return err services = compose_result["compose_content"]["services"] if service_name in services: msg = f"Service {service_name} already exists" return __standardize_result(False, msg, None, None) services[service_name] = loaded_definition return __dump_compose_file( path, compose_result, f"Service {service_name} created", already_existed=True, ) def service_upsert(path, service_name, definition): """ Create or update the definition of a docker-compose service This does not pull or up the service This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces path Path where the docker-compose file is stored on the server service_name Name of the service to create definition Service definition as yaml or json string CLI Example: .. code-block:: bash salt myminion dockercompose.service_upsert /path/where/docker-compose/stored service_name definition """ compose_result, loaded_definition, err = __load_compose_definitions( path, definition ) if err: return err services = compose_result["compose_content"]["services"] if service_name in services: msg = f"Service {service_name} already exists" return __standardize_result(False, msg, None, None) services[service_name] = loaded_definition return __dump_compose_file( path, compose_result, f"Service definition for {service_name} is set", already_existed=True, ) def service_remove(path, service_name): """ Remove the definition of a docker-compose service This does not rm the container This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces path Path where the docker-compose file is stored on the server service_name Name of the service to remove CLI Example: .. code-block:: bash salt myminion dockercompose.service_remove /path/where/docker-compose/stored service_name """ compose_result, err = __load_docker_compose(path) if err: return err services = compose_result["compose_content"]["services"] if service_name not in services: return __standardize_result( False, f"Service {service_name} did not exists", None, None ) del services[service_name] return __dump_compose_file( path, compose_result, f"Service {service_name} is removed from {path}", already_existed=True, ) def service_set_tag(path, service_name, tag): """ Change the tag of a docker-compose service This does not pull or up the service This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces path Path where the docker-compose file is stored on the server service_name Name of the service to remove tag Name of the tag (often used as version) that the service image should have CLI Example: .. code-block:: bash salt myminion dockercompose.service_create /path/where/docker-compose/stored service_name tag """ compose_result, err = __load_docker_compose(path) if err: return err services = compose_result["compose_content"]["services"] if service_name not in services: return __standardize_result( False, f"Service {service_name} did not exists", None, None ) if "image" not in services[service_name]: return __standardize_result( False, f'Service {service_name} did not contain the variable "image"', None, None, ) image = services[service_name]["image"].split(":")[0] services[service_name]["image"] = f"{image}:{tag}" return __dump_compose_file( path, compose_result, f'Service {service_name} is set to tag "{tag}"', already_existed=True, )