diff --git a/doc/ref/modules/all/index.rst b/doc/ref/modules/all/index.rst index 0dd360450931..3c962410593e 100644 --- a/doc/ref/modules/all/index.rst +++ b/doc/ref/modules/all/index.rst @@ -108,7 +108,6 @@ execution modules dnsmasq dnsutil dockercompose - dockerio dockerng dpkg drac diff --git a/doc/ref/modules/all/salt.modules.dockerio.rst b/doc/ref/modules/all/salt.modules.dockerio.rst deleted file mode 100644 index 07b3d0ae2873..000000000000 --- a/doc/ref/modules/all/salt.modules.dockerio.rst +++ /dev/null @@ -1,6 +0,0 @@ -===================== -salt.modules.dockerio -===================== - -.. automodule:: salt.modules.dockerio - :members: \ No newline at end of file diff --git a/doc/ref/states/all/index.rst b/doc/ref/states/all/index.rst index ae902b4d8ee0..b45b7808de01 100644 --- a/doc/ref/states/all/index.rst +++ b/doc/ref/states/all/index.rst @@ -71,7 +71,6 @@ state modules debconfmod dellchassis disk - dockerio dockerng drac elasticsearch_index diff --git a/doc/ref/states/all/salt.states.dockerio.rst b/doc/ref/states/all/salt.states.dockerio.rst deleted file mode 100644 index 07bee4f6acd4..000000000000 --- a/doc/ref/states/all/salt.states.dockerio.rst +++ /dev/null @@ -1,6 +0,0 @@ -==================== -salt.states.dockerio -==================== - -.. automodule:: salt.states.dockerio - :members: \ No newline at end of file diff --git a/doc/spelling_wordlist.txt b/doc/spelling_wordlist.txt index 16f6904e4d96..f2eb2a614b66 100644 --- a/doc/spelling_wordlist.txt +++ b/doc/spelling_wordlist.txt @@ -188,7 +188,6 @@ dnspython dnsservers dnsutil dockerfile -dockerio docstring docstrings dpkg diff --git a/salt/modules/dockerio.py b/salt/modules/dockerio.py deleted file mode 100644 index 499ac9a4cb2c..000000000000 --- a/salt/modules/dockerio.py +++ /dev/null @@ -1,2336 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Management of Docker Containers - -.. versionadded:: 2014.1.0 - -.. deprecated:: 2015.8.0 - Future feature development will be done only in :mod:`dockerng - `. See the documentation for this module for - information on the deprecation path. - -.. note:: - - The DockerIO integration is still in beta; the API is subject to change - -General Notes -------------- - -As we use states, we don't want to be continuously popping dockers, so we -will map each container id (or image) with a grain whenever it is relevant. - -As a corollary, we will resolve a container id either directly by the id -or try to find a container id matching something stocked in grain. - -Installation Prerequisites --------------------------- - -- You will need the ``docker-py`` python package in your python installation - path that is running salt. Its version should support `Docker Remote API - v1.12 `_. - - Currently, ``docker-py 0.6.0`` is known to support `Docker Remote API v1.12 - `_ - - .. code-block:: bash - - pip install docker-py==0.6.0 - -Prerequisite Pillar Configuration for Authentication ----------------------------------------------------- - -- To push or pull you will need to be authenticated as the ``docker-py`` bindings - require it -- For this to happen, you will need to configure a mapping in the pillar - representing your per URL authentication bits: - - .. code-block:: yaml - - docker-registries: - registry_url: - email: foo@foo.com - password: s3cr3t - username: foo - -- You need at least an entry to the default docker index: - - .. code-block:: yaml - - docker-registries: - https://index.docker.io/v1/: - email: foo@foo.com - password: s3cr3t - username: foo - -- You can define multiple registry blocks for them to be aggregated. The only thing to keep - in mind is that their ID must finish with ``-docker-registries``: - - .. code-block:: yaml - - ac-docker-registries: - https://index.bar.io/v1/: - email: foo@foo.com - password: s3cr3t - username: foo - - ab-docker-registries: - https://index.foo.io/v1/: - email: foo@foo.com - password: s3cr3t - username: foo - - This could be also written as: - - .. code-block:: yaml - - docker-registries: - https://index.bar.io/v1/: - email: foo@foo.com - password: s3cr3t - username: foo - https://index.foo.io/v1/: - email: foo@foo.com - password: s3cr3t - username: foo - -Methods -_______ - -- Registry Dialog - - :py:func:`login` - - :py:func:`push` - - :py:func:`pull` -- Docker Management - - :py:func:`version` - - :py:func:`info` -- Image Management - - :py:func:`search` - - :py:func:`inspect_image` - - :py:func:`get_images` - - :py:func:`remove_image` - - :py:func:`import_image` - - :py:func:`build` - - :py:func:`tag` - - :py:func:`save` - - :py:func:`load` -- Container Management - - :py:func:`start` - - :py:func:`stop` - - :py:func:`restart` - - :py:func:`kill` - - :py:func:`wait` - - :py:func:`get_containers` - - :py:func:`inspect_container` - - :py:func:`remove_container` - - :py:func:`is_running` - - :py:func:`top` - - :py:func:`port` - - :py:func:`logs` - - :py:func:`diff` - - :py:func:`commit` - - :py:func:`create_container` - - :py:func:`export` - - :py:func:`get_container_root` - -Runtime Execution within a specific, already existing/running container --------------------------------------------------------------------------- - -Idea is to use `lxc-attach `_ to execute -inside the container context. -We do not want to use ``docker run`` but want to execute something inside a -running container. - -These are the available methods: - -- :py:func:`retcode` -- :py:func:`run` -- :py:func:`run_all` -- :py:func:`run_stderr` -- :py:func:`run_stdout` -- :py:func:`script` -- :py:func:`script_retcode` - -''' - -# Import Python Futures -from __future__ import absolute_import - -__docformat__ = 'restructuredtext en' - -# Import Python libs -import datetime -import json -import logging -import os -import re -import traceback -import shutil -import types - -# Import Salt libs -from salt.modules import cmdmod -from salt.exceptions import CommandExecutionError, SaltInvocationError -import salt.utils -import salt.utils.files -import salt.utils.odict - -# Import 3rd-party libs -import salt.ext.six as six -# pylint: disable=import-error -from salt.ext.six.moves import range # pylint: disable=no-name-in-module,redefined-builtin -try: - import docker - HAS_DOCKER = True -except ImportError: - HAS_DOCKER = False -# pylint: enable=import-error - -HAS_NSENTER = bool(salt.utils.which('nsenter')) - - -log = logging.getLogger(__name__) - -INVALID_RESPONSE = 'We did not get any expected answer from docker' -VALID_RESPONSE = '' -NOTSET = object() -base_status = { - 'status': None, - 'id': None, - 'comment': '', - 'out': None -} - -# Define the module's virtual name -__virtualname__ = 'docker' - - -def __virtual__(): - ''' - Only load if docker libs are present - ''' - if HAS_DOCKER: - return __virtualname__ - return (False, 'dockerio execution module not loaded: docker python library not available.') - - -def _sizeof_fmt(num): - ''' - Return disk format size data - ''' - for unit in ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']: - if num < 1024.0: - return '{0:3.1f} {1}'.format(num, unit) - num /= 1024.0 - - -def _set_status(m, - id_=NOTSET, - comment=INVALID_RESPONSE, - status=False, - out=None): - ''' - Assign status data to a dict - ''' - m['comment'] = comment - m['status'] = status - m['out'] = out - if id_ is not NOTSET: - m['id'] = id_ - return m - - -def _invalid(m, id_=NOTSET, comment=INVALID_RESPONSE, out=None): - ''' - Return invalid status - ''' - return _set_status(m, status=False, id_=id_, comment=comment, out=out) - - -def _valid(m, id_=NOTSET, comment=VALID_RESPONSE, out=None): - ''' - Return valid status - ''' - return _set_status(m, status=True, id_=id_, comment=comment, out=out) - - -def _get_client(timeout=None): - ''' - Get a connection to a docker API (socket or URL) - based on config.get mechanism (pillar -> grains) - - By default it will use the base docker-py defaults which - at the time of writing are using the local socket and - the 1.4 API - - Set those keys in your configuration tree somehow: - - - docker.url: URL to the docker service - - docker.version: API version to use - - ''' - kwargs = {} - get = __salt__['config.get'] - for key, val in (('base_url', 'docker.url'), - ('version', 'docker.version')): - param = get(val, NOTSET) - if param is not NOTSET: - kwargs[key] = param - if timeout is not None: - # make sure we override default timeout of docker-py - # only if defined by user. - kwargs['timeout'] = timeout - - if 'version' not in kwargs: - # Let docker-py auto detect docker version incase - # it's not defined by user. - kwargs['version'] = 'auto' - - if 'base_url' not in kwargs and 'DOCKER_HOST' in os.environ: - # Check if the DOCKER_HOST environment variable has been set - kwargs['base_url'] = os.environ.get('DOCKER_HOST') - - client = docker.Client(**kwargs) - - # try to authenticate the client using credentials - # found in pillars - registry_auth_config = __pillar__.get('docker-registries', {}) - for key, data in six.iteritems(__pillar__): - if key.endswith('-docker-registries'): - registry_auth_config.update(data) - - for registry, creds in six.iteritems(registry_auth_config): - client.login(creds['username'], password=creds['password'], - email=creds.get('email'), registry=registry) - - return client - - -def _get_image_infos(image): - ''' - Verify that the image exists - We will try to resolve either by: - - name - - image_id - - tag - - image - Image Name / Image Id / Image Tag - - Returns the image id - ''' - status = base_status.copy() - client = _get_client() - try: - infos = client.inspect_image(image) - if infos: - _valid(status, - id_=infos['Id'], - out=infos, - comment='found') - except Exception: - pass - if not status['id']: - _invalid(status) - raise CommandExecutionError( - 'ImageID \'{0}\' could not be resolved to ' - 'an existing Image'.format(image) - ) - return status['out'] - - -def _get_container_infos(container): - ''' - Get container infos - We will try to resolve either by: - - the mapping grain->docker id or directly - - dockerid - - container - Image Id / grain name - ''' - status = base_status.copy() - client = _get_client() - try: - container_info = client.inspect_container(container) - if container_info: - _valid(status, - id_=container_info['Id'], - out=container_info) - except Exception: - pass - if not status['id']: - raise CommandExecutionError( - 'Container_id {0} could not be resolved to ' - 'an existing container'.format( - container) - ) - if 'id' not in status['out'] and 'Id' in status['out']: - status['out']['id'] = status['out']['Id'] - return status['out'] - - -def get_containers(all=True, - trunc=False, - since=None, - before=None, - limit=-1, - host=False, - inspect=False): - ''' - Get a list of mappings representing all containers - - all - return all containers, Default is ``True`` - - trunc - set it to True to have the short ID, Default is ``False`` - - host - include the Docker host's ipv4 and ipv6 address in return, Default is ``False`` - - inspect - Get more granular information about each container by running a docker inspect - - CLI Example: - - .. code-block:: bash - - salt '*' docker.get_containers - salt '*' docker.get_containers host=True - salt '*' docker.get_containers host=True inspect=True - ''' - - client = _get_client() - status = base_status.copy() - - if host: - status['host'] = {} - status['host']['interfaces'] = __salt__['network.interfaces']() - - containers = client.containers(all=all, - trunc=trunc, - since=since, - before=before, - limit=limit) - - # Optionally for each container get more granular information from them - # by inspecting the container - if inspect: - for container in containers: - container_id = container.get('Id') - if container_id: - inspect = _get_container_infos(container_id) - container['detail'] = inspect.copy() - - _valid(status, comment='All containers in out', out=containers) - - return status - - -def logs(container): - ''' - Return logs for a specified container - - container - container id - - CLI Example: - - .. code-block:: bash - - salt '*' docker.logs - ''' - status = base_status.copy() - client = _get_client() - try: - container_logs = client.logs(_get_container_infos(container)['Id']) - _valid(status, id_=container, out=container_logs) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def commit(container, - repository=None, - tag=None, - message=None, - author=None, - conf=None): - ''' - Commit a container (promotes it to an image) - - container - container id - repository - repository/image to commit to - tag - tag of the image (Optional) - message - commit message (Optional) - author - author name (Optional) - conf - conf (Optional) - - CLI Example: - - .. code-block:: bash - - salt '*' docker.commit - ''' - status = base_status.copy() - client = _get_client() - try: - container = _get_container_infos(container)['Id'] - commit_info = client.commit( - container, - repository=repository, - tag=tag, - message=message, - author=author, - conf=conf) - found = False - for k in ('Id', 'id', 'ID'): - if k in commit_info: - found = True - image_id = commit_info[k] - if not found: - raise Exception('Invalid commit return') - image = _get_image_infos(image_id)['Id'] - comment = 'Image {0} created from {1}'.format(image, container) - _valid(status, id_=image, out=commit_info, comment=comment) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def diff(container): - ''' - Get container diffs - - container - container id - - CLI Example: - - .. code-block:: bash - - salt '*' docker.diff - ''' - status = base_status.copy() - client = _get_client() - try: - container_diff = client.diff(_get_container_infos(container)['Id']) - _valid(status, id_=container, out=container_diff) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def export(container, path): - ''' - Export a container to a file - - container - container id - path - path to which file is to be exported - - CLI Example: - - .. code-block:: bash - - salt '*' docker.export - ''' - try: - ppath = os.path.abspath(path) - with salt.utils.fopen(ppath, 'w') as fic: - status = base_status.copy() - client = _get_client() - response = client.export(_get_container_infos(container)['Id']) - byte = response.read(4096) - fic.write(byte) - while byte != '': - # Do stuff with byte. - byte = response.read(4096) - fic.write(byte) - fic.flush() - _valid(status, - id_=container, out=ppath, - comment='Exported to {0}'.format(ppath)) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def create_container(image, - command=None, - hostname=None, - user=None, - detach=True, - stdin_open=False, - tty=False, - mem_limit=None, - ports=None, - environment=None, - dns=None, - volumes=None, - volumes_from=None, - name=None, - cpu_shares=None, - cpuset=None, - binds=None): - ''' - Create a new container - - image - image to create the container from - command - command to execute while starting - hostname - hostname of the container - user - user to run docker as - detach - daemon mode, Default is ``True`` - environment - environment variable mapping ``({'foo':'BAR'})`` - ports - port redirections ``({'222': {}})`` - volumes - list of volume mappings in either local volume, bound volume, or read-only - bound volume form:: - - (['/var/lib/mysql/', '/usr/local/etc/ssl:/etc/ssl', '/etc/passwd:/etc/passwd:ro']) - binds - complete dictionary of bound volume mappings:: - - { '/usr/local/etc/ssl/certs/internal.crt': { - 'bind': '/etc/ssl/certs/com.example.internal.crt', - 'ro': True - }, - '/var/lib/mysql': { - 'bind': '/var/lib/mysql/', - 'ro': False - } - } - - This dictionary is suitable for feeding directly into the Docker API, and all - keys are required. - (see https://docker-py.readthedocs.io/en/latest/volumes/) - tty - attach ttys, Default is ``False`` - stdin_open - let stdin open, Default is ``False`` - name - name given to container - cpu_shares - CPU shares (relative weight) - cpuset - CPUs in which to allow execution ('0-3' or '0,1') - - CLI Example: - - .. code-block:: bash - - salt '*' docker.create_container o/ubuntu volumes="['/s','/m:/f']" - - ''' - log.trace("modules.dockerio.create_container() called for image " + image) - status = base_status.copy() - client = _get_client() - - # In order to permit specification of bind volumes in the volumes field, - # we'll look through it for bind-style specs and move them. This is purely - # for CLI convenience and backwards-compatibility, as states.dockerio - # should parse volumes before this, and the binds argument duplicates this. - # N.B. this duplicates code in states.dockerio._parse_volumes() - if isinstance(volumes, list): - for volume in volumes: - if ':' in volume: - volspec = volume.split(':') - source = volspec[0] - target = volspec[1] - ro = False - try: - if len(volspec) > 2: - ro = volspec[2] == "ro" - except IndexError: - pass - binds[source] = {'bind': target, 'ro': ro} - volumes.remove(volume) - - try: - if salt.utils.version_cmp(client.version()['ApiVersion'], '1.18') == 1: - container_info = client.create_container( - image=image, - command=command, - hostname=hostname, - user=user, - detach=detach, - stdin_open=stdin_open, - tty=tty, - ports=ports, - environment=environment, - dns=dns, - volumes=volumes, - volumes_from=volumes_from, - name=name, - cpu_shares=cpu_shares, - cpuset=cpuset, - host_config=docker.utils.create_host_config(binds=binds, - mem_limit=mem_limit) - ) - else: - container_info = client.create_container( - image=image, - command=command, - hostname=hostname, - user=user, - detach=detach, - stdin_open=stdin_open, - tty=tty, - mem_limit=mem_limit, - ports=ports, - environment=environment, - dns=dns, - volumes=volumes, - volumes_from=volumes_from, - name=name, - cpu_shares=cpu_shares, - cpuset=cpuset, - host_config=docker.utils.create_host_config(binds=binds) - ) - - log.trace("docker.client.create_container returned: " + str(container_info)) - container = container_info['Id'] - callback = _valid - comment = 'Container created' - out = { - 'info': _get_container_infos(container), - 'out': container_info - } - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return callback(status, id_=container, comment=comment, out=out) - except Exception as e: - _invalid(status, id_=image, out=traceback.format_exc()) - raise e - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def version(): - ''' - Get docker version - - CLI Example: - - .. code-block:: bash - - salt '*' docker.version - ''' - status = base_status.copy() - client = _get_client() - try: - docker_version = client.version() - _valid(status, out=docker_version) - except Exception: - _invalid(status, out=traceback.format_exc()) - return status - - -def info(): - ''' - Get the version information about docker. This is similar to ``docker info`` command - - CLI Example: - - .. code-block:: bash - - salt '*' docker.info - ''' - status = base_status.copy() - client = _get_client() - try: - version_info = client.info() - _valid(status, out=version_info) - except Exception: - _invalid(status, out=traceback.format_exc()) - return status - - -def port(container, private_port): - ''' - Private port mapping allocation information. This method is broken on docker-py - side. Just use the result of inspect to mangle port - allocation - - container - container id - - private_port - private port on the container to query for - - CLI Example: - - .. code-block:: bash - - salt '*' docker.port - ''' - status = base_status.copy() - client = _get_client() - try: - port_info = client.port( - _get_container_infos(container)['Id'], - private_port) - _valid(status, id_=container, out=port_info) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def stop(container, timeout=10): - ''' - Stop a running container - - container - container id - - timeout - timeout for container to exit gracefully before killing it, Default is ``10`` seconds - - CLI Example: - - .. code-block:: bash - - salt '*' docker.stop [timeout=20] - ''' - client = _get_client() - status = base_status.copy() - try: - dcontainer = _get_container_infos(container)['Id'] - if is_running(dcontainer): - client.stop(dcontainer, timeout=timeout) - if not is_running(dcontainer): - _valid( - status, - comment='Container {0} was stopped'.format( - container), - id_=container) - else: - _invalid(status) - else: - _valid(status, - comment='Container {0} was already stopped'.format( - container), - id_=container) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc(), - comment=( - 'An exception occurred while stopping ' - 'your container {0}').format(container)) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def kill(container, signal=None): - ''' - Kill a running container - - container - container id - signal - signal to send - - .. versionadded:: 2015.8.0 - - CLI Example: - - .. code-block:: bash - - salt '*' docker.kill - ''' - client = _get_client() - status = base_status.copy() - try: - dcontainer = _get_container_infos(container)['Id'] - if is_running(dcontainer): - client.kill(dcontainer, signal=signal) - if signal: - # no need to check if container is running - # because some signals might not stop the container. - _valid(status, - comment='Kill signal \'{0}\' successfully' - ' sent to the container \'{1}\''.format(signal, container), - id_=container) - else: - if not is_running(dcontainer): - _valid(status, - comment='Container {0} was killed'.format(container), - id_=container) - else: - _invalid(status, - comment='Container {0} was not killed'.format( - container)) - else: - _valid(status, - comment='Container {0} was already stopped'.format( - container), - id_=container) - except Exception: - _invalid(status, - id_=container, - out=traceback.format_exc(), - comment=( - 'An exception occurred while killing ' - 'your container {0}').format(container)) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def restart(container, timeout=10): - ''' - Restart a running container - - container - container id - - timeout - timeout for container to exit gracefully before killing it, Default is ``10`` seconds - - CLI Example: - - .. code-block:: bash - - salt '*' docker.restart [timeout=20] - ''' - client = _get_client() - status = base_status.copy() - try: - dcontainer = _get_container_infos(container)['Id'] - client.restart(dcontainer, timeout=timeout) - if is_running(dcontainer): - _valid(status, - comment='Container {0} was restarted'.format(container), - id_=container) - else: - _invalid(status) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc(), - comment=( - 'An exception occurred while restarting ' - 'your container {0}').format(container)) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def start(container, - binds=None, - port_bindings=None, - lxc_conf=None, - publish_all_ports=None, - links=None, - privileged=False, - dns=None, - volumes_from=None, - network_mode=None, - restart_policy=None, - cap_add=None, - cap_drop=None): - ''' - Start the specified container - - container - container id - - CLI Example: - - .. code-block:: bash - - salt '*' docker.start - ''' - if binds: - if not isinstance(binds, dict): - raise SaltInvocationError('binds must be formatted as a dictionary') - - client = _get_client() - status = base_status.copy() - try: - dcontainer = _get_container_infos(container)['Id'] - if not is_running(container): - bindings = None - if port_bindings is not None: - try: - bindings = {} - for key, val in six.iteritems(port_bindings): - bindings[key] = (val.get('HostIp', ''), val['HostPort']) - except AttributeError: - raise SaltInvocationError( - 'port_bindings must be formatted as a dictionary of ' - 'dictionaries' - ) - client.start(dcontainer, - binds=binds, - port_bindings=bindings, - lxc_conf=lxc_conf, - publish_all_ports=publish_all_ports, - links=links, - privileged=privileged, - dns=dns, - volumes_from=volumes_from, - network_mode=network_mode, - restart_policy=restart_policy, - cap_add=cap_add, - cap_drop=cap_drop) - - if is_running(dcontainer): - _valid(status, - comment='Container {0} was started'.format(container), - id_=container) - else: - _invalid(status) - else: - _valid(status, - comment='Container {0} was already started'.format(container), - id_=container) - except Exception: - _invalid(status, - id_=container, - out=traceback.format_exc(), - comment=( - 'An exception occurred while starting ' - 'your container {0}').format(container)) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def wait(container): - ''' - Wait for a container to exit gracefully - - container - container id - - CLI Example: - - .. code-block:: bash - - salt '*' docker.wait - ''' - client = _get_client() - status = base_status.copy() - try: - dcontainer = _get_container_infos(container)['Id'] - if is_running(dcontainer): - client.wait(dcontainer) - if not is_running(container): - _valid(status, - id_=container, - comment='Container waited for stop') - else: - _invalid(status) - else: - _valid(status, - comment='Container {0} was already stopped'.format(container), - id_=container) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc(), - comment=( - 'An exception occurred while waiting ' - 'your container {0}').format(container)) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def exists(container): - ''' - Check if a given container exists - - container - container id - - Returns ``True`` if container exists otherwise returns ``False`` - - CLI Example: - - .. code-block:: bash - - salt '*' docker.exists - - ''' - try: - _get_container_infos(container) - return True - except Exception: - return False - - -def is_running(container): - ''' - Check if the specified container is running - - container - container id - - Returns ``True`` if container is running otherwise returns ``False`` - - CLI Example: - - .. code-block:: bash - - salt '*' docker.is_running - ''' - try: - infos = _get_container_infos(container) - return infos.get('State', {}).get('Running') - except Exception: - return False - - -def remove_container(container, force=False, v=False): - ''' - Remove a container from a docker installation - - container - container id - - force - remove a running container, Default is ``False`` - - v - remove the volumes associated to the container, Default is ``False`` - - CLI Example: - - .. code-block:: bash - - salt '*' docker.remove_container [force=True|False] [v=True|False] - ''' - client = _get_client() - status = base_status.copy() - status['id'] = container - dcontainer = None - try: - dcontainer = _get_container_infos(container)['Id'] - if is_running(dcontainer): - if not force: - _invalid(status, id_=container, out=None, - comment=( - 'Container {0} is running, ' - 'won\'t remove it').format(container)) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - else: - kill(dcontainer) - client.remove_container(dcontainer, v=v) - try: - _get_container_infos(dcontainer) - _invalid(status, - comment='Container was not removed: {0}'.format(container)) - except Exception: - status['status'] = True - status['comment'] = 'Container {0} was removed'.format(container) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - __salt__['mine.send']('dockerng.ps', verbose=True, all=True, host=True) - return status - - -def top(container): - ''' - Run the docker top command on a specific container - - container - container id - - CLI Example: - - .. code-block:: bash - - salt '*' docker.top - ''' - client = _get_client() - status = base_status.copy() - try: - dcontainer = _get_container_infos(container)['Id'] - if is_running(dcontainer): - ret = client.top(dcontainer) - if ret: - ret['mprocesses'] = [] - titles = ret['Titles'] - for i in ret['Processes']: - data = salt.utils.odict.OrderedDict() - for k, j in enumerate(titles): - data[j] = i[k] - ret['mprocesses'].append(data) - _valid(status, - out=ret, - id_=container, - comment='Current top for container') - if not status['id']: - _invalid(status) - else: - _invalid(status, - comment='Container {0} is not running'.format(container)) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def inspect_container(container): - ''' - Get container information. This is similar to ``docker inspect`` command but only for containers - - container - container id - - CLI Example: - - .. code-block:: bash - - salt '*' docker.inspect_container - - ''' - status = base_status.copy() - status['id'] = container - try: - infos = _get_container_infos(container) - _valid(status, id_=container, out=infos) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc(), - comment='Container does not exit: {0}'.format(container)) - return status - - -def login(url=None, username=None, password=None, email=None): - ''' - Wrapper to the ``docker.py`` login method (does not do much yet) - - url - registry url to authenticate to - - username - username to authenticate - - password - password to authenticate - - email - email to authenticate - - CLI Example: - - .. code-block:: bash - - salt '*' docker.login - ''' - client = _get_client() - return client.login(username, password, email, url) - - -def search(term): - ''' - Search for an image on the registry - - term - search keyword - - CLI Example: - - .. code-block:: bash - - salt '*' docker.search - ''' - client = _get_client() - status = base_status.copy() - ret = client.search(term) - if ret: - _valid(status, out=ret, id_=term) - else: - _invalid(status) - return status - - -def _create_image_assemble_error_status(status, ret, image_logs): - ''' - Given input in this form:: - - [{u'error': u'Get file:///r.tar.gz: unsupported protocol scheme "file"', - u'errorDetail': { - u'message':u'Get file:///r.tar.gz:unsupported protocol scheme "file"'}}, - {u'status': u'Downloading from file:///r.tar.gz'}] - ''' - comment = 'An error occurred while importing your image' - out = None - is_invalid = True - status['out'] = '' - try: - is_invalid = False - status['out'] += '\n' + ret - for err_log in image_logs: - if isinstance(err_log, dict): - if 'errorDetail' in err_log: - if 'code' in err_log['errorDetail']: - msg = '\n{0}\n{1}: {2}'.format( - err_log['error'], - err_log['errorDetail']['code'], - err_log['errorDetail']['message'] - ) - else: - msg = '\n{0}\n{1}'.format( - err_log['error'], - err_log['errorDetail']['message'], - ) - comment += msg - except Exception: - is_invalid = True - trace = traceback.format_exc() - out = ( - 'An error occurred while ' - 'parsing error output:\n{0}' - ).format(trace) - if is_invalid: - _invalid(status, out=out, comment=comment) - return status - - -def import_image(src, repo, tag=None): - ''' - Import content from a local tarball or a URL to a docker image - - src - content to import (URL or absolute path to a tarball) - - repo - repository to import to - - tag - set tag of the image (Optional) - - CLI Example: - - .. code-block:: bash - - salt '*' docker.import_image [tag] - ''' - client = _get_client() - status = base_status.copy() - try: - ret = client.import_image(src, repository=repo, tag=tag) - if ret: - image_logs, _info = _parse_image_multilogs_string(ret) - _create_image_assemble_error_status(status, ret, image_logs) - if status['status'] is not False: - infos = _get_image_infos(image_logs[0]['status']) - _valid(status, - comment='Image {0} was created'.format(infos['Id']), - id_=infos['Id'], - out=ret) - else: - _invalid(status) - except Exception: - _invalid(status, out=traceback.format_exc()) - return status - - -def tag(image, repository, tag=None, force=False): - ''' - Tag an image into a repository - - image - name of image - - repository - name of repository - - tag - tag to apply (Optional) - - force - force apply tag, Default is ``False`` - - CLI Example: - - .. code-block:: bash - - salt '*' docker.tag [tag] [force=True|False] - ''' - client = _get_client() - status = base_status.copy() - try: - dimage = _get_image_infos(image)['Id'] - ret = client.tag(dimage, repository, tag=tag, force=force) - except Exception: - _invalid(status, - out=traceback.format_exc(), - comment='Cant tag image {0} {1}{2}'.format( - image, repository, - tag and (':' + tag) or '').strip()) - return status - if ret: - _valid(status, - id_=image, - comment='Image was tagged: {0}{1}'.format( - repository, - tag and (':' + tag) or '').strip()) - else: - _invalid(status) - return status - - -def get_images(name=None, quiet=False, all=True): - ''' - List docker images - - name - repository name - - quiet - only show image id, Default is ``False`` - - all - show all images, Default is ``True`` - - CLI Example: - - .. code-block:: bash - - salt '*' docker.get_images [quiet=True|False] [all=True|False] - ''' - client = _get_client() - status = base_status.copy() - try: - infos = client.images(name=name, quiet=quiet, all=all) - for i in range(len(infos)): - inf = infos[i] - try: - inf['Human_Size'] = _sizeof_fmt(int(inf['Size'])) - except ValueError: - pass - try: - ts = int(inf['Created']) - dts = datetime.datetime.fromtimestamp(ts) - inf['Human_IsoCreated'] = dts.isoformat() - inf['Human_Created'] = dts.strftime( - '%Y-%m-%d %H:%M:%S') - except Exception: - pass - try: - inf['Human_VirtualSize'] = ( - _sizeof_fmt(int(inf['VirtualSize']))) - except ValueError: - pass - _valid(status, out=infos) - except Exception: - _invalid(status, out=traceback.format_exc()) - return status - - -def build(path=None, - tag=None, - quiet=False, - fileobj=None, - nocache=False, - rm=True, - timeout=None): - ''' - Build a docker image from a dockerfile or an URL - - path - url/branch/docker_dir or path on the filesystem to the dockerfile - - tag - tag of the image - - quiet - quiet mode, Default is ``False`` - - nocache - do not use docker image cache, Default is ``False`` - - rm - remove intermediate commits, Default is ``True`` - - timeout - timeout value before aborting (in seconds) - - CLI Example: - - .. code-block:: bash - - salt '*' docker.build vieux/apache - salt '*' docker.build github.com/creack/docker-firefox - ''' - client = _get_client(timeout=timeout) - status = base_status.copy() - if path or fileobj: - try: - ret = client.build(path=path, - tag=tag, - quiet=quiet, - fileobj=fileobj, - rm=rm, - nocache=nocache) - - if isinstance(ret, types.GeneratorType): - - message = json.loads(list(ret)[-1]) - if 'stream' in message: - if 'Successfully built' in message['stream']: - _valid(status, out=message['stream']) - if 'errorDetail' in message: - _invalid(status, out=message['errorDetail']['message']) - - elif isinstance(ret, tuple): - id_, out = ret[0], ret[1] - if id_: - _valid(status, id_=id_, out=out, comment='Image built') - else: - _invalid(status, id_=id_, out=out) - - except Exception: - _invalid(status, - out=traceback.format_exc(), - comment='Unexpected error while building an image') - return status - - return status - - -def remove_image(image): - ''' - Remove an image from a system. - - image - name of image - - CLI Example: - - .. code-block:: bash - - salt '*' docker.remove_image - ''' - client = _get_client() - status = base_status.copy() - # will raise an error if no deletion - try: - infos = _get_image_infos(image) - if infos: - status['id'] = infos['Id'] - try: - client.remove_image(infos['Id']) - except Exception: - _invalid(status, - id_=image, - out=traceback.format_exc(), - comment='Image could not be deleted') - try: - infos = _get_image_infos(image) - _invalid(status, - comment=( - 'Image marked to be deleted but not deleted yet')) - except Exception: - _valid(status, id_=image, comment='Image deleted') - else: - _invalid(status) - except Exception: - _invalid(status, - out=traceback.format_exc(), - comment='Image does not exist: {0}'.format(image)) - return status - - -def inspect_image(image): - ''' - Inspect the status of an image and return relative data. This is similar to - ``docker inspect`` command but only for images. - - image - name of the image - - CLI Example: - - .. code-block:: bash - - salt '*' docker.inspect_image - ''' - status = base_status.copy() - try: - infos = _get_image_infos(image) - try: - for k in ['Size']: - infos[ - 'Human_{0}'.format(k) - ] = _sizeof_fmt(int(infos[k])) - except Exception: - pass - _valid(status, id_=image, out=infos) - except Exception: - _invalid(status, id_=image, out=traceback.format_exc(), - comment='Image does not exist') - return status - - -def _parse_image_multilogs_string(ret): - ''' - Parse image log strings into grokable data - ''' - image_logs, infos = [], None - if ret and ret.strip().startswith('{') and ret.strip().endswith('}'): - pushd = 0 - buf = '' - for char in ret: - buf += char - if char == '{': - pushd += 1 - if char == '}': - pushd -= 1 - if pushd == 0: - try: - buf = json.loads(buf) - except Exception: - pass - else: - image_logs.append(buf) - buf = '' - image_logs.reverse() - - # Valid statest when pulling an image from the docker registry - valid_states = [ - 'Download complete', - 'Already exists', - ] - - # search last layer grabbed - for ilog in image_logs: - if isinstance(ilog, dict): - if ilog.get('status') in valid_states and ilog.get('id'): - infos = _get_image_infos(ilog['id']) - break - - return image_logs, infos - - -def _pull_assemble_error_status(status, ret, logs): - ''' - Given input in this form:: - - u'{"status":"Pulling repository foo/ubuntubox"}: - "image (latest) from foo/ ... - rogress":"complete","id":"2c80228370c9"}' - - construct something like that (load JSON data is possible):: - - [u'{"status":"Pulling repository foo/ubuntubox"', - {"status":"Download","progress":"complete","id":"2c80228370c9"}] - ''' - comment = 'An error occurred pulling your image' - out = '' - try: - out = '\n' + ret - for err_log in logs: - if isinstance(err_log, dict): - if 'errorDetail' in err_log: - if 'code' in err_log['errorDetail']: - msg = '\n{0}\n{1}: {2}'.format( - err_log['error'], - err_log['errorDetail']['code'], - err_log['errorDetail']['message'] - ) - else: - msg = '\n{0}\n{1}'.format( - err_log['error'], - err_log['errorDetail']['message'], - ) - comment += msg - except Exception: - out = traceback.format_exc() - _invalid(status, out=out, comment=comment) - return status - - -def pull(repo, tag=None, insecure_registry=False): - ''' - Pulls an image from any registry. See documentation at top of this page to - configure authenticated access - - repo - name of repository - - tag - specific tag to pull (Optional) - - insecure_registry - set as ``True`` to use insecure (non HTTPS) registry. Default is ``False`` - (only available if using docker-py >= 0.5.0) - - CLI Example: - - .. code-block:: bash - - salt '*' docker.pull [tag] - ''' - client = _get_client() - status = base_status.copy() - try: - kwargs = {'tag': tag} - # if docker-py version is greater than 0.5.0 use the - # insecure_registry parameter - if salt.utils.compare_versions(ver1=docker.__version__, - oper='>=', - ver2='0.5.0'): - kwargs['insecure_registry'] = insecure_registry - ret = client.pull(repo, **kwargs) - if ret: - image_logs, infos = _parse_image_multilogs_string(ret) - if infos and infos.get('Id', None): - repotag = repo - if tag: - repotag = '{0}:{1}'.format(repo, tag) - _valid(status, - out=image_logs if image_logs else ret, - id_=infos['Id'], - comment='Image {0} was pulled ({1})'.format( - repotag, infos['Id'])) - - else: - _pull_assemble_error_status(status, ret, image_logs) - else: - _invalid(status) - except Exception: - _invalid(status, id_=repo, out=traceback.format_exc()) - return status - - -def _push_assemble_error_status(status, ret, logs): - ''' - Given input in this form:: - - u'{"status":"Pulling repository foo/ubuntubox"}: - "image (latest) from foo/ ... - rogress":"complete","id":"2c80228370c9"}' - - construct something like that (load json data is possible):: - - [u'{"status":"Pulling repository foo/ubuntubox"', - {"status":"Download","progress":"complete","id":"2c80228370c9"}] - ''' - comment = 'An error occurred pushing your image' - status['out'] = '' - try: - status['out'] += '\n' + ret - for err_log in logs: - if isinstance(err_log, dict): - if 'errorDetail' in err_log: - if 'code' in err_log['errorDetail']: - msg = '\n{0}\n{1}: {2}'.format( - err_log['error'], - err_log['errorDetail']['code'], - err_log['errorDetail']['message'] - ) - else: - msg = '\n{0}\n{1}'.format( - err_log['error'], - err_log['errorDetail']['message'], - ) - comment += msg - except Exception: - trace = traceback.format_exc() - status['out'] = ( - 'An error occurred while ' - 'parsing error output:\n{0}' - ).format(trace) - _invalid(status, comment=comment) - return status - - -def push(repo, tag=None, quiet=False, insecure_registry=False): - ''' - Pushes an image to any registry. See documentation at top of this page to - configure authenticated access - - repo - name of repository - - tag - specific tag to push (Optional) - - quiet - set as ``True`` to quiet output, Default is ``False`` - - insecure_registry - set as ``True`` to use insecure (non HTTPS) registry. Default is ``False`` - (only available if using docker-py >= 0.5.0) - - CLI Example: - - .. code-block:: bash - - salt '*' docker.push [tag] [quiet=True|False] - ''' - client = _get_client() - status = base_status.copy() - registry, repo_name = docker.auth.resolve_repository_name(repo) - try: - kwargs = {'tag': tag} - # if docker-py version is greater than 0.5.0 use the - # insecure_registry parameter - if salt.utils.compare_versions(ver1=docker.__version__, - oper='>=', - ver2='0.5.0'): - kwargs['insecure_registry'] = insecure_registry - ret = client.push(repo, **kwargs) - if ret: - image_logs, infos = _parse_image_multilogs_string(ret) - if image_logs: - repotag = repo_name - if tag: - repotag = '{0}:{1}'.format(repo, tag) - if not quiet: - status['out'] = image_logs - else: - status['out'] = None - laststatus = image_logs[2].get('status', None) - if laststatus and ( - ('already pushed' in laststatus) - or ('Pushing tags for rev' in laststatus) - or ('Pushing tag for rev' in laststatus) - ): - status['status'] = True - status['id'] = _get_image_infos(repo)['Id'] - status['comment'] = 'Image {0}({1}) was pushed'.format( - repotag, status['id']) - else: - _push_assemble_error_status(status, ret, image_logs) - else: - status['out'] = ret - _push_assemble_error_status(status, ret, image_logs) - else: - _invalid(status) - except Exception: - _invalid(status, id_=repo, out=traceback.format_exc()) - return status - - -def _run_wrapper(status, container, func, cmd, *args, **kwargs): - ''' - Wrapper to a cmdmod function - - Idea is to prefix the call to cmdrun with the relevant driver to - execute inside a container context - - .. note:: - - Only lxc and native drivers are implemented. - - status - status object - container - container id to execute in - func - cmd function to execute - cmd - command to execute in the container - ''' - - client = _get_client() - # For old version of docker. lxc was the only supported driver. - # We can safely hardcode it - driver = client.info().get('ExecutionDriver', 'lxc-') - container_info = _get_container_infos(container) - container_id = container_info['Id'] - if driver.startswith('lxc-'): - full_cmd = 'lxc-attach -n {0} -- {1}'.format(container_id, cmd) - elif driver.startswith('native-'): - if HAS_NSENTER: - # http://jpetazzo.github.io/2014/03/23/lxc-attach-nsinit-nsenter-docker-0-9/ - container_pid = container_info['State']['Pid'] - if container_pid == 0: - _invalid(status, id_=container, - comment='Container is not running') - return status - full_cmd = ( - 'nsenter --target {pid} --mount --uts --ipc --net --pid' - ' -- {cmd}'.format(pid=container_pid, cmd=cmd) - ) - else: - raise CommandExecutionError( - 'nsenter is not installed on the minion, cannot run command' - ) - else: - raise NotImplementedError( - 'Unknown docker ExecutionDriver \'{0}\'. Or didn\'t find command' - ' to attach to the container'.format(driver)) - - # now execute the command - comment = 'Executed {0}'.format(full_cmd) - try: - ret = __salt__[func](full_cmd, *args, **kwargs) - if ((isinstance(ret, dict) and ('retcode' in ret) and (ret['retcode'] != 0)) - or (func == 'cmd.retcode' and ret != 0)): - _invalid(status, id_=container, out=ret, comment=comment) - else: - _valid(status, id_=container, out=ret, comment=comment) - except Exception: - _invalid(status, id_=container, comment=comment, out=traceback.format_exc()) - return status - - -def load(imagepath): - ''' - Load the specified file at imagepath into docker that was generated from - a docker save command - e.g. `docker load < imagepath` - - imagepath - imagepath to docker tar file - - CLI Example: - - .. code-block:: bash - - salt '*' docker.load /path/to/image - ''' - - status = base_status.copy() - if os.path.isfile(imagepath): - try: - dockercmd = ['docker', 'load', '-i', imagepath] - ret = __salt__['cmd.run'](dockercmd, python_shell=False) - if isinstance(ret, dict) and ('retcode' in ret) and (ret['retcode'] != 0): - return _invalid(status, id_=None, - out=ret, - comment='Command to load image {0} failed.'.format(imagepath)) - - _valid(status, id_=None, out=ret, comment='Image load success') - except Exception: - _invalid(status, id_=None, - comment="Image not loaded.", - out=traceback.format_exc()) - else: - _invalid(status, id_=None, - comment='Image file {0} could not be found.'.format(imagepath), - out=traceback.format_exc()) - - return status - - -def save(image, filename): - ''' - .. versionadded:: 2015.5.0 - - Save the specified image to filename from docker - e.g. `docker save image > filename` - - image - name of image - - filename - The filename of the saved docker image - - CLI Example: - - .. code-block:: bash - - salt '*' docker.save arch_image /path/to/save/image - ''' - status = base_status.copy() - ok = False - try: - _info = _get_image_infos(image) - ok = True - except Exception: - _invalid(status, id_=image, - comment="docker image {0} could not be found.".format(image), - out=traceback.format_exc()) - - if ok: - try: - dockercmd = ['docker', 'save', '-o', filename, image] - ret = __salt__['cmd.run'](dockercmd) - if isinstance(ret, dict) and ('retcode' in ret) and (ret['retcode'] != 0): - return _invalid(status, - id_=image, - out=ret, - comment='Command to save image {0} to {1} failed.'.format(image, filename)) - - _valid(status, id_=image, out=ret, comment='Image save success') - except Exception: - _invalid(status, id_=image, comment="Image not saved.", out=traceback.format_exc()) - - return status - - -def run(container, cmd): - ''' - Wrapper for :py:func:`cmdmod.run` inside a container context - - container - container id (or grain) - - cmd - command to execute - - .. note:: - The return is a bit different as we use the docker struct. - Output of the command is in 'out' and result is always ``True``. - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - CLI Example: - - .. code-block:: bash - - salt '*' docker.run 'ls -l /etc' - ''' - status = base_status.copy() - return _run_wrapper( - status, container, 'cmd.run', cmd) - - -def run_all(container, cmd): - ''' - Wrapper for :py:func:`cmdmod.run_all` inside a container context - - container - container id (or grain) - - cmd - command to execute - - .. note:: - The return is a bit different as we use the docker struct. - Output of the command is in 'out' and result is ``False`` if - command failed to execute. - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - CLI Example: - - .. code-block:: bash - - salt '*' docker.run_all 'ls -l /etc' - ''' - status = base_status.copy() - return _run_wrapper( - status, container, 'cmd.run_all', cmd) - - -def run_stderr(container, cmd): - ''' - Wrapper for :py:func:`cmdmod.run_stderr` inside a container context - - container - container id (or grain) - - cmd - command to execute - - .. note:: - The return is a bit different as we use the docker struct. - Output of the command is in 'out' and result is always ``True``. - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - CLI Example: - - .. code-block:: bash - - salt '*' docker.run_stderr 'ls -l /etc' - ''' - status = base_status.copy() - return _run_wrapper( - status, container, 'cmd.run_stderr', cmd) - - -def run_stdout(container, cmd): - ''' - Wrapper for :py:func:`cmdmod.run_stdout` inside a container context - - container - container id (or grain) - - cmd - command to execute - - .. note:: - The return is a bit different as we use the docker struct. - Output of the command is in 'out' and result is always ``True``. - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - CLI Example: - - .. code-block:: bash - - salt '*' docker.run_stdout 'ls -l /etc' - ''' - status = base_status.copy() - return _run_wrapper( - status, container, 'cmd.run_stdout', cmd) - - -def retcode(container, cmd): - ''' - Wrapper for :py:func:`cmdmod.retcode` inside a container context - - container - container id (or grain) - - cmd - command to execute - - .. note:: - The return is True or False depending on the commands success. - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - CLI Example: - - .. code-block:: bash - - salt '*' docker.retcode 'ls -l /etc' - ''' - status = base_status.copy() - return _run_wrapper( - status, container, 'cmd.retcode', cmd)['status'] - - -def get_container_root(container): - ''' - Get the container rootfs path - - container - container id or grain - - CLI Example: - - .. code-block:: bash - - salt '*' docker.get_container_root - ''' - default_path = os.path.join( - '/var/lib/docker', - 'containers', - _get_container_infos(container)['Id'], - ) - default_rootfs = os.path.join(default_path, 'rootfs') - rootfs_re = re.compile(r'^lxc.rootfs\s*=\s*(.*)\s*$', re.U) - try: - lxcconfig = os.path.join(default_path, 'config.lxc') - with salt.utils.fopen(lxcconfig) as fhr: - lines = fhr.readlines() - rlines = lines[:] - rlines.reverse() - for rline in rlines: - robj = rootfs_re.search(rline) - if robj: - rootfs = robj.groups()[0] - break - except Exception: - rootfs = default_rootfs - return rootfs - - -def _script(status, - container, - source, - args=None, - cwd=None, - stdin=None, - runas=None, - shell=cmdmod.DEFAULT_SHELL, - template='jinja', - umask=None, - timeout=None, - reset_system_locale=True, - run_func_=None, - no_clean=False, - saltenv='base', - output_loglevel='info', - quiet=False, - **kwargs): - try: - if not run_func_: - run_func_ = run_all - rpath = get_container_root(container) - tpath = os.path.join(rpath, 'tmp') - - if 'env' in kwargs: - salt.utils.warn_until( - 'Oxygen', - 'Parameter \'env\' has been detected in the argument list. This ' - 'parameter is no longer used and has been replaced by \'saltenv\' ' - 'as of Salt 2016.11.0. This warning will be removed in Salt Oxygen.' - ) - kwargs.pop('env') - - path = salt.utils.files.mkstemp(dir=tpath) - if template: - __salt__['cp.get_template']( - source, path, template, saltenv, **kwargs) - else: - fn_ = __salt__['cp.cache_file'](source, saltenv) - if not fn_: - return {'pid': 0, - 'retcode': 1, - 'stdout': '', - 'stderr': '', - 'cache_error': True} - shutil.copyfile(fn_, path) - in_path = os.path.join('/', os.path.relpath(path, rpath)) - os.chmod(path, 0o755) - command = in_path + ' ' + str(args) if args else in_path - status = run_func_(container, - command, - cwd=cwd, - stdin=stdin, - output_loglevel=output_loglevel, - quiet=quiet, - runas=runas, - shell=shell, - umask=umask, - timeout=timeout, - reset_system_locale=reset_system_locale) - if not no_clean: - os.remove(path) - except Exception: - _invalid(status, id_=container, out=traceback.format_exc()) - return status - - -def script(container, - source, - args=None, - cwd=None, - stdin=None, - runas=None, - shell=cmdmod.DEFAULT_SHELL, - template='jinja', - umask=None, - timeout=None, - reset_system_locale=True, - no_clean=False, - saltenv='base'): - ''' - Wrapper for :py:func:`cmdmod.script` inside a container context - - container - container id (or grain) - - additional parameters - See :py:func:`cmd.script ` - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - Download a script from a remote location and execute the script in the container. - The script can be located on the salt master file server or on an HTTP/FTP server. - - The script will be executed directly, so it can be written in any available programming - language. - - The script can also be formatted as a template, the default is jinja. Arguments for the - script can be specified as well. - - CLI Example: - - .. code-block:: bash - - salt '*' docker.script salt://docker_script.py - salt '*' docker.script salt://scripts/runme.sh 'arg1 arg2 "arg 3"' - salt '*' docker.script salt://scripts/windows_task.ps1 args=' -Input c:\\tmp\\infile.txt' shell='powershell' - - A string of standard input can be specified for the command to be run using the stdin - parameter. This can be useful in cases where sensitive information must be read from - standard input: - - CLI Example: - - .. code-block:: bash - - salt '*' docker.script salt://scripts/runme.sh stdin='one\\ntwo\\nthree\\nfour\\nfive\\n' - ''' - status = base_status.copy() - - return _script(status, - container, - source, - args=args, - cwd=cwd, - stdin=stdin, - runas=runas, - shell=shell, - template=template, - umask=umask, - timeout=timeout, - reset_system_locale=reset_system_locale, - no_clean=no_clean, - saltenv=saltenv) - - -def script_retcode(container, - source, - cwd=None, - stdin=None, - runas=None, - shell=cmdmod.DEFAULT_SHELL, - template='jinja', - umask=None, - timeout=None, - reset_system_locale=True, - no_clean=False, - saltenv='base'): - ''' - Wrapper for :py:func:`cmdmod.script_retcode` inside a container context - - container - container id (or grain) - - additional parameters - See :py:func:`cmd.script_retcode ` - - .. warning:: - Be advised that this function allows for raw shell access to the named - container! If allowing users to execute this directly it may allow more - rights than intended! - - CLI Example: - - .. code-block:: bash - - salt '*' docker.script_retcode salt://docker_script.py - ''' - status = base_status.copy() - - return _script(status, - container, - source=source, - cwd=cwd, - stdin=stdin, - runas=runas, - shell=shell, - template=template, - umask=umask, - timeout=timeout, - reset_system_locale=reset_system_locale, - run_func_=retcode, - no_clean=no_clean, - saltenv=saltenv) diff --git a/salt/states/dockerio.py b/salt/states/dockerio.py deleted file mode 100644 index 22d62e39030a..000000000000 --- a/salt/states/dockerio.py +++ /dev/null @@ -1,1269 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Manage Docker containers -======================== - -.. deprecated:: 2015.8.0 - Future feature development will be done only in :mod:`dockerng - `. See the documentation for this module for - information on the deprecation path. - -`Docker `_ -is a lightweight, portable, self-sufficient software container -wrapper. The base supported wrapper type is -`LXC `_, -`cgroups `_, and the -`Linux Kernel `_. - -.. note:: - - This state module requires - `docker-py `_ version >= 0.6.0 - which supports `Docker Remote API version 1.12 - `_. - -Available Functions -------------------- - -- built - - .. code-block:: yaml - - corp/mysuperdocker_img: - docker.built: - - path: /path/to/dir/container - -- pulled - - .. code-block:: yaml - - ubuntu: - docker.pulled: - - tag: latest - -- pushed - - .. code-block:: yaml - - corp/mysuperdocker_img: - docker.pushed - -- installed - - .. code-block:: yaml - - mysuperdocker-container: - docker.installed: - - name: mysuperdocker - - hostname: superdocker - - image: corp/mysuperdocker_img - -- loaded - - .. code-block:: yaml - - mysuperdocker-file: - docker.loaded: - - name: mysuperdocker - - source: salt://_files/tmp/docker_image.tar - -- running - - .. code-block:: yaml - - my_service: - docker.running: - - container: mysuperdocker - - image: corp/mysuperdocker_img - - ports: - - "5000/tcp": - HostIp: "" - HostPort: "5000" - - .. note:: - - The ``ports`` argument above is a dictionary. The double - indentation is required for PyYAML to load the data structure - properly as a python dictionary. More information can be found - :ref:`here ` - -- absent - - .. code-block:: yaml - - mys_old_uperdocker: - docker.absent - -- run - - .. code-block:: yaml - - /finish-install.sh: - docker.run: - - cid: mysuperdocker - - unless: grep -q something /var/log/foo - - docker_unless: grep -q done /install_log - -Use Cases ---------- - - Ensures the container is running with the latest image available - - .. code-block:: yaml - - my-service-image: - docker.pulled: - - name: registry/my-service:latest - - force: true - - my-service-container: - docker.installed: - - image: registry/my-service:latest - - watch: - - docker: my-service-image - - my-service: - docker.running: - - container: my-service-container - - watch: - - docker: my-service-container - -.. note:: - - The docker modules are named ``dockerio`` because - the name 'docker' would conflict with the underlying docker-py library. - - -''' - -from __future__ import absolute_import -import functools -import logging - -# Import salt libs -from salt.ext.six import string_types -import salt.utils -import salt.utils.files -import salt.ext.six as six - -# Enable proper logging -log = logging.getLogger(__name__) - -# Define the module's virtual name -__virtualname__ = 'docker' - - -def __virtual__(): - ''' - Only load if the dockerio execution module is available - ''' - if 'docker.version' in __salt__: - return __virtualname__ - return False - - -INVALID_RESPONSE = 'We did not get an acceptable answer from docker' -VALID_RESPONSE = '' -NOTSET = object() - - -def _ret_status(exec_status=None, - name='', - comment='', - result=None, - changes=None): - if not changes: - changes = {} - if exec_status is None: - exec_status = {} - if exec_status: - if result is None: - result = exec_status['status'] - scomment = exec_status.get('comment', None) - if scomment: - comment += '\n' + scomment - out = exec_status.get('out', None) - if out: - if isinstance(out, string_types): - comment += '\n' + out - return { - 'changes': changes, - 'result': result, - 'name': name, - 'comment': comment, - } - - -def _valid(exec_status=None, name='', comment='', changes=None): - return _ret_status(exec_status=exec_status, - comment=comment, - name=name, - changes=changes, - result=True) - - -def _invalid(exec_status=None, name='', comment='', changes=None): - return _ret_status(exec_status=exec_status, - comment=comment, - name=name, - changes=changes, - result=False) - - -def _get_image_name(image, tag): - if ':' not in image: - # backward compatibility: name could be already tagged - return ':'.join((image, tag)) - return image - - -def _parse_volumes(volumes): - ''' - Parse a given volumes state specification for later use in - modules.docker.create_container(). This produces a dict that can be directly - consumed by the Docker API /containers/create. - - Note: this only really exists for backwards-compatibility, and because - modules.dockerio.start() currently takes a binds argument. - - volumes - A structure containing information about the volumes to be included in the - container that will be created, either: - - a bare dictionary - - a list of dictionaries and lists - - .. code-block:: yaml - - # bare dict style - - volumes: - /usr/local/etc/ssl/certs/example.crt: - bind: /etc/ssl/certs/com.example.internal.crt - ro: True - /var/run: - bind: /var/run/host/ - ro: False - - # list of dicts style: - - volumes: - - /usr/local/etc/ssl/certs/example.crt: - bind: /etc/ssl/certs/com.example.internal.crt - ro: True - - /var/run: /var/run/host/ # read-write bound volume - - /var/lib/mysql # un-bound, container-only volume - - note: bind mounts specified like "/etc/timezone:/tmp/host_tz" will fall - through this parser. - - Returns a dict of volume specifications: - - .. code-block:: yaml - - { - 'bindvols': { - '/usr/local/etc/ssl/certs/example.crt': { - 'bind': '/etc/ssl/certs/com.example.internal.crt', - 'ro': True - }, - '/var/run/': { - 'bind': '/var/run/host', - 'ro': False - }, - }, - 'contvols': [ '/var/lib/mysql/' ] - } - - ''' - log.trace("Parsing given volumes dict: " + str(volumes)) - bindvolumes = {} - contvolumes = [] - if isinstance(volumes, dict): - # If volumes as a whole is a dict, then there's no way to specify a non-bound volume - # so we exit early and assume the dict is properly formed. - bindvolumes = volumes - if isinstance(volumes, list): - for vol in volumes: - if isinstance(vol, dict): - for volsource, voldef in vol.items(): - if isinstance(voldef, dict): - target = voldef['bind'] - read_only = voldef.get('ro', False) - else: - target = str(voldef) - read_only = False - source = volsource - else: # isinstance(vol, dict) - if ':' in vol: - volspec = vol.split(':') - source = volspec[0] - target = volspec[1] - read_only = False - try: - if len(volspec) > 2: - read_only = volspec[2] == "ro" - except IndexError: - pass - else: - contvolumes.append(str(vol)) - continue - bindvolumes[source] = { - 'bind': target, - 'ro': read_only - } - result = {'bindvols': bindvolumes, 'contvols': contvolumes} - log.trace("Finished parsing volumes, with result: " + str(result)) - return result - - -def mod_watch(name, sfun=None, *args, **kw): - if sfun == 'built': - # Needs to refresh the image - kw['force'] = True - build_status = built(name, **kw) - result = build_status['result'] - status = _ret_status(build_status, name, result=result, - changes={name: result}) - return status - elif sfun == 'installed': - # Throw away the old container and create a new one - remove_container = __salt__['docker.remove_container'] - remove_status = _ret_status(remove_container(container=name, - force=True), - name=name) - installed_status = installed(name=name, **kw) - result = installed_status['result'] and remove_status['result'] - comment = remove_status['comment'] - status = _ret_status(installed_status, name=name, - result=result, - changes={name: result}, - comment=comment) - return status - elif sfun == 'running': - # Force a restart or kill the container - container = kw.get('container', name) - kill_signal = kw.get('kill_signal') - if kill_signal: - killer = __salt__['docker.kill'] - status = _ret_status(killer(container, signal=kill_signal), - name=name, - changes={name: True}) - else: - restarter = __salt__['docker.restart'] - status = _ret_status(restarter(container), - name=name, - changes={name: True}) - return status - - return {'name': name, - 'changes': {}, - 'result': False, - 'comment': ('watch requisite is not' - ' implemented for {0}'.format(sfun))} - - -def pulled(name, - tag='latest', - force=False, - insecure_registry=False, - *args, - **kwargs): - ''' - Pull an image from a docker registry. (`docker pull`) - - .. note:: - - See first the documentation for `docker login`, `docker pull`, - `docker push`, - and `docker.import_image `_ - (`docker import - `_). - NOTE that we added in SaltStack a way to authenticate yourself with the - Docker Hub Registry by supplying your credentials (username, email & - password) using pillars. For more information, see salt.modules.dockerio - execution module. - - name - Name of the image - - tag - Tag of the image - - force - Pull even if the image is already pulled - - insecure_registry - Set to ``True`` to allow connections to non-HTTPS registries. Default ``False``. - ''' - - inspect_image = __salt__['docker.inspect_image'] - image_name = _get_image_name(name, tag) - image_infos = inspect_image(image_name) - if image_infos['status'] and not force: - return _valid( - name=name, - comment='Image already pulled: {0}'.format(image_name)) - - if __opts__['test']: - comment = 'Image {0} will be pulled'.format(image_name) - return _ret_status(name=name, comment=comment) - - previous_id = image_infos['out']['Id'] if image_infos['status'] else None - pull = __salt__['docker.pull'] - returned = pull(name, tag=tag, insecure_registry=insecure_registry) - if previous_id != returned['id']: - changes = {name: {'old': previous_id, - 'new': returned['id']}} - comment = 'Image {0} pulled'.format(image_name) - else: - changes = {} - comment = '' - return _ret_status(returned, name, changes=changes, comment=comment) - - -def pushed(name, tag='latest', insecure_registry=False): - ''' - Push an image from a docker registry. (`docker push`) - - .. note:: - - See first the documentation for `docker login`, `docker pull`, - `docker push`, - and `docker.import_image `_ - (`docker import - `_). - NOTE that we added in SaltStack a way to authenticate yourself with the - Docker Hub Registry by supplying your credentials (username, email - & password) using pillars. For more information, see - salt.modules.dockerio execution module. - - name - Name of the image - - tag - Tag of the image [Optional] - - insecure_registry - Set to ``True`` to allow connections to non-HTTPS registries. Default ``False``. - ''' - - image_name = _get_image_name(name, tag) - if __opts__['test']: - comment = 'Image {0} will be pushed'.format(image_name) - return _ret_status(name=name, comment=comment) - - push = __salt__['docker.push'] - returned = push(name, tag=tag, insecure_registry=insecure_registry) - log.debug("Returned: "+str(returned)) - if returned['status']: - changes = {name: {'Rev': returned['id']}} - else: - changes = {} - return _ret_status(returned, name, changes=changes) - - -def loaded(name, tag='latest', source=None, source_hash='', force=False): - ''' - Load an image into the local docker registry (`docker load`) - - name - Name of the docker image - - tag - tag of the image (defaults to 'latest') - - source - The source .tar file to download to the minion, created by docker save - this source file can be hosted on either the salt master server, - or on an HTTP or FTP server. - - If the file is hosted on a HTTP or FTP server then the source_hash - argument is also required - - .. note:: - - See first the documentation for Salt `file.managed - `_ - - source_hash - This can be one of the following: - 1. a source hash string - 2. the URI of a file that contains source hash strings - - force - Load even if the image exists - ''' - - inspect_image = __salt__['docker.inspect_image'] - image_name = _get_image_name(name, tag) - image_infos = inspect_image(image_name) - if image_infos['status'] and not force: - return _valid( - name=name, - comment='Image already loaded: {0}'.format(image_name)) - - if __opts__['test']: - comment = 'Image {0} will be loaded'.format(image_name) - return _ret_status(name=name, comment=comment) - - tmp_filename = salt.utils.files.mkstemp() - __states__['file.managed'](name=tmp_filename, - source=source, - source_hash=source_hash) - changes = {} - - if image_infos['status']: - changes['old'] = image_infos['out']['Id'] - remove_image = __salt__['docker.remove_image'] - remove_info = remove_image(image_name) - if not remove_info['status']: - return _invalid(name=name, - comment='Image could not be removed: {0}'.format(name)) - - load = __salt__['docker.load'] - returned = load(tmp_filename) - - image_infos = inspect_image(image_name) - if image_infos['status']: - changes['new'] = image_infos['out']['Id'] - else: - return _invalid( - name=name, - comment='Image {0} was not loaded into docker'.format(image_name)) - - return _ret_status(returned, name, changes=changes) - - -def built(name, - tag='latest', - path=None, - quiet=False, - nocache=False, - rm=True, - force=False, - timeout=None, - *args, **kwargs): - ''' - Build a docker image from a path or URL to a dockerfile. (`docker build`) - - name - Name of the image - - tag - tag of the image (defaults to 'latest') - - path - URL (e.g. `url/branch/docker_dir/dockerfile`) - or filesystem path to the dockerfile - - ''' - inspect_image = __salt__['docker.inspect_image'] - image_name = _get_image_name(name, tag) - image_infos = inspect_image(image_name) - if image_infos['status'] and not force: - return _valid( - name=name, - comment='Image already built: {0}, id: {1}'.format( - image_name, image_infos['out']['Id'])) - - if __opts__['test']: - comment = 'Image {0} will be built'.format(image_name) - return {'name': name, - 'changes': {}, - 'result': None, - 'comment': comment} - - previous_id = image_infos['out']['Id'] if image_infos['status'] else None - build = __salt__['docker.build'] - kw = dict(tag=image_name, - path=path, - quiet=quiet, - nocache=nocache, - rm=rm, - timeout=timeout, - ) - returned = build(**kw) - if previous_id != returned['id']: - changes = {name: {'old': previous_id, - 'new': returned['id']}} - comment = 'Image {0} built'.format(image_name) - else: - changes = {} - comment = '' - return _ret_status(exec_status=returned, - name=name, - changes=changes, - comment=comment) - - -def installed(name, - image, - tag='latest', - command=None, - hostname=None, - user=None, - detach=True, - stdin_open=False, - tty=False, - mem_limit=None, - ports=None, - environment=None, - dns=None, - volumes=None, - volumes_from=None, - cpu_shares=None, - cpuset=None, - *args, **kwargs): - ''' - Ensure that a container with the given name exists; - if not, build a new container from the specified image. - (`docker run`) - - name - Name for the container - - image - Image from which to build this container - - tag - tag of the image (defaults to 'latest') - - environment - Environment variables for the container, either - - a mapping of key, values - - a list of mappings of key, values - ports - List of ports definitions, either: - - a port to map - - a mapping of mapping portInHost : PortInContainer - volumes - List of volumes (see notes for the running function) - - For other parameters, see absolutely first the salt.modules.dockerio - execution module and the `docker-py python bindings for docker - documentation `_ for - `docker.create_container`. - - .. note:: - This command does not verify that the named container - is running the specified image. - ''' - ins_image = __salt__['docker.inspect_image'] - ins_container = __salt__['docker.inspect_container'] - create = __salt__['docker.create_container'] - image_name = _get_image_name(image, tag) - iinfos = ins_image(image_name) - if not iinfos['status']: - return _invalid(comment='Image "{0}" does not exist'.format(image_name)) - cinfos = ins_container(name) - already_exists = cinfos['status'] - # if container exists but is not started, try to start it - if already_exists: - return _valid(comment='Container \'{0}\' already exists'.format(name)) - dports, denvironment = {}, {} - - if __opts__['test']: - comment = 'Container \'{0}\' will be created'.format(name) - return _ret_status(name=name, comment=comment) - - if not ports: - ports = [] - if not volumes: - volumes = [] - if isinstance(environment, dict): - for k in environment: - denvironment[six.text_type(k)] = six.text_type(environment[k]) - if isinstance(environment, list): - for p in environment: - if isinstance(p, dict): - for k in p: - denvironment[six.text_type(k)] = six.text_type(p[k]) - for p in ports: - if not isinstance(p, dict): - dports[str(p)] = {} - else: - for k in p: - dports[str(p)] = {} - - parsed_volumes = _parse_volumes(volumes) - bindvolumes = parsed_volumes['bindvols'] - contvolumes = parsed_volumes['contvols'] - - kw = dict( - binds=bindvolumes, - command=command, - hostname=hostname, - user=user, - detach=detach, - stdin_open=stdin_open, - tty=tty, - mem_limit=mem_limit, - ports=dports, - environment=denvironment, - dns=dns, - volumes=contvolumes, - volumes_from=volumes_from, - name=name, - cpu_shares=cpu_shares, - cpuset=cpuset) - out = create(image_name, **kw) - # if container has been created, even if not started, we mark - # it as installed - changes = 'Container created' - try: - cid = out['out']['info']['id'] - except Exception as e: - log.debug(str(e)) - else: - changes = 'Container {0} created'.format(cid) - out['comment'] = changes - ret = _ret_status(out, name, changes=changes) - return ret - - -def absent(name): - ''' - Ensure that the container is absent; if not, it will - will be killed and destroyed. (`docker inspect`) - - name: - Either the container name or id - ''' - ins_container = __salt__['docker.inspect_container'] - cinfos = ins_container(name) - changes = {} - - if cinfos['status']: - cid = cinfos['id'] - changes[cid] = {} - is_running = __salt__['docker.is_running'](cid) - - if __opts__['test']: - comment = 'Container \'{0}\' will be stopped and destroyed'.format(cid) - return _ret_status(name=name, comment=comment) - - # Stop container gracefully, if running - if is_running: - changes[cid]['old'] = 'running' - __salt__['docker.stop'](cid) - is_running = __salt__['docker.is_running'](cid) - if is_running: - return _invalid(comment=('Container \'{0}\' could not be stopped' - .format(cid))) - else: - __salt__['docker.remove_container'](cid) - is_gone = __salt__['docker.exists'](cid) - if is_gone: - return _valid(comment=('Container \'{0}\'' - ' was stopped and destroyed, '.format(cid)), - changes={name: True}) - else: - return _valid(comment=('Container \'{0}\'' - ' was stopped but could not be destroyed,'.format(cid)), - changes={name: True}) - else: - __salt__['docker.remove_container'](cid) - is_gone = __salt__['docker.exists'](cid) - if is_gone: - return _valid(comment=('Container \'{0}\'' - 'is stopped and was destroyed, '.format(cid)), - changes={name: True}) - else: - return _valid(comment=('Container \'{0}\'' - ' is stopped but could not be destroyed,'.format(cid)), - changes={name: True}) - else: - return _valid(comment='Container \'{0}\' not found'.format(name)) - - -def present(name, image=None, tag='latest', is_latest=False): - ''' - If a container with the given name is not present, this state will fail. - Supports optionally checking for specific image/tag - (`docker inspect`) - - name: - container id - image: - image the container should be running (defaults to any) - tag: - tag of the image (defaults to 'latest') - is_latest: - also check if the container runs the latest version of the image ( - latest defined as the latest pulled onto the local machine) - ''' - ins_container = __salt__['docker.inspect_container'] - cinfos = ins_container(name) - if 'id' in cinfos: - cid = cinfos['id'] - else: - cid = name - if not cinfos['status']: - return _invalid(comment='Container {0} not found'.format(cid or name)) - if cinfos['status'] and image is None: - return _valid(comment='Container {0} exists'.format(cid)) - image_name = _get_image_name(image, tag) - if cinfos['status'] and cinfos['out']['Config']["Image"] == image_name and not is_latest: - return _valid(comment='Container {0} exists and has image {1}'.format(cid, image_name)) - ins_image = __salt__['docker.inspect_image'] - iinfos = ins_image(image_name) - if cinfos['status'] and cinfos['out']['Image'] == iinfos['out']['Id']: - return _valid(comment='Container {0} exists and has latest version of image {1}'.format(cid, image_name)) - return _invalid(comment='Container {0} found with wrong image'.format(cid or name)) - - -def run(name, - cid=None, - hostname=None, - onlyif=None, - unless=None, - docked_onlyif=None, - docked_unless=None, - *args, **kwargs): - ''' - Run a command in a specific container - - You can match by either name or hostname - - name - command to run in the container - - cid - Container id or name - - state_id - state_id - - onlyif - Only execute cmd if statement on the host returns 0 - - unless - Do not execute cmd if statement on the host returns 0 - - docked_onlyif - Only execute cmd if statement in the container returns 0 - - docked_unless - Do not execute cmd if statement in the container returns 0 - - ''' - if hostname: - salt.utils.warn_until( - 'Helium', - 'The \'hostname\' argument has been deprecated.' - ) - retcode = __salt__['docker.retcode'] - drun_all = __salt__['docker.run_all'] - valid = functools.partial(_valid, name=name) - if onlyif is not None: - if not isinstance(onlyif, string_types): - if not onlyif: - return valid(comment='onlyif execution failed') - elif isinstance(onlyif, string_types): - if not __salt__['cmd.retcode'](onlyif) == 0: - return valid(comment='onlyif execution failed') - - if unless is not None: - if not isinstance(unless, string_types): - if unless: - return valid(comment='unless execution succeeded') - elif isinstance(unless, string_types): - if __salt__['cmd.retcode'](unless) == 0: - return valid(comment='unless execution succeeded') - - if docked_onlyif is not None: - if not isinstance(docked_onlyif, string_types): - if not docked_onlyif: - return valid(comment='docked_onlyif execution failed') - elif isinstance(docked_onlyif, string_types): - if not retcode(cid, docked_onlyif): - return valid(comment='docked_onlyif execution failed') - - if docked_unless is not None: - if not isinstance(docked_unless, string_types): - if docked_unless: - return valid(comment='docked_unless execution succeeded') - elif isinstance(docked_unless, string_types): - if retcode(cid, docked_unless): - return valid(comment='docked_unless execution succeeded') - - if __opts__['test']: - comment = 'Command \'{0}\' will be executed on container {1}'.format(name, cid) - return _ret_status(name=name, comment=comment) - - result = drun_all(cid, name) - if result['status']: - return valid(comment=result['comment']) - else: - return _invalid(comment=result['comment'], name=name) - - -def script(*args, **kw): - ''' - Placeholder function for a cmd.script alike. - - .. note:: - - Not yet implemented. - Its implementation might be very similar from - :mod:`salt.states.dockerio.run` - ''' - raise NotImplementedError - - -def running(name, - image, - tag='latest', - container=None, - command=None, - hostname=None, - user=None, - detach=True, - stdin_open=False, - tty=False, - mem_limit=None, - ports=None, - environment=None, - dns=None, - volumes=None, - volumes_from=None, - start=True, - cap_add=None, - cap_drop=None, - privileged=None, - lxc_conf=None, - network_mode=None, - check_is_running=True, - publish_all_ports=False, - links=None, - restart_policy=None, - cpu_shares=None, - cpuset=None, - kill_signal=None, - *args, **kwargs): - ''' - Ensure that a container is running. If the container does not exist, it - will be created from the specified image. (`docker run`) - - name / container - Name for the container - - image - Image from which to build this container - - tag - tag of the image (defaults to 'latest') - - environment - Environment variables for the container, either - - a mapping of key, values - - a list of mappings of key, values - ports - List of ports definitions, either: - - a port to map - - a mapping of mapping portInHost : PortInContainer - - .. code-block:: yaml - - - ports: - - "5000/tcp": - HostIp: "" - HostPort: "5000" - - publish_all_ports - Publish all ports from the port list (default is false, - only meaningful if port does not contain portinhost:portincontainer mapping) - - volumes - List of volumes to mount or create in the container (like ``-v`` of ``docker run`` command), - mapping host directory to container directory. - - To specify a volume in the container in terse list format: - - .. code-block:: yaml - - - volumes: - - "/var/log/service" # container-only volume - - "/srv/timezone:/etc/timezone" # bound volume - - "/usr/local/etc/passwd:/etc/passwd:ro" # read-only bound volume - - You can also use the short dictionary form (note that the notion of - source:target from docker is preserved): - - .. code-block:: yaml - - - volumes: - - /var/log/service: /var/log/service # mandatory read-write implied - - Or, alternatively, to specify read-only mounting, use the extended form: - - .. code-block:: yaml - - - volumes: - - /home/user1: - bind: /mnt/vol2 - ro: True - - /var/www: - bind: /mnt/vol1 - ro: False - - Or (for backwards compatibility) another dict style: - - .. code-block:: yaml - - - volumes: - /home/user1: - bind: /mnt/vol2 - ro: True - /var/www: - bind: /mnt/vol1 - ro: False - - volumes_from - List of containers to share volumes with - - dns - List of DNS servers. - - .. code-block:: yaml - - - dns: - - 127.0.0.1 - - network_mode - - 'bridge': creates a new network stack for the container on the docker bridge - - 'none': no networking for this container - - 'container:[name|id]': reuses another container network stack) - - 'host': use the host network stack inside the container - - .. code-block:: yaml - - - network_mode: host - - restart_policy - Restart policy to apply when a container exits (no, on-failure[:max-retry], always) - - .. code-block:: yaml - - - restart_policy: - MaximumRetryCount: 5 - Name: on-failure - - cap_add - List of capabilities to add in a container. - - cap_drop - List of capabilities to drop in a container. - - check_is_running - Enable checking if a container should run or not. - Useful for data-only containers that must be linked to another one. - e.g. nginx <- static-files - - cpu_shares - CPU shares (relative weight) - - .. code-block:: yaml - - - cpu_shares: 2 - cpuset - CPUs in which to allow execution ('0-3' or '0,1') - - .. code-block:: yaml - - - cpuset: '0-3' - kill_signal - If defined, its value will be sent as a kill signal to the running - container. i.e. It will use client.kill(signal=kill_signal) - instead of client.restart(), when the state is triggered by a watcher - requisite. - - possible use case: Soft reload of nginx - - .. code-block:: yaml - - nginx: - docker.running: - - image: some-fictional-registry.com/nginx - - tag: latest - - kill_signal: SIGHUP - - watch: - - file: /etc/nginx/nginx.conf - - This state will ask nginx to reload (instead of restart) - each time the /etc/nginx/nginx.conf is modified. - - .. versionadded:: 2015.8.0 - - - For other parameters, see salt.modules.dockerio execution module - and the docker-py python bindings for docker documentation - `_ for - `docker.create_container`. - - .. note:: - This command does not verify that the named container - is running the specified image. - ''' - if container is None: - container = name - ins_image = __salt__['docker.inspect_image'] - ins_container = __salt__['docker.inspect_container'] - create = __salt__['docker.create_container'] - image_name = _get_image_name(image, tag) - iinfos = ins_image(image_name) - image_exists = iinfos['status'] - - if not image_exists: - return _invalid(comment='image "{0}" does not exists'.format(image_name)) - - cinfos = ins_container(name) - already_exists = cinfos['status'] - already_exists_with_same_image = ( - # if container is known by name, - already_exists - # and the container is based on expected image, - and cinfos['out']['Image'] == iinfos['out']['Id'] - # then assume it already exists. - ) - - is_running = __salt__['docker.is_running'](container) - - # if container exists but is not started, try to start it - if already_exists_with_same_image and (is_running or not start): - return _valid(comment='container \'{0}\' already exists'.format(name)) - if not already_exists_with_same_image and already_exists: - # Outdated container: It means it runs against an old image. - # We're gonna have to stop and remove the old container, to let - # the name available for the new one. - if __opts__['test']: - comment = 'Will replace outdated container \'{0}\''.format(name) - return _ret_status(name=name, comment=comment) - if is_running: - stop_status = __salt__['docker.stop'](name) - if not stop_status['status']: - return _invalid(comment='Failed to stop outdated container \'{0}\''.format(name)) - - remove_status = __salt__['docker.remove_container'](name) - if not remove_status['status']: - return _invalid(comment='Failed to remove outdated container \'{0}\''.format(name)) - already_exists = False - # now it's clear, the name is available for the new container - - if __opts__['test']: - comment = 'Will create container \'{0}\''.format(name) - return _ret_status(name=name, comment=comment) - - # parse input data - exposeports, bindports, contvolumes, bindvolumes, denvironment, changes = [], {}, [], {}, {}, [] - if not ports: - ports = {} - if not volumes: - volumes = {} - if not volumes_from: - volumes_from = [] - if isinstance(environment, dict): - for key in environment: - denvironment[six.text_type(key)] = six.text_type(environment[key]) - if isinstance(environment, list): - for var in environment: - if isinstance(var, dict): - for key in var: - denvironment[six.text_type(key)] = six.text_type(var[key]) - if isinstance(volumes, dict): - bindvolumes = volumes - if isinstance(volumes, list): - for vol in volumes: - if isinstance(vol, dict): - # get source as the dict key - source = list(vol.keys())[0] - # then find target - if isinstance(vol[source], dict): - target = vol[source]['bind'] - read_only = vol[source].get('ro', False) - else: - target = str(vol[source]) - read_only = False - bindvolumes[source] = {'bind': target, - 'ro': read_only - } - else: - # assume just an own volumes - contvolumes.append(str(vol)) - if isinstance(ports, dict): - bindports = ports - # in dict form all ports bind, so no need for exposeports - if isinstance(ports, list): - for port in ports: - if isinstance(port, dict): - container_port = list(port.keys())[0] - # find target - if isinstance(port[container_port], dict): - host_port = port[container_port]['HostPort'] - host_ip = port[container_port].get('HostIp', '0.0.0.0') - else: - host_port = str(port[container_port]) - host_ip = '0.0.0.0' - bindports[container_port] = { - 'HostPort': host_port, - 'HostIp': host_ip - } - else: - # assume just a port to expose - exposeports.append(str(port)) - - parsed_volumes = _parse_volumes(volumes) - bindvolumes = parsed_volumes['bindvols'] - contvolumes = parsed_volumes['contvols'] - - if not already_exists: - kwargs = dict(command=command, - hostname=hostname, - user=user, - detach=detach, - stdin_open=stdin_open, - tty=tty, - mem_limit=mem_limit, - ports=exposeports, - environment=denvironment, - dns=dns, - binds=bindvolumes, - volumes=contvolumes, - name=name, - cpu_shares=cpu_shares, - cpuset=cpuset) - out = create(image_name, **kwargs) - # if container has been created, even if not started, we mark - # it as installed - try: - cid = out['out']['info']['id'] - log.debug(str(cid)) - except Exception as e: - changes.append('Container created') - log.debug(str(e)) - else: - changes.append('Container {0} created'.format(cid)) - if start: - started = __salt__['docker.start'](name, - binds=bindvolumes, - port_bindings=bindports, - lxc_conf=lxc_conf, - publish_all_ports=publish_all_ports, - links=links, - privileged=privileged, - dns=dns, - volumes_from=volumes_from, - network_mode=network_mode, - restart_policy=restart_policy, - cap_add=cap_add, - cap_drop=cap_drop) - if check_is_running: - is_running = __salt__['docker.is_running'](name) - log.debug("Docker-io running:" + str(started)) - log.debug("Docker-io running:" + str(is_running)) - if is_running: - changes.append('Container \'{0}\' started.\n'.format(name)) - else: - return _invalid(comment=('Container \'{0}\' cannot be started\n{1!s}' - .format(name, started['out'],))) - else: - changes.append('Container \'{0}\' started.\n'.format(name)) - return _valid(comment='\n'.join(changes), changes={name: True}) diff --git a/tests/integration/modules/dockertest.py b/tests/integration/modules/dockertest.py deleted file mode 100644 index a274ca9f3ab7..000000000000 --- a/tests/integration/modules/dockertest.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Tests for integration with Docker's Python library - -:codeauthor: :email:`C. R. Oldham ` -''' - -# Import python libs -from __future__ import absolute_import -import os -import string -import logging - -# Import Salt Testing libs -from salttesting.helpers import ensure_in_syspath, requires_salt_modules -from salttesting import skipIf -ensure_in_syspath('../../') - -# Import salt libs -import integration - -log = logging.getLogger(__name__) - - -@requires_salt_modules('docker') -class DockerTest(integration.ModuleCase): - ''' - Test docker integration - ''' - - def _get_container_id(self, image_name=None): - cmdstring = 'docker ps | grep {0}'.format(image_name) - ret_cmdrun = self.run_function('cmd.run_all', cmd=cmdstring) - ids = [] - for l in ret_cmdrun['stdout'].splitlines(): - try: - ids.append(string.split(ret_cmdrun['stdout'])[0]) - except IndexError: - pass - return ids - - def test_version(self): - ''' - dockerio.version - ''' - ret = self.run_function('docker.version') - ret_cmdrun = self.run_function('cmd.run_all', cmd='docker version | grep "Client version:"') - self.assertEqual('Client version: {0}'.format(ret['out']['Version']), ret_cmdrun['stdout']) - - def test_build(self): - ''' - dockerio.build - - Long timeout here because build will transfer many images from the Internet - before actually creating the final container - ''' - dockerfile_path = os.path.join(integration.INTEGRATION_TEST_DIR, 'files/file/base/') - ret = self.run_function('docker.build', timeout=300, path=dockerfile_path, tag='testsuite_image') - self.assertTrue(ret['status'], 'Image built') - - def test_images(self): - ''' - dockerio.get_images - ''' - ret = self.run_function('docker.get_images') - foundit = False - for i in ret['out']: - try: - if i['RepoTags'][0] == 'testsuite_image:latest': - foundit = True - break - except KeyError: - pass - self.assertTrue(foundit, 'Could not find created image.') - - def test_create_container(self): - ''' - dockerio.create_container - ''' - - ret = self.run_function('docker.create_container', image='testsuite_image', command='echo ping') - self.assertTrue(ret['status'], 'Container was not created') - - @skipIf(True, "Currently broken") - def test_stop(self): - ''' - dockerio.stop - ''' - - container_id = self._get_container_id(image_name='testsuite_image') - self.assertTrue(container_id, 'test_stop: found no containers running') - for i in container_id: - ret = self.run_function('docker.stop', i) - self.assertFalse(self.run_function('docker.is_running', i)) - - @skipIf(True, "Currently broken") - def test_run_stdout(self): - ''' - dockerio.run_stdout - - The testsuite Dockerfile creates a file in the image's /tmp folder called 'cheese' - The Dockerfile is in salt/tests/integration/files/file/base/Dockerfile - - ''' - - run_ret = self.run_function('docker.create_container', image='testsuite_image') - base_container_id = run_ret['id'] - ret = self.run_function('docker.run_stdout', container=base_container_id, cmd="cat /tmp/cheese") - run_container_id = ret['id'] - self.assertEqual(ret['out'], 'The cheese shop is open') - self.run_function('docker.stop', base_container_id) - self.run_function('docker.stop', run_container_id) - self.assertFalse(self.run_function('docker.is_running', base_container_id)) - self.assertFalse(self.run_function('docker.is_running', run_container_id)) - - @skipIf(True, "Currently broken") - def test_commit(self): - ''' - dockerio.commit - ''' - - run_ret = self.run_function('docker.create_container', image='testsuite_image') - log.debug("first container: {0}".format(run_ret)) - base_container_id = run_ret['id'] - ret = self.run_function('docker.run_stdout', container=base_container_id, cmd='echo "The cheese shop is now closed." > /tmp/deadcheese') - log.debug("second container: {0}".format(ret)) - run_container_id = ret['id'] - commit_ret = self.run_function('docker.commit', container=base_container_id, repository='testsuite_committed_img', message='This image was created by the testsuite') - log.debug("post-commit: {0}".format(commit_ret)) - self.run_function('docker.stop', run_container_id) - new_container = self.run_function('docker.create_container', image='testsuite_committed_img') - final_ret = self.run_function('docker.run_stdout', container=new_container['id'], cmd='cat /tmp/cheese') - self.assertEqual(final_ret['out'], 'The cheese shop is now closed.') - - -if __name__ == '__main__': - from integration import run_tests - run_tests(DockerTest) diff --git a/tests/unit/modules/dockerio_test.py b/tests/unit/modules/dockerio_test.py deleted file mode 100644 index 151ebb8c2a63..000000000000 --- a/tests/unit/modules/dockerio_test.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -''' - :codeauthor: :email:`Mike Place ` -''' - -# Import python libs -from __future__ import absolute_import - -# Import Salt Testing libs -from salttesting import TestCase, skipIf -from salttesting.helpers import ensure_in_syspath - -ensure_in_syspath('../../') - -from salt.modules import dockerio - -HAS_DOCKER = dockerio.__virtual__() - - -@skipIf(not HAS_DOCKER, 'The docker execution module must be available to run the DockerIO test case') -class DockerIoTestCase(TestCase): - def test__sizeof_fmt(self): - self.assertEqual('0.0 bytes', dockerio._sizeof_fmt(0)) - self.assertEqual('1.0 KB', dockerio._sizeof_fmt(1024)) - self.assertEqual('1.0 MB', dockerio._sizeof_fmt(1024**2)) - self.assertEqual('1.0 GB', dockerio._sizeof_fmt(1024**3)) - self.assertEqual('1.0 TB', dockerio._sizeof_fmt(1024**4)) - self.assertEqual('1.0 PB', dockerio._sizeof_fmt(1024**5)) - - -if __name__ == '__main__': - from integration import run_tests - run_tests(DockerIoTestCase, needs_daemon=False) diff --git a/tests/unit/states/dockerio_test.py b/tests/unit/states/dockerio_test.py deleted file mode 100644 index 54f51bea97f9..000000000000 --- a/tests/unit/states/dockerio_test.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- - -# Import Python libs -from __future__ import absolute_import -from contextlib import contextmanager - -# Import Salt Testing libs -from salttesting import skipIf, TestCase -from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock - - -@contextmanager -def provision_state(module, fixture): - previous_dict = getattr(module, '__salt__', {}).copy() - try: - module.__dict__.setdefault('__salt__', {}).update(fixture) - yield - finally: - setattr(module, '__salt__', previous_dict) - - -@skipIf(NO_MOCK, NO_MOCK_REASON) -@skipIf(True, 'Skipped: This module has been deprecated.') -class DockerStateTestCase(TestCase): - def test_docker_run_success(self): - from salt.states import dockerio - salt_fixture = {'docker.retcode': MagicMock(return_value=0), - 'docker.run_all': MagicMock( - return_value={'stdout': '.\n..\n', - 'stderr': '', - 'status': True, - 'comment': 'Success', - 'retcode': 0})} - - with provision_state(dockerio, salt_fixture): - result = dockerio.run('ls /', 'ubuntu') - - self.assertEqual(result, {'name': 'ls /', - 'result': True, - 'comment': 'Success', - 'changes': {}}) - - def test_docker_run_failure(self): - from salt.states import dockerio - salt_fixture = {'docker.retcode': MagicMock(return_value=0), - 'docker.run_all': MagicMock( - return_value={'stdout': '', - 'stderr': 'Error', - 'status': False, - 'comment': 'Failure', - 'retcode': 1})} - - with provision_state(dockerio, salt_fixture): - result = dockerio.run('ls /', 'ubuntu') - - self.assertEqual(result, {'name': 'ls /', - 'result': False, - 'comment': 'Failure', - 'changes': {}}) - - def test_docker_run_onlyif(self): - from salt.states import dockerio - salt_fixture = {'docker.retcode': MagicMock(return_value=1), - 'docker.run_all': None} - with provision_state(dockerio, salt_fixture): - result = dockerio.run('ls /', 'ubuntu', - onlyif='ls -l') - self.assertEqual(result, {'name': 'ls /', - 'result': True, - 'comment': 'onlyif execution failed', - 'changes': {}}) - - def test_docker_run_unless(self): - from salt.states import dockerio - salt_fixture = {'docker.retcode': MagicMock(return_value=0), - 'docker.run_all': None} - with provision_state(dockerio, salt_fixture): - result = dockerio.run('ls /', 'ubuntu', - unless='ls -l') - self.assertEqual(result, {'name': 'ls /', - 'result': True, - 'comment': 'unless execution succeeded', - 'changes': {}}) - - def test_docker_run_docked_onlyif(self): - from salt.states import dockerio - salt_fixture = {'docker.retcode': MagicMock(return_value=1), - 'docker.run_all': None} - with provision_state(dockerio, salt_fixture): - result = dockerio.run('ls /', 'ubuntu', - docked_onlyif='ls -l') - self.assertEqual(result, {'name': 'ls /', - 'result': True, - 'comment': 'docked_onlyif execution failed', - 'changes': {}}) - - def test_docker_run_docked_unless(self): - from salt.states import dockerio - salt_fixture = {'docker.retcode': MagicMock(return_value=0), - 'docker.run_all': None} - with provision_state(dockerio, salt_fixture): - result = dockerio.run('ls /', 'ubuntu', - docked_unless='ls -l') - self.assertEqual(result, {'name': 'ls /', - 'result': True, - 'comment': ('docked_unless execution' - ' succeeded'), - 'changes': {}}) - - -if __name__ == '__main__': - from integration import run_tests - run_tests(DockerStateTestCase, needs_daemon=False)