Add default license boilerplate, refactor common powershell code, fixes for raw/script modules.

This commit is contained in:
Chris Church 2014-06-17 16:12:49 -05:00 committed by Matt Martz
parent 35a7c93c76
commit f7af29680b
7 changed files with 113 additions and 109 deletions

View file

@ -106,7 +106,6 @@ class ActionModule(object):
# transfer the file to a remote tmp location # transfer the file to a remote tmp location
source = source.replace('\x00', '') # why does this happen here? source = source.replace('\x00', '') # why does this happen here?
args = args.replace('\x00', '') # why does this happen here? args = args.replace('\x00', '') # why does this happen here?
#tmp_src = os.path.join(tmp, os.path.basename(source)) # CCTODO
tmp_src = conn.shell.join_path(tmp, os.path.basename(source)) tmp_src = conn.shell.join_path(tmp, os.path.basename(source))
tmp_src = tmp_src.replace('\x00', '') tmp_src = tmp_src.replace('\x00', '')

View file

@ -20,10 +20,6 @@
from ansible import utils from ansible import utils
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
import ansible.constants as C
import os
import os.path
class Connector(object): class Connector(object):
''' Handles abstract connections to remote hosts ''' ''' Handles abstract connections to remote hosts '''

View file

@ -1,6 +1,6 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com> # (c) 2014, Chris Church <chris@ninemoreminutes.com>
# #
# This file is (not yet) part of Ansible. # This file is part of Ansible.
# #
# Ansible is free software: you can redistribute it and/or modify # Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -28,6 +28,7 @@ import urlparse
from ansible import errors from ansible import errors
from ansible import utils from ansible import utils
from ansible.callbacks import vvv, vvvv from ansible.callbacks import vvv, vvvv
from ansible.runner.shell_plugins import powershell
try: try:
from winrm import Response from winrm import Response
@ -36,12 +37,6 @@ try:
except ImportError: except ImportError:
raise errors.AnsibleError("winrm is not installed") raise errors.AnsibleError("winrm is not installed")
# When running with unmodified Ansible (1.6.x), load local hacks.
try:
_winrm_hacks = imp.load_source('_winrm_hacks', os.path.join(os.path.dirname(__file__), '_winrm_hacks.py'))
except (ImportError, IOError):
_winrm_hacks = None
_winrm_cache = { _winrm_cache = {
# 'user:pwhash@host:port': <protocol instance> # 'user:pwhash@host:port': <protocol instance>
} }
@ -61,17 +56,12 @@ class Connection(object):
self.protocol = None self.protocol = None
self.shell_id = None self.shell_id = None
self.delegate = None self.delegate = None
if _winrm_hacks:
_winrm_hacks.patch_module_finder(self)
def _winrm_connect(self): def _winrm_connect(self):
''' '''
Establish a WinRM connection over HTTP/HTTPS. Establish a WinRM connection over HTTP/HTTPS.
''' '''
if _winrm_hacks: port = self.port or 5986
port = _winrm_hacks.get_port(self)
else:
port = self.port or 5986
vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \ vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \
(self.user, port, self.host), host=self.host) (self.user, port, self.host), host=self.host)
netloc = '%s:%d' % (self.host, port) netloc = '%s:%d' % (self.host, port)
@ -105,35 +95,9 @@ class Connection(object):
return protocol return protocol
vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host) vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host)
continue continue
# FIXME: Cache connection!!!
if exc: if exc:
raise exc raise exc
def _winrm_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 _winrm_get_script_cmd(self, script):
'''
Convert a PowerShell script to a single base64-encoded command.
'''
vvvv('WINRM SCRIPT: %s' % script, host=self.host)
encoded_script = base64.b64encode(script.encode('utf-16-le'))
return ['PowerShell', '-NoProfile', '-NonInteractive',
'-EncodedCommand', encoded_script]
def _winrm_exec(self, command, args): def _winrm_exec(self, command, args):
vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) vvvv("WINRM EXEC %r %r" % (command, args), host=self.host)
if not self.protocol: if not self.protocol:
@ -152,27 +116,19 @@ class Connection(object):
self.protocol.cleanup_command(self.shell_id, command_id) self.protocol.cleanup_command(self.shell_id, command_id)
def connect(self): def connect(self):
if not _winrm_hacks: if not self.protocol:
if not self.protocol: self.protocol = self._winrm_connect()
self.protocol = self._winrm_connect()
# When using hacks, connect lazily on first command, to allow for
# runner to set self.delegate, needed if actual host vs. host name are
# different.
return self return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None): def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None):
cmd = cmd.encode('utf-8') cmd = cmd.encode('utf-8')
vvv("EXEC %s" % cmd, host=self.host) vvv("EXEC %s" % cmd, host=self.host)
cmd_parts = shlex.split(cmd, posix=False) cmd_parts = shlex.split(cmd, posix=False)
vvvv("WINRM PARTS %r" % cmd_parts, host=self.host) vvvv("WINRM PARTS %r" % cmd_parts, host=self.host)
# For script/raw support. # For script/raw support.
if len(cmd_parts) == 1 and cmd_parts[0].lower().endswith('.ps1'): if cmd_parts and cmd_parts[0].lower().endswith('.ps1'):
cmd_parts = ['PowerShell', '-ExecutionPolicy', 'Unrestricted', '-File', cmd_parts[0]] script = 'PowerShell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File ' + ' '.join(['"%s"' % x for x in cmd_parts])
if _winrm_hacks: cmd_parts = powershell._encode_script(script, as_list=True)
cmd_parts = _winrm_hacks.filter_cmd_parts(self, cmd_parts)
if not cmd_parts:
vvv('WINRM NOOP')
return (0, '', '', '')
try: try:
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
except Exception, e: except Exception, e:
@ -181,8 +137,6 @@ class Connection(object):
return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8')) return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8'))
def put_file(self, in_path, out_path): def put_file(self, in_path, out_path):
if _winrm_hacks:
out_path = _winrm_hacks.fix_slashes(out_path)
vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
if not os.path.exists(in_path): if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
@ -205,19 +159,16 @@ class Connection(object):
$stream.Write($buffer, 0, $buffer.length) | Out-Null; $stream.Write($buffer, 0, $buffer.length) | Out-Null;
$stream.SetLength(%d) | Out-Null; $stream.SetLength(%d) | Out-Null;
$stream.Close() | Out-Null; $stream.Close() | Out-Null;
''' % (buffer_size, self._winrm_escape(out_path), offset, b64_data, in_size) ''' % (buffer_size, powershell._escape(out_path), offset, b64_data, in_size)
cmd_parts = self._winrm_get_script_cmd(script) cmd_parts = powershell._encode_script(script, as_list=True)
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
if result.status_code != 0: if result.status_code != 0:
raise RuntimeError(result.std_err.encode('utf-8')) raise RuntimeError(result.std_err.encode('utf-8'))
script = u''
except Exception: # IOError? except Exception: # IOError?
traceback.print_exc() traceback.print_exc()
raise errors.AnsibleError("failed to transfer file to %s" % out_path) raise errors.AnsibleError("failed to transfer file to %s" % out_path)
def fetch_file(self, in_path, out_path): def fetch_file(self, in_path, out_path):
if _winrm_hacks:
in_path = _winrm_hacks.fix_slashes(in_path)
out_path = out_path.replace('\\', '/') out_path = out_path.replace('\\', '/')
vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
buffer_size = 2**20 # 1MB chunks buffer_size = 2**20 # 1MB chunks
@ -236,8 +187,8 @@ class Connection(object):
$bytes = $buffer[0..($bytesRead-1)]; $bytes = $buffer[0..($bytesRead-1)];
[System.Convert]::ToBase64String($bytes); [System.Convert]::ToBase64String($bytes);
$stream.Close() | Out-Null; $stream.Close() | Out-Null;
''' % (buffer_size, self._winrm_escape(in_path), offset) ''' % (buffer_size, powershell._escape(in_path), offset)
cmd_parts = self._winrm_get_script_cmd(script) cmd_parts = powershell._encode_script(script, as_list=True)
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
data = base64.b64decode(result.std_out.strip()) data = base64.b64decode(result.std_out.strip())
out_file.write(data) out_file.write(data)

