# -*- coding: utf-8 -*-
'''
Management of dockers
=====================

.. versionadded:: 2014.1.0 (Hydrogen)

.. 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 pop continuously dockers, we will map each
  container id  (or image) with a grain whenever it is relevant.
- As a corollary, we will resolve for a container id either directly this
  container 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
  running salt. The version of docker-py should support `version 1.6 of docker
  remote API. <https://docs.docker.io/en/latest/api/docker_remote_api_v1.6/>`_.
- For now, you need docker-py from sources:

    https://github.com/dotcloud/docker-py

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::

    docker-registries:
        registry_url:
            email: foo@foo.com
            password: s3cr3t
            username: foo

- You need at least an entry to the default docker index::

    docker-registries:
        https://index.docker.io/v1:
            email: foo@foo.com
            password: s3cr3t
            username: foo

you can define multiple registries blocks for them to be aggregated, their id
just must finish with -docker-registries::

   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

Would be the equivalent to::

   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

Registry dialog methods
-----------------------

- login
- push
- pull

Docker management
-----------------

- version
- info

Image management
----------------

You have those methods:

- search
- inspect_image
- get_images
- remove_image
- import_image
- build
- tag

Container management
--------------------

You have those methods:

- start
- stop
- restart
- kill
- wait
- get_containers
- inspect_container
- remove_container
- is_running
- top
- ports
- logs
- diff
- commit
- create_container
- export
- get_container_root

Runtime execution within a specific already existing and running container
--------------------------------------------------------------------------

- Idea is to use lxc-attach to execute inside the container context.
- We do not use a "docker run command" but want to execute something inside a
  running container.


You have those methods:

- retcode
- run
- run_all
- run_stderr
- run_stdout
- script
- script_retcode

'''
__docformat__ = 'restructuredtext en'

import datetime
import json
import os
import re
import traceback
import shutil

from salt.modules import cmdmod
from salt.exceptions import CommandExecutionError
from salt._compat import string_types
import salt.utils
from salt.utils.vops import *
from salt.utils.odict import OrderedDict

try:
    import docker
    HAS_DOCKER = True
except ImportError:
    HAS_DOCKER = False

import logging

log = logging.getLogger(__name__)

DEB_DOCKER_VERSION = "1.12"

INVALID_RESPONSE = 'We did not get any expectable 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


