From 3aede800c575989b7c7f2b18e2818b5b4fdf4fd2 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Mon, 27 Apr 2015 00:28:25 -0500 Subject: [PATCH] Fixing winrm connection for v2 --- v2/ansible/executor/connection_info.py | 2 +- v2/ansible/plugins/action/__init__.py | 22 +-- .../plugins/connections/paramiko_ssh.py | 7 +- v2/ansible/plugins/connections/ssh.py | 4 +- v2/ansible/plugins/connections/winrm.py | 137 +++++++++--------- v2/ansible/plugins/shell/powershell.py | 75 +++++----- v2/ansible/utils/display.py | 3 + 7 files changed, 125 insertions(+), 125 deletions(-) diff --git a/v2/ansible/executor/connection_info.py b/v2/ansible/executor/connection_info.py index cf5763ba81..05fd5e8784 100644 --- a/v2/ansible/executor/connection_info.py +++ b/v2/ansible/executor/connection_info.py @@ -48,7 +48,7 @@ class ConnectionInformation: self.remote_addr = None self.remote_user = None self.password = passwords.get('conn_pass','') - self.port = 22 + self.port = None self.private_key_file = C.DEFAULT_PRIVATE_KEY_FILE self.timeout = C.DEFAULT_TIMEOUT diff --git a/v2/ansible/plugins/action/__init__.py b/v2/ansible/plugins/action/__init__.py index c49ac8e6f0..aead235037 100644 --- a/v2/ansible/plugins/action/__init__.py +++ b/v2/ansible/plugins/action/__init__.py @@ -56,22 +56,12 @@ class ActionBase: def get_shell(self): - # FIXME: no more inject, get this from the host variables? - #default_shell = getattr(self._connection, 'default_shell', '') - #shell_type = inject.get('ansible_shell_type') - #if not shell_type: - # if default_shell: - # shell_type = default_shell - # else: - # shell_type = os.path.basename(C.DEFAULT_EXECUTABLE) - - shell_type = getattr(self._connection, 'default_shell', '') - if not shell_type: - shell_type = os.path.basename(C.DEFAULT_EXECUTABLE) - - shell_plugin = shell_loader.get(shell_type) - if shell_plugin is None: - shell_plugin = shell_loader.get('sh') + if hasattr(self._connection, '_shell'): + shell_plugin = getattr(self._connection, '_shell', '') + else: + shell_plugin = shell_loader.get(os.path.basename(C.DEFAULT_EXECUTABLE)) + if shell_plugin is None: + shell_plugin = shell_loader.get('sh') return shell_plugin diff --git a/v2/ansible/plugins/connections/paramiko_ssh.py b/v2/ansible/plugins/connections/paramiko_ssh.py index 256578a0d7..a2b961bd68 100644 --- a/v2/ansible/plugins/connections/paramiko_ssh.py +++ b/v2/ansible/plugins/connections/paramiko_ssh.py @@ -141,7 +141,8 @@ class Connection(ConnectionBase): if not HAVE_PARAMIKO: raise AnsibleError("paramiko is not installed") - self._display.vvv("ESTABLISH CONNECTION FOR USER: %s on PORT %s TO %s" % (self._connection_info.remote_user, self._connection_info.port, self._connection_info.remote_addr), host=self._connection_info.remote_addr) + port = self._connection_info.port or 22 + self._display.vvv("ESTABLISH CONNECTION FOR USER: %s on PORT %s TO %s" % (self._connection_info.remote_user, port, self._connection_info.remote_addr), host=self._connection_info.remote_addr) ssh = paramiko.SSHClient() @@ -170,7 +171,7 @@ class Connection(ConnectionBase): key_filename=key_filename, password=self._connection_info.password, timeout=self._connection_info.timeout, - port=self._connection_info.port + port=port, ) except Exception, e: msg = str(e) @@ -178,7 +179,7 @@ class Connection(ConnectionBase): raise AnsibleError("paramiko version issue, please upgrade paramiko on the machine running ansible") elif "Private key file is encrypted" in msg: msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u .' % ( - self._connection_info.remote_user, self._connection_info.remote_addr, self._connection_info.port, msg) + self._connection_info.remote_user, self._connection_info.remote_addr, port, msg) raise AnsibleConnectionFailure(msg) else: raise AnsibleConnectionFailure(msg) diff --git a/v2/ansible/plugins/connections/ssh.py b/v2/ansible/plugins/connections/ssh.py index de7e923da7..cc5b321d14 100644 --- a/v2/ansible/plugins/connections/ssh.py +++ b/v2/ansible/plugins/connections/ssh.py @@ -39,7 +39,7 @@ from ansible.plugins.connections import ConnectionBase class Connection(ConnectionBase): ''' ssh based connections ''' - def __init__(self, connection_info, *args, **kwargs): + def __init__(self, *args, **kwargs): # SSH connection specific init stuff self.HASHED_KEY_MAGIC = "|1|" self._has_pipelining = True @@ -50,7 +50,7 @@ class Connection(ConnectionBase): self._cp_dir = '/tmp' #fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN) - super(Connection, self).__init__(connection_info, *args, **kwargs) + super(Connection, self).__init__(*args, **kwargs) @property def transport(self): diff --git a/v2/ansible/plugins/connections/winrm.py b/v2/ansible/plugins/connections/winrm.py index b41a74c8e1..833358d58c 100644 --- a/v2/ansible/plugins/connections/winrm.py +++ b/v2/ansible/plugins/connections/winrm.py @@ -23,17 +23,13 @@ import re import shlex import traceback import urlparse -from ansible import errors -from ansible import utils -from ansible.callbacks import vvv, vvvv, verbose -from ansible.runner.shell_plugins import powershell try: from winrm import Response from winrm.exceptions import WinRMTransportError from winrm.protocol import Protocol except ImportError: - raise errors.AnsibleError("winrm is not installed") + raise AnsibleError("winrm is not installed") HAVE_KERBEROS = False try: @@ -42,10 +38,12 @@ try: except ImportError: pass -def vvvvv(msg, host=None): - verbose(msg, host=host, caplevel=4) +from ansible import constants as C +from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound +from ansible.plugins.connections import ConnectionBase +from ansible.plugins import shell_loader -class Connection(object): +class Connection(ConnectionBase): '''WinRM connections over HTTP/HTTPS.''' transport_schemes = { @@ -53,69 +51,79 @@ class Connection(object): 'https': [('kerberos', 'https'), ('plaintext', 'https')], } - def __init__(self, runner, host, port, user, password, *args, **kwargs): - self.runner = runner - self.host = host - self.port = port - self.user = user - self.password = password - self.has_pipelining = False - self.default_shell = 'powershell' - self.default_suffixes = ['.ps1', ''] - self.protocol = None - self.shell_id = None - self.delegate = None + def __init__(self, *args, **kwargs): - # Add runas support - #self.become_methods_supported=['runas'] + self.has_pipelining = False + self.default_suffixes = ['.ps1', ''] + self.protocol = None + self.shell_id = None + self.delegate = None + + self._shell = shell_loader.get('powershell') + + # TODO: Add runas support self.become_methods_supported=[] + super(Connection, self).__init__(*args, **kwargs) + + @property + def transport(self): + ''' used to identify this connection object from other classes ''' + return 'winrm' + def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' - port = self.port or 5986 - vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \ - (self.user, port, self.host), host=self.host) - netloc = '%s:%d' % (self.host, port) + port = self._connection_info.port or 5986 + self._display.vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \ + (self._connection_info.remote_user, port, self._connection_info.remote_addr), host=self._connection_info.remote_addr) + netloc = '%s:%d' % (self._connection_info.remote_addr, port) exc = None for transport, scheme in self.transport_schemes['http' if port == 5985 else 'https']: - if transport == 'kerberos' and (not HAVE_KERBEROS or not '@' in self.user): + if transport == 'kerberos' and (not HAVE_KERBEROS or not '@' in self._connection_info.remote_user): continue + if transport == 'kerberos': - realm = self.user.split('@', 1)[1].strip() or None + realm = self._connection_info.remote_user.split('@', 1)[1].strip() or None else: realm = None + endpoint = urlparse.urlunsplit((scheme, netloc, '/wsman', '', '')) - vvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), - host=self.host) - protocol = Protocol(endpoint, transport=transport, - username=self.user, password=self.password, - realm=realm) + + self._display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._connection_info.remote_addr) + protocol = Protocol( + endpoint, + transport=transport, + username=self._connection_info.remote_user, + password=self._connection_info.password, + realm=realm + ) + try: protocol.send_message('') return protocol except WinRMTransportError, exc: err_msg = str(exc) if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I): - raise errors.AnsibleError("the connection attempt timed out") + raise AnsibleError("the connection attempt timed out") m = re.search(r'Code\s+?(\d{3})', err_msg) if m: code = int(m.groups()[0]) if code == 401: - raise errors.AnsibleError("the username/password specified for this server was incorrect") + raise AnsibleError("the username/password specified for this server was incorrect") elif code == 411: return protocol - vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host) + self._display.vvvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self._connection_info.remote_addr) continue if exc: - raise errors.AnsibleError(str(exc)) + raise AnsibleError(str(exc)) def _winrm_exec(self, command, args=(), from_exec=False): if from_exec: - vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) + self._display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._connection_info.remote_addr) else: - vvvvv("WINRM EXEC %r %r" % (command, args), host=self.host) + self._display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._connection_info.remote_addr) if not self.protocol: self.protocol = self._winrm_connect() if not self.shell_id: @@ -125,49 +133,46 @@ class Connection(object): command_id = self.protocol.run_command(self.shell_id, command, args) response = Response(self.protocol.get_command_output(self.shell_id, command_id)) if from_exec: - vvvv('WINRM RESULT %r' % response, host=self.host) + self._display.vvvvv('WINRM RESULT %r' % response, host=self._connection_info.remote_addr) else: - vvvvv('WINRM RESULT %r' % response, host=self.host) - vvvvv('WINRM STDOUT %s' % response.std_out, host=self.host) - vvvvv('WINRM STDERR %s' % response.std_err, host=self.host) + self._display.vvvvvv('WINRM RESULT %r' % response, host=self._connection_info.remote_addr) + self._display.vvvvvv('WINRM STDOUT %s' % response.std_out, host=self._connection_info.remote_addr) + self._display.vvvvvv('WINRM STDERR %s' % response.std_err, host=self._connection_info.remote_addr) return response finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id) - def connect(self): + def _connect(self): if not self.protocol: self.protocol = self._winrm_connect() return self - def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable=None, in_data=None): - - if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported: - raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method) + def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None): cmd = cmd.encode('utf-8') cmd_parts = shlex.split(cmd, posix=False) if '-EncodedCommand' in cmd_parts: encoded_cmd = cmd_parts[cmd_parts.index('-EncodedCommand') + 1] decoded_cmd = base64.b64decode(encoded_cmd) - vvv("EXEC %s" % decoded_cmd, host=self.host) + self._display.vvv("EXEC %s" % decoded_cmd, host=self._connection_info.remote_addr) else: - vvv("EXEC %s" % cmd, host=self.host) + self._display.vvv("EXEC %s" % cmd, host=self._connection_info.remote_addr) # For script/raw support. if cmd_parts and cmd_parts[0].lower().endswith('.ps1'): - script = powershell._build_file_cmd(cmd_parts, quote_args=False) - cmd_parts = powershell._encode_script(script, as_list=True) + script = self._shell._build_file_cmd(cmd_parts, quote_args=False) + cmd_parts = self._shell._encode_script(script, as_list=True) try: result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True) except Exception, e: traceback.print_exc() - raise errors.AnsibleError("failed to exec cmd %s" % cmd) + raise AnsibleError("failed to exec cmd %s" % cmd) return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8')) def put_file(self, in_path, out_path): - vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) + self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._connection_info.remote_addr) if not os.path.exists(in_path): - raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) + raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) with open(in_path) as in_file: in_size = os.path.getsize(in_path) script_template = ''' @@ -179,8 +184,8 @@ class Connection(object): [void]$s.Close(); ''' # Determine max size of data we can pass per command. - script = script_template % (powershell._escape(out_path), in_size, '', in_size) - cmd = powershell._encode_script(script) + script = script_template % (self._shell._escape(out_path), in_size, '', in_size) + cmd = self._shell._encode_script(script) # Encode script with no data, subtract its length from 8190 (max # windows command length), divide by 2.67 (UTF16LE base64 command # encoding), then by 1.35 again (data base64 encoding). @@ -192,19 +197,19 @@ class Connection(object): if out_data.lower().startswith('#!powershell') and not out_path.lower().endswith('.ps1'): out_path = out_path + '.ps1' b64_data = base64.b64encode(out_data) - script = script_template % (powershell._escape(out_path), offset, b64_data, in_size) - vvvv("WINRM PUT %s to %s (offset=%d size=%d)" % (in_path, out_path, offset, len(out_data)), host=self.host) - cmd_parts = powershell._encode_script(script, as_list=True) + script = script_template % (self._shell._escape(out_path), offset, b64_data, in_size) + self._display.vvvvv("WINRM PUT %s to %s (offset=%d size=%d)" % (in_path, out_path, offset, len(out_data)), host=self._connection_info.remote_addr) + cmd_parts = self._shell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: raise IOError(result.std_err.encode('utf-8')) except Exception: traceback.print_exc() - raise errors.AnsibleError("failed to transfer file to %s" % out_path) + raise AnsibleError("failed to transfer file to %s" % out_path) def fetch_file(self, in_path, out_path): out_path = out_path.replace('\\', '/') - vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) + self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._connection_info.remote_addr) buffer_size = 2**19 # 0.5MB chunks if not os.path.exists(os.path.dirname(out_path)): os.makedirs(os.path.dirname(out_path)) @@ -233,9 +238,9 @@ class Connection(object): Write-Error "%(path)s does not exist"; Exit 1; } - ''' % dict(buffer_size=buffer_size, path=powershell._escape(in_path), offset=offset) - vvvv("WINRM FETCH %s to %s (offset=%d)" % (in_path, out_path, offset), host=self.host) - cmd_parts = powershell._encode_script(script, as_list=True) + ''' % dict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset) + self._display.vvvvv("WINRM FETCH %s to %s (offset=%d)" % (in_path, out_path, offset), host=self._connection_info.remote_addr) + cmd_parts = self._shell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: raise IOError(result.std_err.encode('utf-8')) @@ -259,7 +264,7 @@ class Connection(object): offset += len(data) except Exception: traceback.print_exc() - raise errors.AnsibleError("failed to transfer file to %s" % out_path) + raise AnsibleError("failed to transfer file to %s" % out_path) finally: if out_file: out_file.close() diff --git a/v2/ansible/plugins/shell/powershell.py b/v2/ansible/plugins/shell/powershell.py index 9f3825c3b0..e4331e46c6 100644 --- a/v2/ansible/plugins/shell/powershell.py +++ b/v2/ansible/plugins/shell/powershell.py @@ -32,33 +32,6 @@ _powershell_version = os.environ.get('POWERSHELL_VERSION', None) if _powershell_version: _common_args = ['PowerShell', '-Version', _powershell_version] + _common_args[1:] -def _escape(value, include_vars=False): - '''Return value escaped for use in PowerShell command.''' - # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences - # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python - subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), - ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), - ('\'', '`\''), ('`', '``'), ('\x00', '`0')] - if include_vars: - subs.append(('$', '`$')) - pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) - substs = [s for p, s in subs] - replace = lambda m: substs[m.lastindex - 1] - return re.sub(pattern, replace, value) - -def _encode_script(script, as_list=False): - '''Convert a PowerShell script to a single base64-encoded command.''' - script = '\n'.join([x.strip() for x in script.splitlines() if x.strip()]) - encoded_script = base64.b64encode(script.encode('utf-16-le')) - cmd_parts = _common_args + ['-EncodedCommand', encoded_script] - if as_list: - return cmd_parts - return ' '.join(cmd_parts) - -def _build_file_cmd(cmd_parts): - '''Build command line to run a file, given list of file name plus args.''' - return ' '.join(_common_args + ['-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts]) - class ShellModule(object): def env_prefix(self, **kwargs): @@ -75,19 +48,19 @@ class ShellModule(object): return '' def remove(self, path, recurse=False): - path = _escape(path) + path = self._escape(path) if recurse: - return _encode_script('''Remove-Item "%s" -Force -Recurse;''' % path) + return self._encode_script('''Remove-Item "%s" -Force -Recurse;''' % path) else: - return _encode_script('''Remove-Item "%s" -Force;''' % path) + return self._encode_script('''Remove-Item "%s" -Force;''' % path) def mkdtemp(self, basefile, system=False, mode=None): - basefile = _escape(basefile) + basefile = self._escape(basefile) # FIXME: Support system temp path! - return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile) + return self._encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile) def md5(self, path): - path = _escape(path) + path = self._escape(path) script = ''' If (Test-Path -PathType Leaf "%(path)s") { @@ -105,15 +78,43 @@ class ShellModule(object): Write-Host "1"; } ''' % dict(path=path) - return _encode_script(script) + return self._encode_script(script) def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): cmd = cmd.encode('utf-8') cmd_parts = shlex.split(cmd, posix=False) if not cmd_parts[0].lower().endswith('.ps1'): cmd_parts[0] = '%s.ps1' % cmd_parts[0] - script = _build_file_cmd(cmd_parts) + script = self._build_file_cmd(cmd_parts) if rm_tmp: - rm_tmp = _escape(rm_tmp) + rm_tmp = self._escape(rm_tmp) script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, rm_tmp) - return _encode_script(script) + return self._encode_script(script) + + def _escape(self, value, include_vars=False): + '''Return value escaped for use in PowerShell command.''' + # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences + # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python + subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), + ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), + ('\'', '`\''), ('`', '``'), ('\x00', '`0')] + if include_vars: + subs.append(('$', '`$')) + pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) + substs = [s for p, s in subs] + replace = lambda m: substs[m.lastindex - 1] + return re.sub(pattern, replace, value) + + def _encode_script(self, script, as_list=False): + '''Convert a PowerShell script to a single base64-encoded command.''' + script = '\n'.join([x.strip() for x in script.splitlines() if x.strip()]) + encoded_script = base64.b64encode(script.encode('utf-16-le')) + cmd_parts = _common_args + ['-EncodedCommand', encoded_script] + if as_list: + return cmd_parts + return ' '.join(cmd_parts) + + def _build_file_cmd(self, cmd_parts): + '''Build command line to run a file, given list of file name plus args.''' + return ' '.join(_common_args + ['-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts]) + diff --git a/v2/ansible/utils/display.py b/v2/ansible/utils/display.py index 63cc9e4c6d..0881627c4b 100644 --- a/v2/ansible/utils/display.py +++ b/v2/ansible/utils/display.py @@ -73,6 +73,9 @@ class Display: def vvvvv(self, msg, host=None): return self.verbose(msg, host=host, caplevel=4) + def vvvvvv(self, msg, host=None): + return self.verbose(msg, host=host, caplevel=5) + def verbose(self, msg, host=None, caplevel=2): # FIXME: this needs to be implemented #msg = utils.sanitize_output(msg)