1# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> 2# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com> 3# Based on func.py 4# (c) 2014, Michael Scherer <misc@zarb.org> 5# (c) 2017 Ansible Project 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import (absolute_import, division, print_function) 9__metaclass__ = type 10 11DOCUMENTATION = """ 12 author: Michael Scherer (@mscherer) <misc@zarb.org> 13 connection: saltstack 14 short_description: Allow ansible to piggyback on salt minions 15 description: 16 - This allows you to use existing Saltstack infrastructure to connect to targets. 17 version_added: "2.2" 18""" 19 20import re 21import os 22import pty 23import subprocess 24 25from ansible.module_utils._text import to_bytes, to_text 26from ansible.module_utils.six.moves import cPickle 27 28HAVE_SALTSTACK = False 29try: 30 import salt.client as sc 31 HAVE_SALTSTACK = True 32except ImportError: 33 pass 34 35import os 36from ansible import errors 37from ansible.plugins.connection import ConnectionBase 38 39 40class Connection(ConnectionBase): 41 ''' Salt-based connections ''' 42 43 has_pipelining = False 44 # while the name of the product is salt, naming that module salt cause 45 # trouble with module import 46 transport = 'saltstack' 47 48 def __init__(self, play_context, new_stdin, *args, **kwargs): 49 super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) 50 self.host = self._play_context.remote_addr 51 52 def _connect(self): 53 if not HAVE_SALTSTACK: 54 raise errors.AnsibleError("saltstack is not installed") 55 56 self.client = sc.LocalClient() 57 self._connected = True 58 return self 59 60 def exec_command(self, cmd, sudoable=False, in_data=None): 61 ''' run a command on the remote minion ''' 62 super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) 63 64 if in_data: 65 raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") 66 67 self._display.vvv("EXEC %s" % (cmd), host=self.host) 68 # need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077 69 res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', 'true;' + cmd]) 70 if self.host not in res: 71 raise errors.AnsibleError("Minion %s didn't answer, check if salt-minion is running and the name is correct" % self.host) 72 73 p = res[self.host] 74 return (p['retcode'], p['stdout'], p['stderr']) 75 76 def _normalize_path(self, path, prefix): 77 if not path.startswith(os.path.sep): 78 path = os.path.join(os.path.sep, path) 79 normpath = os.path.normpath(path) 80 return os.path.join(prefix, normpath[1:]) 81 82 def put_file(self, in_path, out_path): 83 ''' transfer a file from local to remote ''' 84 85 super(Connection, self).put_file(in_path, out_path) 86 87 out_path = self._normalize_path(out_path, '/') 88 self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) 89 with open(in_path) as in_fh: 90 content = in_fh.read() 91 self.client.cmd(self.host, 'file.write', [out_path, content]) 92 93 # TODO test it 94 def fetch_file(self, in_path, out_path): 95 ''' fetch a file from remote to local ''' 96 97 super(Connection, self).fetch_file(in_path, out_path) 98 99 in_path = self._normalize_path(in_path, '/') 100 self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) 101 content = self.client.cmd(self.host, 'cp.get_file_str', [in_path])[self.host] 102 open(out_path, 'wb').write(content) 103 104 def close(self): 105 ''' terminate the connection; nothing to do here ''' 106 pass 107