def _sizeof_fmt(num):
    '''
    Return disk format size data
    '''
    for x in ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']:
        if num < 1024.0:
            return '%3.1f %s' % (num, x)
        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(version=None, 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 k, p in (('base_url', 'docker.url'),
                 ('version', 'docker.version')):
        param = get(p, NOTSET)
        if param is not NOTSET:
            kwargs[k] = param
    if not kwargs.get('version'):
        os_type = get('os', NOTSET)
        if os_type is NOTSET:
            os_type = get('cust_ostype', NOTSET)
        if os_type is NOTSET:
            os_type = None
        else:
            os_type = os_type.lower()

#        if os_type:
#            if os_type in ['debian', 'ubuntu']:
#                kwargs['version'] = DEB_DOCKER_VERSION

    if timeout is not None:
        # make sure we override default timeout of docker-py
        # only if defined by user.
        kwargs['timeout'] = timeout
    client = docker.Client(**kwargs)
    # force 1..5 API for registry login
    if not version:
        if client._version == '1.4':
            client._version = '1.5'
    if getattr(client, '_cfg', None) is None:
        client._cfg = {
            'Configs': {},
            'rootPath': '/dev/null'
        }
    client._cfg.update(_merge_auth_bits())
    return client


def _merge_auth_bits():
    '''
    Merge the local docker authentication file
    with the pillar configuration
    '''
    cfg = os.path.expanduser('~/.dockercfg')
    try:
        fic = open(cfg)
        try:
            config = json.loads(fic.read())
        finally:
            fic.close()
    except Exception:
        config = {'rootPath': '/dev/null'}
    if not 'Configs' in config:
        config['Configs'] = {}
    config['Configs'].update(
        __pillar__.get('docker-registries', {})
    )
    for k, data in __pillar__.items():
        if k.endswith('-docker-registries'):
            config['Configs'].update(data)
    return config


def _set_id(infos):
    if infos:
        id = None
        if infos.get("Id"): id = infos["Id"]
        elif infos.get("ID"): id = infos["ID"]
        elif infos.get("id"): id = infos["id"]
        if "id" not in infos:
            infos["id"] = id
        if "ID" not in infos:
            infos["ID"] = id
        if "Id" not in infos:
            infos["Id"] = id
    return infos

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 = _set_id(client.inspect_image(image))
        if infos:
            valid(
                status,
                id=infos['id'],
                out=infos,
                comment='found')
    except Exception:
        pass
    status = _set_id(status)
    if not status['id']:
        invalid(status)
        raise CommandExecutionError(
            'ImageID "%s" could not be resolved to '
            'an existing Image' % (image)
        )
    return _set_id(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:
        info = _set_id(client.inspect_container(container))
        if info:
            valid(status,
                  id=info['ID'],
                  out=info)
    except Exception:
        pass
    status = _set_id(status)
    if not status['id']:
        raise CommandExecutionError(
            'Container_id {0} could not be resolved to '
            'an existing container'.format(
                container)
        )
    return _set_id(status['out'])

def get_containers(all=True,
                   trunc=False,
                   since=None,
                   before=None,
                   limit=-1,
                   *args,
                   **kwargs):
    '''
    Get a list of mappings representing all containers

    all
        Return all containers

    trunc
        Set it to True to have the short ID

    Returns a mapping of something which looks like
    container

    CLI Example:

    .. code-block:: bash

        salt '*' docker.get_containers
    '''
    client = _get_client()
    status = base_status.copy()
    ret = client.containers(all=all,
                            trunc=trunc,
                            since=since,
                            before=before,
                            limit=limit)
    if ret:
        valid(status, comment='All containers in out', out=ret)
    else:
        invalid(status)
    return status


def logs(container, *args, **kwargs):
    '''
    Return logs for a specified container

    container
        container id

    CLI Example:

    .. code-block:: bash

        salt '*' docker.logs <container id>
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        info = client.logs(_get_container_infos(container)['id'])
        valid(status, id=container, out=info)
    except Exception:
        invalid(status, id=container, comment="Couldn't retreive container infos or logs for container '%s'"%container, out=traceback.format_exc())
    return status


def commit(container,
           repository=None,
           tag=None,
           message=None,
           author=None,
           conf=None,
           *args,
           **kwargs):
    '''
    Commit a container (promotes it to an image)

    container
        container id
    repository
        repository/imageName to commit to
    tag
        optional tag
    message
        optional commit message
    author
        optional author
    conf
        optional conf

    CLI Example:

    .. code-block:: bash

        salt '*' docker.commit <container id>
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        container = _get_container_infos(container)['id']
        info = _set_id(client.commit(
            container,
            repository=repository,
            tag=tag,
            message=message,
            author=author,
            conf=conf))
        found = False
        for k in 'Id', 'id', 'ID':
            if k in info:
                found = True
                id = info[k]
        if not found:
            raise Exception('Invalid commit return')
        image = _get_image_infos(id)['id']
        comment = 'Image {0} created from {1}'.format(image, container)
        valid(status, id=image, out=info, comment=comment)
    except Exception:
        invalid(status, id=container, comment="Couldn't commit container '%s' to repository '%s'"%(container,repository), out=traceback.format_exc())
    return status


def diff(container, *args, **kwargs):
    '''
    Get container diffs

    container
        container id

    CLI Example:

    .. code-block:: bash

        salt '*' docker.diff <container id>
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        info = _set_id(client.diff(_get_container_infos(container)['id']))
        valid(status, id=container, out=info)
    except Exception:
        invalid(status, id=container, out=traceback.format_exc(), comment="Couldn't get container diffs '%s'"%(container))
    return status


def export(container, path, *args, **kwargs):
    '''
    Export a container to a file

    container
        container id
    path
        path to the export

    CLI Example:

    .. code-block:: bash

        salt '*' docker.export <container id>
    '''
    try:
        ppath = os.path.abspath(path)
        fic = open(ppath, 'w')
        status = base_status.copy()
        client = _get_client()
        response = client.export(_get_container_infos(container)['id'])
        try:
            byte = response.read(4096)
            fic.write(byte)
            while byte != '':
                # Do stuff with byte.
                byte = response.read(4096)
                fic.write(byte)
        finally:
            fic.flush()
            fic.close()
        valid(status,
              id=container, out=ppath,
              comment='Exported to {0}'.format(ppath))
    except Exception:
        invalid(status, id=container, comment="Couldn't export container '%s' to path '%s'"%(container,path), out=traceback.format_exc())
    return status


def create_container(image,
                     command=None,
                     hostname=None,
                     user=None,
                     detach=True,
                     entrypoint=None,
                     stdin_open=False,
                     tty=False,
                     mem_limit=0,
                     cpu_shares=None,
                     ports=None,
                     environment=None,
                     dns=None,
#                     devices=None,
#                     port_bindings=None,
#                     binds=None,
                     volumes=None,
                     volumes_from=None,
                     name=None,
                     *args, **kwargs):
    '''
    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
    environment
        environment variable mapping ({'foo':'BAR'})
    dns
        list of DNS servers
    ports
        ports redirections ({'222': {}})
    volumes
        list of volumes mapping::

            (['/mountpoint/in/container:/guest/foo',
              '/same/path/mounted/point'])

    tty
        attach ttys
    stdin_open
        let stdin open
    volumes_from
        container to get volumes definition from
    name
        name given to container

    EG:

        salt-call docker.create_container o/ubuntu volumes="['/s','/m:/f']"

    mem_limit:
        memory size limit

    cpu_shares:
        cpu shares authorized

    CLI Example:

    .. code-block:: bash

        salt '*' docker.create_container <image>
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        mountpoints = {}
#        if not binds:
#            binds = {}
        binds = {}
        # create empty mountpoints for them to be
        # editable
        # either we have a list of guest or host:guest
        if isinstance(volumes, list):
            for mountpoint in volumes:
                mounted = mountpoint
                if ':' in mountpoint:
                    parts = mountpoint.split(':')
                    mountpoint = parts[1]
                    mounted = parts[0]
                mountpoints[mountpoint] = {}
                binds[mounted] = mountpoint
        info = _set_id(client.create_container(
            image=image,
            command=command,
            hostname=hostname,
            user=user,
            entrypoint=entrypoint,
            detach=detach,
            stdin_open=stdin_open,
            tty=tty,
            mem_limit=mem_limit,
            ports=ports,
            environment=environment,
            dns=dns,
            volumes=mountpoints,
            volumes_from=volumes_from,
#            devices=devices,
#            port_bindings=bindings,
#            binds=binds,
            name=name,
            cpu_shares=cpu_shares
        ))
        container = info['Id']
        callback = valid
        comment = 'Container created'
        out = {
            'info': _get_container_infos(container),
            'out': info
        }
        return callback(status, id=container, comment=comment, out=out)
    except Exception:
        invalid(status, id=image, comment="Couldn't create countainer from image '%s'"%(image), out=traceback.format_exc())
    return status


def version(*args, **kwargs):
    '''
    Get docker version

    CLI Example:

    .. code-block:: bash

        salt '*' docker.version
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        info = _set_id(client.version())
        valid(status, out=info)
    except Exception:
        invalid(status, comment="Couldn't fetch curent Docker version.", out=traceback.format_exc())
    return status


def info(*args, **kwargs):
    '''
    Get the version information about docker

    :rtype: dict
    :returns: A status message with the command output

    CLI Example:

    .. code-block:: bash

        salt '*' docker.info
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        info = _set_id(client.info())
        valid(status, out=info)
    except Exception:
        invalid(status, comment="Couldn't get Docker information", out=traceback.format_exc())
    return status


def port(container, private_port, *args, **kwargs):
    '''
    Private/Public for a specific 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 <container id>
    '''
    status = base_status.copy()
    client = _get_client()
    try:
        info = _set_id(client.port(
            _get_container_infos(container)['id'],
            port))
        valid(status, id=container, out=info)
    except Exception:
        invalid(status, id=container, comment="Unable to map ports on container '%s'"%(container), out=traceback.format_exc())
    return status


def stop(container, timeout=10, *args, **kwargs):
    '''
    Stop a running container

    :type container: string
    :param container: The container id to stop

    :type timeout: int
    :param timeout: Wait for a timeout to let the container exit gracefully
        before killing it

    :rtype: dict
    :returns: A status message with the command output
          ex::

            {'id': 'abcdef123456789',
             'status': True}

    CLI Example:

    .. code-block:: bash

        salt '*' docker.stop <container id>
    '''
    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=(
                    'Unable to stop your container {0}').format(container))
    return status


def kill(container, *args, **kwargs):
    '''
    Kill a running container

    :type container: string
    :param container: The container id to kill

    :rtype: dict
    :returns: A status message with the command output
          ex::

            {'id': 'abcdef123456789',
           'status': True}

    CLI Example:

    .. code-block:: bash

        salt '*' docker.kill <container id>
    '''
    client = _get_client()
    status = base_status.copy()
    try:
        dcontainer = _get_container_infos(container)['id']
        if is_running(dcontainer):
            client.kill(dcontainer)
            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=(
                    'Unable to kill your container {0}').format(container))
    return status


