Executes a Salt function inside a running container

    .. versionadded:: 2016.11.0

    The container does not need to have Salt installed, but Python is required.

    name
        Container name or ID

    function
        Salt execution module function

    CLI Example:

    .. code-block:: bash

        salt myminion docker.call test.ping
        salt myminion test.arg arg1 arg2 key1=val1
        salt myminion dockerng.call compassionate_mirzakhani test.arg arg1 arg2 key1=val1

def call(name, function, *args, **kwargs):
    '''
    Executes a Salt function inside a running container

    .. versionadded:: 2016.11.0

    The container does not need to have Salt installed, but Python is required.

    name
        Container name or ID

    function
        Salt execution module function

    CLI Example:

    .. code-block:: bash

        salt myminion docker.call test.ping
        salt myminion test.arg arg1 arg2 key1=val1
        salt myminion dockerng.call compassionate_mirzakhani test.arg arg1 arg2 key1=val1

    '''
    # where to put the salt-thin
    thin_dest_path = _generate_tmp_path()
    mkdirp_thin_argv = ['mkdir', '-p', thin_dest_path]

    # make thin_dest_path in the container
    ret = run_all(name, subprocess.list2cmdline(mkdirp_thin_argv))
    if ret['retcode'] != 0:
        return {'result': False, 'comment': ret['stderr']}

    if function is None:
        raise CommandExecutionError('Missing function parameter')

    # move salt into the container
    thin_path = __utils__['thin.gen_thin'](
        __opts__['cachedir'],
        extra_mods=__salt__['config.option']("thin_extra_mods", ''),
        so_mods=__salt__['config.option']("thin_so_mods", '')
    )
    ret = copy_to(name, thin_path, os.path.join(
        thin_dest_path, os.path.basename(thin_path)))

    # untar archive
    untar_cmd = ["python", "-c", (
        "import tarfile; "
        "tarfile.open(\"{0}/{1}\").extractall(path=\"{0}\")"
    ).format(thin_dest_path, os.path.basename(thin_path))]
    ret = run_all(name, subprocess.list2cmdline(untar_cmd))
    if ret['retcode'] != 0:
        return {'result': False, 'comment': ret['stderr']}

    try:
        salt_argv = [
            'python{0}'.format(sys.version_info[0]),
            os.path.join(thin_dest_path, 'salt-call'),
            '--metadata',
            '--local',
            '--log-file', os.path.join(thin_dest_path, 'log'),
            '--cachedir', os.path.join(thin_dest_path, 'cache'),
            '--out', 'json',
            '-l', 'quiet',
            '--',
            function
        ] + list(args) + ['{0}={1}'.format(key, value) for (key, value) in kwargs.items() if not key.startswith('__')]

        ret = run_all(name, subprocess.list2cmdline(map(str, salt_argv)))
        # python not found
        if ret['retcode'] != 0:
            raise CommandExecutionError(ret['stderr'])

        # process "real" result in stdout
        try:
            data = __utils__['json.find_json'](ret['stdout'])
            local = data.get('local', data)
            if isinstance(local, dict):
                if 'retcode' in local:
                    __context__['retcode'] = local['retcode']
            return local.get('return', data)
        except ValueError:
            return {'result': False,
                    'comment': 'Can\'t parse container command output'}
    finally:
        # delete the thin dir so that it does not end in the image
        rm_thin_argv = ['rm', '-rf', thin_dest_path]
        run_all(name, subprocess.list2cmdline(rm_thin_argv))