# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com>
# Based on func.py
# (c) 2014, Michael Scherer <misc@zarb.org>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = '''
    author: Michael Scherer (@mscherer) <misc@zarb.org>
    name: saltstack
    short_description: Allow ansible to piggyback on salt minions
    description:
        - This allows you to use existing Saltstack infrastructure to connect to targets.
'''

import os
import base64

from ansible import errors
from ansible.plugins.connection import ConnectionBase

HAVE_SALTSTACK = False
try:
    import salt.client as sc
    HAVE_SALTSTACK = True
except ImportError:
    pass


class Connection(ConnectionBase):
    """ Salt-based connections """

    has_pipelining = False
    # while the name of the product is salt, naming that module salt cause
    # trouble with module import
    transport = 'community.general.saltstack'

    def __init__(self, play_context, new_stdin, *args, **kwargs):
        super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
        self.host = self._play_context.remote_addr

    def _connect(self):
        if not HAVE_SALTSTACK:
            raise errors.AnsibleError("saltstack is not installed")

        self.client = sc.LocalClient()
        self._connected = True
        return self

    def exec_command(self, cmd, sudoable=False, in_data=None):
        """ run a command on the remote minion """
        super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)

        if in_data:
            raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")

        self._display.vvv("EXEC %s" % cmd, host=self.host)
        # need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077
        res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', 'true;' + cmd])
        if self.host not in res:
            raise errors.AnsibleError("Minion %s didn't answer, check if salt-minion is running and the name is correct" % self.host)

        p = res[self.host]
        return p['retcode'], p['stdout'], p['stderr']

    @staticmethod
    def _normalize_path(path, prefix):
        if not path.startswith(os.path.sep):
            path = os.path.join(os.path.sep, path)
        normpath = os.path.normpath(path)
        return os.path.join(prefix, normpath[1:])

    def put_file(self, in_path, out_path):
        """ transfer a file from local to remote """

        super(Connection, self).put_file(in_path, out_path)

        out_path = self._normalize_path(out_path, '/')
        self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
        with open(in_path, 'rb') as in_fh:
            content = in_fh.read()
        self.client.cmd(self.host, 'hashutil.base64_decodefile', [base64.b64encode(content), out_path])

    # TODO test it
    def fetch_file(self, in_path, out_path):
        """ fetch a file from remote to local """

        super(Connection, self).fetch_file(in_path, out_path)

        in_path = self._normalize_path(in_path, '/')
        self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
        content = self.client.cmd(self.host, 'cp.get_file_str', [in_path])[self.host]
        open(out_path, 'wb').write(content)

    def close(self):
        """ terminate the connection; nothing to do here """
        pass

[(49, 'salt.client.LocalClient', 'sc.LocalClient', True, 'import salt.client as sc\n'), (73, 'os.path.normpath', 'os.path.normpath', False, 'import os\n'), (74, 'os.path.join', 'os.path.join', False, 'import os\n'), (47, 'ansible.errors.AnsibleError', 'errors.AnsibleError', False, 'from ansible import errors\n'), (58, 'ansible.errors.AnsibleError', 'errors.AnsibleError', False, 'from ansible import errors\n'), (64, 'ansible.errors.AnsibleError', 'errors.AnsibleError', False, 'from ansible import errors\n'), (72, 'os.path.join', 'os.path.join', False, 'import os\n'), (85, 'base64.b64encode', 'base64.b64encode', False, 'import base64\n')]