def restart(container, timeout=10, *args, **kwargs):
    '''
    Restart a running container

    :type container: string
    :param container: The container id to restart

    :type timout: int
    :param timeout: Wait for a timeout to let the container exit gracefully
        before killing it

    :rtype: dict
    :returns: A status message with the command output
          ex::

            {'id': 'abcdef123456789',
           'status': True}

    CLI Example:

    .. code-block:: bash

        salt '*' docker.restart <container id>
    '''
    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=(
                    'Unable to restart your container {0}').format(container))
    return status


def start(container, binds=None, ports=None, port_bindings=None,
          lxc_conf=None, publish_all_ports=None, links=None,
          privileged=False, devices=None,
          *args, **kwargs):
    '''
    restart the specified container

    container
        Container id
    Returns the status mapping as usual
         {'id': id of the container,
          'status': True if started }

    CLI Example:

    .. code-block:: bash

        salt '*' docker.start <container id>
    '''
    if not binds:
        binds = {}
    if not ports:
        ports = {}
    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:
                bindings = {}
                for k, v in port_bindings.iteritems():
                    bindings[k] = (v.get('HostIp', ''), v['HostPort'])
            cap_add = (["SYS_ADMIN"] if devices else None)
            client.start(dcontainer, binds=binds, port_bindings=bindings,
                         lxc_conf=lxc_conf, devices=devices,
                         publish_all_ports=publish_all_ports, links=links,
                         privileged=privileged, cap_add=cap_add)
            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=(
                    'Unable to start your container {0}').format(container))
    return status