View file

@ -1,3 +1,20 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible.runner.shell_plugins.sh import ShellModule as ShModule from ansible.runner.shell_plugins.sh import ShellModule as ShModule
class ShellModule(ShModule): class ShellModule(ShModule):

View file

@ -1,3 +1,20 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible.runner.shell_plugins.sh import ShellModule as ShModule from ansible.runner.shell_plugins.sh import ShellModule as ShModule
class ShellModule(ShModule): class ShellModule(ShModule):

View file

@ -1,3 +1,20 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import base64 import base64
import os import os
import re import re
@ -5,35 +22,30 @@ import random
import shlex import shlex
import time import time
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.'''
encoded_script = base64.b64encode(script.encode('utf-16-le'))
cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-EncodedCommand', encoded_script]
if as_list:
return cmd_parts
return ' '.join(cmd_parts)
class ShellModule(object): class ShellModule(object):
def __init__(self):
pass
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 _get_script_cmd(self, script):
'''
Convert a PowerShell script to a single base64-encoded command.
'''
encoded_script = base64.b64encode(script.encode('utf-16-le'))
return ' '.join(['PowerShell', '-NoProfile', '-NonInteractive',
'-EncodedCommand', encoded_script])
def env_prefix(self, **kwargs): def env_prefix(self, **kwargs):
return '' return ''
@ -44,22 +56,20 @@ class ShellModule(object):
return '' return ''
def remove(self, path, recurse=False): def remove(self, path, recurse=False):
path = self._escape(path) path = _escape(path)
if recurse: if recurse:
return self._get_script_cmd('''Remove-Item "%s" -Force -Recurse;''' % path) return _encode_script('''Remove-Item "%s" -Force -Recurse;''' % path)
else: else:
return self._get_script_cmd('''Remove-Item "%s" -Force;''' % path) return _encode_script('''Remove-Item "%s" -Force;''' % path)
def mkdtemp(self, basefile=None, system=False, mode=None): def mkdtemp(self, basefile, system=False, mode=None):
if not basefile: basefile = _escape(basefile)
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
basefile = self._escape(basefile)
# FIXME: Support system temp path! # FIXME: Support system temp path!
return self._get_script_cmd('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName;''' % basefile) return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName;''' % basefile)
def md5(self, path): def md5(self, path):
path = self._escape(path) path = _escape(path)
return self._get_script_cmd('''(Get-FileHash -Path "%s" -Algorithm MD5).Hash.ToLower();''' % path) return _encode_script('''(Get-FileHash -Path "%s" -Algorithm MD5).Hash.ToLower();''' % path)
def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): def build_module_command(self, env_string, shebang, cmd, rm_tmp=None):
cmd_parts = shlex.split(cmd, posix=False) cmd_parts = shlex.split(cmd, posix=False)
@ -68,5 +78,6 @@ class ShellModule(object):
cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts] cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts]
script = ' '.join(cmd_parts) script = ' '.join(cmd_parts)
if rm_tmp: if rm_tmp:
script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, self._escape(rm_tmp)) rm_tmp = _escape(rm_tmp)
return self._get_script_cmd(script) script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, rm_tmp)
return _encode_script(script)

View file

@ -1,3 +1,19 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os import os
import pipes import pipes
@ -5,9 +21,6 @@ import ansible.constants as C
class ShellModule(object): class ShellModule(object):
def __init__(self):
pass
def env_prefix(self, **kwargs): def env_prefix(self, **kwargs):
'''Build command prefix with environment variables.''' '''Build command prefix with environment variables.'''
env = dict( env = dict(