def wait(container, *args, **kwargs):
    '''
    Blocking wait for a container exit gracefully without
    timeout killing it

    container
        Container id
    Return container id if successful
         {'id': id of the container,
          'status': True if stopped }

    CLI Example:

    .. code-block:: bash

        salt '*' docker.wait <container id>
    '''
    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=(
                    'Unable to wait your container {0}').format(container))
    return status


def exists(container, *args, **kwargs):
    '''
    Check if a given container exists

    :type container: string
    :param container: Container id

    :rtype: boolean:

    CLI Example:

    .. code-block:: bash

        salt '*' docker.exists <container>

    '''
    try:
        _get_container_infos(container)
        return True
    except Exception:
        return False


def is_running(container, *args, **kwargs):
    '''
    Is this container running

    container
        Container id

    Return boolean

    CLI Example:

    .. code-block:: bash

        salt '*' docker.is_running <container id>
    '''
    try:
        infos = _get_container_infos(container)
        return infos.get('State', {}).get('Running')
    except Exception:
        return False


def remove_container(container=None, force=False, v=False, *args, **kwargs):
    '''
    Removes a container from a docker installation

    container
        Container id to remove
    force
        By default, do not remove a running container, set this
        to remove it unconditionally
    v
        verbose mode

    Return True or False in the status mapping and also
    any information about docker in status['out']

    CLI Example:

    .. code-block:: bash

        salt '*' docker.remove_container <container id>
    '''
    client = _get_client()
    status = base_status.copy()
    status['id'] = container
    dcontainer = None
    try:
        try:
            dcontainer = _get_container_infos(container)['id']
        except Exception:
            status['status'] = True
            status['comment'] = 'Container {0} was removed'.format(container)
        else:
            if is_running(dcontainer):
                if not force:
                    invalid(status, id=container, out=None,
                            comment=(
                                'Container {0} is running, '
                                'won\'t remove it').format(container))
                    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(), comment="Unable to remove container '%s'"%(container))
    return status


def top(container, *args, **kwargs):
    '''
    Run the docker top command on a specific container

    container
        Container id

    Returns in the 'out' status mapping a mapping for
    those running processes::

       {
            'Titles': top titles list,
            'processes': list of ordered by
                         titles processes information,
            'mprocesses': list of mappings processes information
            constructed above the upon information
       }

    CLI Example:

    .. code-block:: bash

        salt '*' docker.top <container id>
    '''
    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 = 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(), comment="Unable to get stats on container '%s'"%(container))
    return status


def inspect_container(container, *args, **kwargs):
    '''
    Get container information. This is similar to the docker inspect command.

    :type container: string
    :param container: The id of the container to inspect

    :rtype: dict
    :returns: A status message with the command output

    CLI Example:

    .. code-block:: bash

        salt '*' docker.inspect_container <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 exist: {0}'
                ).format(container))
    return _set_id(status)


def login(username=None, password=None, email=None, url=None, client=None, *args, **kwargs):
    '''
    Wrapper to the docker.py login method, does not do much yet

    CLI Example:

    .. code-block:: bash

        salt '*' docker.login <container id>
    '''
    status = base_status.copy()
    status['id'] = username
    try:
        c = (_get_client() if not client else client)
        lg = c.login(username, password, email, url)
        valid(status, id=username, out=lg, comment="%s logged to %s"%(username,(url if url else "default repo")))
    except Exception:
        invalid(status, id=username, out=traceback.format_exc(),
                comment="%s can't login to repo %s"%(username,(url if url else "default repo")))
    return status


def search(term, *args, **kwargs):
    '''
    Search for an image on the registry

    :type term: string
    :param term: The search keyword to query

    CLI Example:

    .. code-block:: bash

        salt '*' docker.search <term>
    '''
    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, logs, *args, **kwargs):
    '''
    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 log in logs:
            if isinstance(log, dict):
                if 'errorDetail' in log:
                    if 'code' in log['errorDetail']:
                        msg = '\n{0}\n{1}: {2}'.format(
                            log['error'],
                            log['errorDetail']['code'],
                            log['errorDetail']['message']
                        )
                    else:
                        msg = '\n{0}\n{1}'.format(
                            log['error'],
                            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, *args, **kwargs):
    '''
    Import content from a local tarball or a URL to a docker image

    :type src: string
    :param src: The content to import (URL, absolute path to a tarball)

    :type repo: string
    :param repo: The repository to import to

    :type tag: string
    :param tag: An optional tag to set

    CLI Example:

    .. code-block:: bash

        salt '*' docker.import_image <src> <repo> [tag]
    '''
    client = _get_client()
    status = base_status.copy()
    try:
        ret = client.import_image(src, repository=repo, tag=tag)
        if ret:
            logs, info = _parse_image_multilogs_string(ret, repo)
            _create_image_assemble_error_status(status, ret, logs)
            if status['status'] is not False:
                infos = _get_image_infos(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(), comment="Unable to import the content from '%s' to an image"%(src))
    return status


def tag(image, repository, tag=None, force=False, *args, **kwargs):
    '''
    Tag an image into a repository

    :type image: string
    :param image: The image to tag

    :type repository: string
    :param repository: The repository to tag the image

    :type tag: string
    :param tag: The tag to apply

    :type force: boolean
    :param force: Forces application of the tag

    CLI Example:

    .. code-block:: bash

        salt '*' docker.tag <image> <repository> [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, filters=None, *args, **kwargs):
    '''
    List docker images

    :type name: string
    :param name: A repository name to filter on

    :type quiet: boolean
    :param quiet: Only show image ids

    :type all: boolean
    :param all: Show all images

    :rtype: dict
    :returns: A status message with the command output

    CLI Example:

    .. code-block:: bash

        salt '*' docker.get_images [name] [quiet=True|False] [all=True|False]
    '''
    client = _get_client()
    status = base_status.copy()
    try:
        infos = client.images(name=name, quiet=quiet, all=all, filters=filters)
        for i in range(len(infos)):
            inf = _set_id(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(), comment="Unable to list Docker images")
    return status


def clean_images(name=None, quiet=False, all=True, *args, **kwargs):
    '''
    Clean old docker images
    '''
    client = _get_client()
    status = base_status.copy()
    comment = ""
    try:
        infos = client.images(name=name, quiet=quiet, all=all, filters={"dangling":True})
        for img in infos:
            img = _set_id(img)
            iid = img.get("id",None)
            if not iid:
                continue
            ret = remove_image(iid)
            if ret.get("comment"):
                comment += "%s\n"%ret["comment"]
        valid(status, out=None, comment=comment)
    except Exception:
        invalid(status, out=traceback.format_exc(), comment="%sUnable to clean properly Docker images"%comment)
    return status


def build(path=None,
          tag=None,
          quiet=False,
          fileobj=None,
          nocache=False,
          rm=True,
          timeout=None,
          *args, **kwargs):
    '''
    Build a docker image from a dockerfile or an URL

    You can either:

        - give the url/branch/docker_dir
        - give a path on the file system

    path
        URL or path in the filesystem to the dockerfile
    tag
        Tag of the image
    quiet
        quiet mode
    nocache
        do not use docker image cache
    rm
        remove intermediate commits
    timeout
        timeout is seconds before aborting

    CLI Example:

    .. code-block:: bash

        salt '*' docker.build
    '''
    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, tuple):
                id, out = ret[0], ret[1]
                c_out = out
                if id:
                    valid(status, id=id, out=out, comment='Image built from Dockerfile')
                else:
                    invalid(status, id=id, out=out, comment="Counldn't create image from Dockerfile\nPossibly Dockerfile badly formated?")
        except Exception:
            invalid(status,
                    out=traceback.format_exc(),
                    comment='Unexpected error while building an image')
            return status
    return status


def remove_image(image, *args, **kwargs):
    '''
    Remove an image from a system.

    :type image: string
    :param image: The image to remove

    :rtype: string
    :returns: A status message.

    CLI Example:

    .. code-block:: bash

        salt '*' docker.remove_image <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 %s'%(image))
        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, *args, **kwargs):
    '''
    Inspect the status of an image and return relative data

    CLI Example:

    .. code-block:: bash

        salt '*' docker.inspect_image <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: %s'%(image))
    return _set_id(status)

def _parse_image_multilogs_string(ret, repo, repotag=None):
    '''
    Parse image log strings into grokable data
    '''
    if not repotag:
        repotag = repo
    logs, infos = [], None
    ret = ret.strip()
    if ret and ret.startswith('{') and ret.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
                logs.append(buf)
                buf = ''
        logs.reverse()
        # search last layer grabbed
        for l in logs:
            if isinstance(l, dict):
                valid = ["Already exists",'Download complete']
                if l.get('status') in valid:
                    infos = _get_image_infos(repotag)
                    break
    return 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 log in logs:
            if isinstance(log, dict):
                if 'errorDetail' in log:
                    if 'code' in log['errorDetail']:
                        msg = '\n{0}\n{1}: {2}'.format(
                            log['error'],
                            log['errorDetail']['code'],
                            log['errorDetail']['message']
                        )
                    else:
                        msg = '\n{0}\n{1}'.format(
                            log['error'],
                            log['errorDetail']['message'],
                        )
                    comment += msg
    except Exception:
        out = traceback.format_exc()
    invalid(status, out=out, comment=comment)
    return status


def pull(repo, tag=None, username=None, password=None, email=None, *args, **kwargs):
    '''
    Pulls an image from any registry. See above documentation for
    how to configure authenticated access.

    :type repo: string
    :param repo: The repository to pull. \
        [registryurl://]REPOSITORY_NAME
        eg::

            index.docker.io:MyRepo
            superaddress.cdn:MyRepo
            MyRepo

    :type tag: string
    :param tag: The specific tag  to pull

    :rtype: dict
    :returns: A status message with the command output
        Example:

        .. code-block:: yaml

            ----------
            comment:
                Image NAME was pulled (ID
            id:
                None
            out:
                ----------
                - id:
                    2c80228370c9
                - status:
                    Download complete
                ----------
                - id:
                    2c80228370c9
                - progress:
                    [=========================>                         ]
                - status:
                    Downloading
                ----------
                - id:
                    2c80228370c9
                - status
                    Pulling image (latest) from foo/ubuntubox
                ----------
                - status:
                    Pulling repository foo/ubuntubox
            status:
                True

    CLI Example:

    .. code-block:: bash

        salt '*' docker.pull <repository> [tag]
    '''
    client = _get_client()
    status = base_status.copy()
    try:
        registry, repo_name = docker.auth.resolve_repository_name(repo)
        if username:
            url = (registry if registry else None)
            lg = login(username,password,email,url=url,client=client)
            if not lg.get("status"):
                invalid(status,comment=lg.get("comment"),out=lg.get("out"))
                return status

#        registry, repo_name = docker.auth.resolve_repository_name(repo)
        ret = client.pull(repo, tag=tag)
        if ret:
            repotag = (repo if not tag else '{0}:{1}'.format(repo, tag))
            logs, infos = _parse_image_multilogs_string(ret, repo, repotag=repotag)
            if infos:
                valid(status,
                      out=logs if logs else ret,
                      id=infos['id'],
                      comment='Image {0} was pulled ({1})'.format(
                          repotag, infos['id']))
            else:
                _pull_assemble_error_status(status, ret, logs)
        else:
            invalid(status)
    except Exception:
        invalid(status, id=repo, out=traceback.format_exc(), comment="An error has occured pulling repo: %s"%(repo))
    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 log in logs:
            if isinstance(log, dict):
                if 'errorDetail' in log:
                    if 'code' in log['errorDetail']:
                        msg = '\n{0}\n{1}: {2}'.format(
                            log['error'],
                            log['errorDetail']['code'],
                            log['errorDetail']['message']
                        )
                    else:
                        msg = '\n{0}\n{1}'.format(
                            log['error'],
                            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, username=None, password=None, email=None, *args, **kwargs):
    '''
    Pushes an image from any registry
    See this top level documentation to know
    how to configure authenticated access

    repo
        [registryurl://]REPOSITORY_NAME_image
        eg::

            index.docker.io:MyRepo/image
            superaddress.cdn:MyRepo/image
            MyRepo/image

    CLI Example:

    .. code-block:: bash

        salt '*' docker.push <repo>
    '''
    client = _get_client()
    status = base_status.copy()

    repository = ("%s:%s"%(repo,tag) if tag else repo)
    try:
        registry, repo_name = docker.auth.resolve_repository_name(repo)
        if username:
            url = (registry if registry else None)
            lg = login(username,password,email,url=url,client=client)
#            #DEBUG
#            print "####### LOGIN #######"
#            print lg
#            print "####### /LOGIN #######"
            if not lg.get("status"):
                invalid(status,comment=lg.get("comment"),out=lg.get("out"))
                return status

        registry, repo_name = docker.auth.resolve_repository_name(repo)
        ret = client.push(repository)
        logs, infos = _parse_image_multilogs_string(ret, repo_name)
#        # DEBUG
#        print "RET=%s"%ret
#        print "LOGS=%s"%logs
#        print "INFOS=%s"%infos
        if logs:
            laststatus = logs[0].get('status', None)
            if laststatus and ('already pushed' in laststatus):
                status['changes'] = False
            elif laststatus and (('Pushing tags for rev' in laststatus)
                                 or ('Pushing tag for rev' in laststatus)):
                status['changes'] = True
            if status.get('changes') != None:
                status['status'] = True
                status['id'] = _get_image_infos(repository)['id']
                status['comment'] = 'Image {0}({1}) was pushed'.format(
                    repository, status['id'])
                status['out'] = obj_to_print(logs[::-1], "")
            else:
                _push_assemble_error_status(status, ret, logs)
        else:
            _push_assemble_error_status(status, ret, logs)

    except Exception:
        invalid(status, id=repo, out=traceback.format_exc(), comment="An error has occured pushing repo: %s"%(repository))

    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 lxc-attach to
    execute inside a container context

    status
        status object
    container
        container id or grain to execute in
    func
        cmd function to execute
    cmd
        command to execute in container
    '''
    try:
        cid = _get_container_infos(container)['id']
        dcmd = 'lxc-attach -n {0} -- {1}'.format(cid, cmd)
        comment = 'Executed {0}'.format(dcmd)
        try:
            f = __salt__[func]
            ret = f(dcmd, *args, **kwargs)
            if (
                (
                    isinstance(ret, dict)
                    and (
                        ('retcode' in ret)
                        and (ret['retcode'] != 0)
                    )
                )
                or (func == 'cmd.retcode' and ret != 0)
            ):
                return invalid(status, id=container, out=ret, comment=comment)
            valid(status, id=container, out=ret, comment=comment,)
        except Exception:
            invalid(status, id=container,
                    comment=comment, out=traceback.format_exc())
    except Exception:
        invalid(status, id=container, out=traceback.format_exc(), comment="Unable to run command '%s'\nOn container: %s"%(cmd,container))
    return status


def run(container, cmd, *args, **kwargs):
    '''
    Wrapper for cmdmod.run inside a container context

    container
        container id (or grain)

    Other params:
        See cmdmod documentation

    The return is a bit different as we use the docker struct,
    The output of the command is in 'out'
    The 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 <container id> 'ls -l /etc'
    '''
    status = base_status.copy()
    return _run_wrapper(
        status, container, 'cmd.run', cmd, *args, **kwargs)


def run_all(container, cmd, *args, **kwargs):
    '''
    Wrapper for cmdmod.run_all inside a container context

    container
        container id (or grain)

    Other params:
        See cmdmod documentation

    The return is a bit different as we use the docker struct,
    The output of the command is in 'out'
    The result if false if command failed

    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 <container id> 'ls -l /etc'
    '''
    status = base_status.copy()
    return _run_wrapper(
        status, container, 'cmd.run_all', cmd, *args, **kwargs)


def run_stderr(container, cmd, *args, **kwargs):
    '''
    Wrapper for cmdmod.run_stderr inside a container context

    container
        container id (or grain)

    Other params:
        See cmdmod documentation

    The return is a bit different as we use the docker struct,
    The output of the command is in 'out'
    The 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 <container id> 'ls -l /etc'
    '''
    status = base_status.copy()
    return _run_wrapper(
        status, container, 'cmd.run_stderr', cmd, *args, **kwargs)


def run_stdout(container, cmd, *args, **kwargs):
    '''
    Wrapper for cmdmod.run_stdout inside a container context

    container
        container id (or grain)

    Other params:
        See cmdmod documentation

    The return is a bit different as we use the docker struct,
    The output of the command is in 'out'
    The 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 <container id> 'ls -l /etc'
    '''
    status = base_status.copy()
    return _run_wrapper(
        status, container, 'cmd.run_stdout', cmd, *args, **kwargs)


def retcode(container, cmd, *args, **kwargs):
    '''
    Wrapper for cmdmod.retcode inside a container context

    container
        container id (or grain)

    Other params:
        See cmdmod documentation

    The return is a bit different as we use the docker struct,
    The output of the command is in 'out'
    The result is false if command failed

    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 <container id> 'ls -l /etc'
    '''
    status = base_status.copy()
    return _run_wrapper(
        status, container, 'cmd.retcode', cmd, *args, **kwargs)


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 <container id>
    '''
    default_path = os.path.join(
        '/var/lib/docker',
        'containers',
        _get_container_infos(container)['id'],
    )
    default_rootfs = os.path.join(default_path, 'roofs')
    rootfs_re = re.compile(r'^lxc.rootfs\s*=\s*(.*)\s*$', re.U)
    try:
        lxcconfig = os.path.join(
            default_path, 'config.lxc')
        f = open(lxcconfig)
        try:
            lines = f.readlines()
            rlines = lines[:]
            rlines.reverse()
            for rl in rlines:
                robj = rootfs_re.search(rl)
                if robj:
                    rootfs = robj.groups()[0]
                    break
        finally:
            f.close()
    except Exception:
        rootfs = default_rootfs
    return rootfs


def _script(status,
            container,
            source,
            args=None,
            cwd=None,
            stdin=None,
            runas=None,
            shell=cmdmod.DEFAULT_SHELL,
            env=None,
            template='jinja',
            umask=None,
            timeout=None,
            reset_system_locale=True,
            run_func_=None,
            no_clean=False,
            saltenv='base',
            **kwargs):
    try:
        if not run_func_:
            run_func_ = run_all
        rpath = get_container_root(container)
        tpath = os.path.join(rpath, 'tmp')

        if isinstance(env, string_types):
            salt.utils.warn_until(
                'Boron',
                'Passing a salt environment should be done using \'saltenv\' '
                'not \'env\'. This functionality will be removed in Salt '
                'Boron.'
            )
            # Backwards compatibility
            saltenv = env

        path = salt.utils.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, 0755)
        command = in_path + ' ' + str(args) if args else in_path
        status = run_func_(container,
                           command,
                           cwd=cwd,
                           stdin=stdin,
                           output_loglevel=kwargs.get('output_loglevel', 'info'),
                           quiet=kwargs.get('quiet', False),
                           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(), comment="Unable to run script '%s' on container '%s'"%(source,container))
    return status


def script(container,
           source,
           args=None,
           cwd=None,
           stdin=None,
           runas=None,
           shell=cmdmod.DEFAULT_SHELL,
           env=None,
           template='jinja',
           umask=None,
           timeout=None,
           reset_system_locale=True,
           no_clean=False,
           saltenv='base',
           *nargs,
           **kwargs):
    '''
    Same usage as cmd.script but running inside a container context

    container
        container id or grain
    others params and documentation
        See cmd.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 <container id> salt://docker_script.py
    '''
    status = base_status.copy()

    if isinstance(env, string_types):
        salt.utils.warn_until(
            'Boron',
            'Passing a salt environment should be done using \'saltenv\' '
            'not \'env\'. This functionality will be removed in Salt '
            'Boron.'
        )
        # Backwards compatibility
        saltenv = env

    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,
                   **kwargs)


def script_retcode(container,
                   source,
                   cwd=None,
                   stdin=None,
                   runas=None,
                   shell=cmdmod.DEFAULT_SHELL,
                   env=None,
                   template='jinja',
                   umask=None,
                   timeout=None,
                   reset_system_locale=True,
                   no_clean=False,
                   saltenv='base',
                   *args,
                   **kwargs):
    '''
    Same usage as cmd.script_retcode but running inside a container context

    container
        container id or grain
    others params and documentation
        See cmd.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 <container id> salt://docker_script.py
    '''

    if isinstance(env, string_types):
        salt.utils.warn_until(
            'Boron',
            'Passing a salt environment should be done using \'saltenv\' '
            'not \'env\'. This functionality will be removed in Salt '
            'Boron.'
        )
        # Backwards compatibility
        saltenv = env

    return _script(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,
                   **kwargs)
