Moving more action plugins over and fixing some bugs with role loading

This commit is contained in:
James Cammarata 2015-01-02 07:51:15 -06:00
commit 065733ad93
30 changed files with 1332 additions and 59 deletions

View file

@ -23,6 +23,7 @@ import StringIO
import json
import os
import random
import sys # FIXME: probably not needed
import tempfile
import time
@ -249,8 +250,8 @@ class ActionBase:
data = self._low_level_execute_command(cmd, tmp, sudoable=True)
# FIXME: implement this function?
#data2 = utils.last_non_blank_line(data['stdout'])
data2 = data['stdout'].strip().splitlines()[-1]
try:
data2 = data['stdout'].strip().splitlines()[-1]
if data2 == '':
# this may happen if the connection to the remote server
# failed, so just return "INVALIDCHECKSUM" to avoid errors
@ -258,6 +259,8 @@ class ActionBase:
else:
return data2.split()[0]
except IndexError:
# FIXME: this should probably not print to sys.stderr, but should instead
# fail in a more normal way?
sys.stderr.write("warning: Calculating checksum failed unusually, please report this to the list so it can be fixed\n")
sys.stderr.write("command: %s\n" % cmd)
sys.stderr.write("----\n")
@ -331,7 +334,7 @@ class ActionBase:
# a remote tmp path may be necessary and not already created
remote_module_path = None
if self._late_needs_tmp_path(tmp, module_style):
if not tmp and self._late_needs_tmp_path(tmp, module_style):
tmp = self._make_tmp_path()
remote_module_path = self._shell.join_path(tmp, module_name)
@ -384,9 +387,12 @@ class ActionBase:
# FIXME: in error situations, the stdout may not contain valid data, so we
# should check for bad rc codes better to catch this here
data = json.loads(self._filter_leading_non_json_lines(res['stdout']))
if 'parsed' in data and data['parsed'] == False:
data['msg'] += res['stderr']
if 'stdout' in res and res['stdout'].strip():
data = json.loads(self._filter_leading_non_json_lines(res['stdout']))
if 'parsed' in data and data['parsed'] == False:
data['msg'] += res['stderr']
else:
data = dict()
debug("done with _execute_module (%s, %s)" % (module_name, module_args))
return data

View file

@ -0,0 +1,62 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
# Copyright 2012, Seth Vidal <skvidal@fedoraproject.org>
#
# 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/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
''' Create inventory hosts and groups in the memory inventory'''
### We need to be able to modify the inventory
BYPASS_HOST_LOOP = True
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()):
# FIXME: is this necessary in v2?
#if self.runner.noop_on_check(inject):
# return ReturnData(conn=conn, comm_ok=True, result=dict(skipped=True, msg='check mode not supported for this module'))
# Parse out any hostname:port patterns
new_name = self._task.args.get('name', self._task.args.get('hostname', None))
#vv("creating host via 'add_host': hostname=%s" % new_name)
if ":" in new_name:
new_name, new_port = new_name.split(":")
self._task.args['ansible_ssh_port'] = new_port
groups = self._task.args.get('groupname', self._task.args.get('groups', self._task.args.get('group', '')))
# add it to the group if that was specified
new_groups = []
if groups:
for group_name in groups.split(","):
if group_name not in new_groups:
new_groups.append(group_name.strip())
# Add any variables to the new_host
host_vars = dict()
for k in self._task.args.keys():
if not k in [ 'name', 'hostname', 'groupname', 'groups' ]:
host_vars[k] = self._task.args[k]
return dict(changed=True, add_host=dict(host_name=new_name, groups=new_groups, host_vars=host_vars))

View file

@ -25,17 +25,24 @@ class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()):
# note: the fail module does not need to pay attention to check mode
# it always runs.
if not 'that' in self._task.args:
raise AnsibleError('conditional required in "that" string')
msg = None
if 'msg' in self._task.args:
msg = self._task.args['msg']
if not 'that' in self._task.args:
raise AnsibleError('conditional required in "that" string')
# make sure the 'that' items are a list
thats = self._task.args['that']
if not isinstance(thats, list):
thats = [ thats ]
for that in self._task.args['that']:
# Now we iterate over the that items, temporarily assigning them
# to the task's when value so we can evaluate the conditional using
# the built in evaluate function. The when has already been evaluated
# by this point, and is not used again, so we don't care about mangling
# that value now
for that in thats:
self._task.when = [ that ]
test_result = self._task.evaluate_conditional(all_vars=task_vars)
if not test_result:

View file

@ -0,0 +1,68 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.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 json
import random
from ansible import constants as C
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()):
''' transfer the given module name, plus the async module, then run it '''
# FIXME: noop stuff needs to be sorted ut
#if self.runner.noop_on_check(inject):
# return ReturnData(conn=conn, comm_ok=True, result=dict(skipped=True, msg='check mode not supported for this module'))
if not tmp:
tmp = self._make_tmp_path()
module_name = self._task.action
async_module_path = self._shell.join_path(tmp, 'async_wrapper')
remote_module_path = self._shell.join_path(tmp, module_name)
env_string = self._compute_environment_string()
# configure, upload, and chmod the target module
(module_style, shebang, module_data) = self._configure_module(module_name=module_name, module_args=self._task.args)
self._transfer_data(remote_module_path, module_data)
self._remote_chmod(tmp, 'a+rx', remote_module_path)
# configure, upload, and chmod the async_wrapper module
(async_module_style, shebang, async_module_data) = self._configure_module(module_name='async_wrapper', module_args=dict())
self._transfer_data(async_module_path, async_module_data)
self._remote_chmod(tmp, 'a+rx', async_module_path)
argsfile = self._transfer_data(self._shell.join_path(tmp, 'arguments'), json.dumps(self._task.args))
async_limit = self._task.async
async_jid = str(random.randint(0, 999999999999))
async_cmd = " ".join([str(x) for x in [async_module_path, async_jid, async_limit, remote_module_path, argsfile]])
result = self._low_level_execute_command(cmd=async_cmd, tmp=None)
# clean up after
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES:
self._remove_tmp_path(tmp)
result['changed'] = True
return result

View file

@ -87,8 +87,8 @@ class ActionModule(ActionBase):
content_tempfile = self._create_content_tempfile(content)
source = content_tempfile
except Exception, err:
result = dict(failed=True, msg="could not write content temp file: %s" % err)
return ReturnData(conn=conn, result=result)
return dict(failed=True, msg="could not write content temp file: %s" % err)
###############################################################################################
# FIXME: first_available_file needs to be reworked?
###############################################################################################
@ -263,7 +263,7 @@ class ActionModule(ActionBase):
#if self.runner.no_log:
# new_module_args['NO_LOG'] = True
module_return = self._execute_module(module_name='copy', module_args=new_module_args, tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_return = self._execute_module(module_name='copy', module_args=new_module_args, delete_remote_tmp=delete_remote_tmp)
module_executed = True
else:
@ -292,7 +292,7 @@ class ActionModule(ActionBase):
# new_module_args['NO_LOG'] = True
# Execute the file module.
module_return = self._execute_module(module_name='file', module_args=new_module_args, tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_return = self._execute_module(module_name='file', module_args=new_module_args, delete_remote_tmp=delete_remote_tmp)
module_executed = True
if not module_return.get('checksum'):

View file

@ -0,0 +1,33 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
# (c) 2012, Dag Wieers <dag@wieers.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.plugins.action import ActionBase
class ActionModule(ActionBase):
''' Fail with custom message '''
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()):
msg = 'Failed as requested from task'
if self._task.args and 'msg' in self._task.args:
msg = self._task.args.get('msg')
return dict(failed=True, msg=msg)

View file

@ -0,0 +1,152 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.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 pwd
import random
import traceback
import tempfile
import base64
from ansible import constants as C
from ansible.errors import *
from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
from ansible.utils.hashing import checksum, checksum_s, md5, secure_hash
class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=dict()):
''' handler for fetch operations '''
# FIXME: is this even required anymore?
#if self.runner.noop_on_check(inject):
# return ReturnData(conn=conn, comm_ok=True, result=dict(skipped=True, msg='check mode not (yet) supported for this module'))
source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
flat = boolean(self._task.args.get('flat'))
fail_on_missing = boolean(self._task.args.get('fail_on_missing'))
validate_checksum = boolean(self._task.args.get('validate_checksum', self._task.args.get('validate_md5')))
if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args:
return dict(failed=True, msg="validate_checksum and validate_md5 cannot both be specified")
if source is None or dest is None:
return dict(failed=True, msg="src and dest are required")
source = self._shell.join_path(source)
source = self._remote_expand_user(source, tmp)
# calculate checksum for the remote file
remote_checksum = self._remote_checksum(tmp, source)
# use slurp if sudo and permissions are lacking
remote_data = None
if remote_checksum in ('1', '2') or self._connection_info.sudo:
slurpres = self._execute_module(module_name='slurp', module_args=dict(src=source), tmp=tmp)
if slurpres.get('rc') == 0:
if slurpres['encoding'] == 'base64':
remote_data = base64.b64decode(slurpres['content'])
if remote_data is not None:
remote_checksum = checksum_s(remote_data)
# the source path may have been expanded on the
# target system, so we compare it here and use the
# expanded version if it's different
remote_source = slurpres.get('source')
if remote_source and remote_source != source:
source = remote_source
else:
# FIXME: should raise an error here? the old code did nothing
pass
# calculate the destination name
if os.path.sep not in self._shell.join_path('a', ''):
source_local = source.replace('\\', '/')
else:
source_local = source
dest = os.path.expanduser(dest)
if flat:
if dest.endswith("/"):
# if the path ends with "/", we'll use the source filename as the
# destination filename
base = os.path.basename(source_local)
dest = os.path.join(dest, base)
if not dest.startswith("/"):
# if dest does not start with "/", we'll assume a relative path
dest = self._loader.path_dwim(dest)
else:
# files are saved in dest dir, with a subdir for each host, then the filename
dest = "%s/%s/%s" % (self._loader.path_dwim(dest), self._connection._host, source_local)
dest = dest.replace("//","/")
if remote_checksum in ('0', '1', '2', '3', '4'):
# these don't fail because you may want to transfer a log file that possibly MAY exist
# but keep going to fetch other log files
if remote_checksum == '0':
result = dict(msg="unable to calculate the checksum of the remote file", file=source, changed=False)
elif remote_checksum == '1':
if fail_on_missing:
result = dict(failed=True, msg="the remote file does not exist", file=source)
else:
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
elif remote_checksum == '2':
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
elif remote_checksum == '3':
result = dict(msg="remote file is a directory, fetch cannot work on directories", file=source, changed=False)
elif remote_checksum == '4':
result = dict(msg="python isn't present on the system. Unable to compute checksum", file=source, changed=False)
return result
# calculate checksum for the local file
local_checksum = checksum(dest)
if remote_checksum != local_checksum:
# create the containing directories, if needed
if not os.path.isdir(os.path.dirname(dest)):
os.makedirs(os.path.dirname(dest))
# fetch the file and check for changes
if remote_data is None:
self._connection.fetch_file(source, dest)
else:
f = open(dest, 'w')
f.write(remote_data)
f.close()
new_checksum = secure_hash(dest)
# For backwards compatibility. We'll return None on FIPS enabled
# systems
try:
new_md5 = md5(dest)
except ValueError:
new_md5 = None
if validate_checksum and new_checksum != remote_checksum:
return dict(failed=True, md5sum=new_md5, msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_checksum=remote_checksum)
return dict(changed=True, md5sum=new_md5, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_checksum=remote_checksum)
else:
# For backwards compatibility. We'll return None on FIPS enabled
# systems
try:
local_md5 = md5(dest)
except ValueError:
local_md5 = None
return dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum)

View file

@ -0,0 +1,37 @@
# Copyright 2012, Jeroen Hoekx <jeroen@hoekx.be>
#
# 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.errors import *
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
''' Create inventory groups based on variables '''
### We need to be able to modify the inventory
BYPASS_HOST_LOOP = True
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()):
if not 'key' in self._task.args:
return dict(failed=True, msg="the 'key' param is required when using group_by")
group_name = self._task.args.get('key')
group_name = group_name.replace(' ','-')
return dict(changed=True, add_group=group_name)

View file

@ -0,0 +1,134 @@
# Copyright 2012, Tim Bielawa <tbielawa@redhat.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 datetime
import sys
import time
from termios import tcflush, TCIFLUSH
from ansible.errors import *
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
''' pauses execution for a length or time, or until input is received '''
PAUSE_TYPES = ['seconds', 'minutes', 'prompt', '']
BYPASS_HOST_LOOP = True
def run(self, tmp=None, task_vars=dict()):
''' run the pause action module '''
duration_unit = 'minutes'
prompt = None
seconds = None
result = dict(
changed = False,
rc = 0,
stderr = '',
stdout = '',
start = None,
stop = None,
delta = None,
)
# FIXME: not sure if we can get this info directly like this anymore?
#hosts = ', '.join(self.runner.host_set)
# Is 'args' empty, then this is the default prompted pause
if self._task.args is None or len(self._task.args.keys()) == 0:
pause_type = 'prompt'
#prompt = "[%s]\nPress enter to continue:\n" % hosts
prompt = "[%s]\nPress enter to continue:\n" % self._task.get_name().strip()
# Are 'minutes' or 'seconds' keys that exist in 'args'?
elif 'minutes' in self._task.args or 'seconds' in self._task.args:
try:
if 'minutes' in self._task.args:
pause_type = 'minutes'
# The time() command operates in seconds so we need to
# recalculate for minutes=X values.
seconds = int(self._task.args['minutes']) * 60
else:
pause_type = 'seconds'
seconds = int(self._task.args['seconds'])
duration_unit = 'seconds'
except ValueError, e:
return dict(failed=True, msg="non-integer value given for prompt duration:\n%s" % str(e))
# Is 'prompt' a key in 'args'?
elif 'prompt' in self._task.args:
pause_type = 'prompt'
#prompt = "[%s]\n%s:\n" % (hosts, self._task.args['prompt'])
prompt = "[%s]\n%s:\n" % (self._task.get_name().strip(), self._task.args['prompt'])
# I have no idea what you're trying to do. But it's so wrong.
else:
return dict(failed=True, msg="invalid pause type given. must be one of: %s" % ", ".join(self.PAUSE_TYPES))
#vv("created 'pause' ActionModule: pause_type=%s, duration_unit=%s, calculated_seconds=%s, prompt=%s" % \
# (self.pause_type, self.duration_unit, self.seconds, self.prompt))
########################################################################
# Begin the hard work!
start = time.time()
result['start'] = str(datetime.datetime.now())
# FIXME: this is all very broken right now, as prompting from the worker side
# is not really going to be supported, and actions marked as BYPASS_HOST_LOOP
# probably should not be run through the executor engine at all. Also, ctrl+c
# is now captured on the parent thread, so it can't be caught here via the
# KeyboardInterrupt exception.
try:
if not pause_type == 'prompt':
print "(^C-c = continue early, ^C-a = abort)"
#print("[%s]\nPausing for %s seconds" % (hosts, seconds))
print("[%s]\nPausing for %s seconds" % (self._task.get_name().strip(), seconds))
time.sleep(seconds)
else:
# Clear out any unflushed buffered input which would
# otherwise be consumed by raw_input() prematurely.
#tcflush(sys.stdin, TCIFLUSH)
result['user_input'] = raw_input(prompt.encode(sys.stdout.encoding))
except KeyboardInterrupt:
while True:
print '\nAction? (a)bort/(c)ontinue: '
c = getch()
if c == 'c':
# continue playbook evaluation
break
elif c == 'a':
# abort further playbook evaluation
raise ae('user requested abort!')
finally:
duration = time.time() - start
result['stop'] = str(datetime.datetime.now())
result['delta'] = int(duration)
if duration_unit == 'minutes':
duration = round(duration / 60.0, 2)
else:
duration = round(duration, 2)
result['stdout'] = "Paused for %s %s" % (duration, duration_unit)
return result

View file

@ -0,0 +1,39 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=dict()):
# FIXME: need to rework the noop stuff still
#if self.runner.noop_on_check(inject):
# # in --check mode, always skip this module execution
# return ReturnData(conn=conn, comm_ok=True, result=dict(skipped=True))
executable = self._task.args.get('executable')
result = self._low_level_execute_command(self._task.args.get('_raw_params'), tmp=tmp, executable=executable)
# for some modules (script, raw), the sudo success key
# may leak into the stdout due to the way the sudo/su
# command is constructed, so we filter that out here
if result.get('stdout','').strip().startswith('SUDO-SUCCESS-'):
result['stdout'] = re.sub(r'^((\r)?\n)?SUDO-SUCCESS.*(\r)?\n', '', result['stdout'])
return result

View file

@ -0,0 +1,99 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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
from ansible import constants as C
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=None):
''' handler for file transfer operations '''
# FIXME: noop stuff still needs to be sorted out
#if self.runner.noop_on_check(inject):
# # in check mode, always skip this module
# return ReturnData(conn=conn, comm_ok=True,
# result=dict(skipped=True, msg='check mode not supported for this module'))
if not tmp:
tmp = self._make_tmp_path()
creates = self._task.args.get('creates')
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
result = self._execute_module(module_name='stat', module_args=dict(path=creates), tmp=tmp, persist_files=True)
stat = result.get('stat', None)
if stat and stat.get('exists', False):
return dict(skipped=True, msg=("skipped, since %s exists" % creates))
removes = self._task.args.get('removes')
if removes:
# do not run the command if the line contains removes=filename
# and the filename does not exist. This allows idempotence
# of command executions.
result = self._execute_module(module_name='stat', module_args=dict(path=removes), tmp=tmp, persist_files=True)
stat = result.get('stat', None)
if stat and not stat.get('exists', False):
return dict(skipped=True, msg=("skipped, since %s does not exist" % removes))
# the script name is the first item in the raw params, so we split it
# out now so we know the file name we need to transfer to the remote,
# and everything else is an argument to the script which we need later
# to append to the remote command
parts = self._task.args.get('_raw_params', '').strip().split()
source = parts[0]
args = ' '.join(parts[1:])
# FIXME: need to sort out all the _original_file stuff still
#if '_original_file' in task_vars:
# source = self._loader.path_dwim_relative(inject['_original_file'], 'files', source, self.runner.basedir)
#else:
# source = self._loader.path_dwim(self.runner.basedir, source)
source = self._loader.path_dwim(source)
# transfer the file to a remote tmp location
tmp_src = self._shell.join_path(tmp, os.path.basename(source))
self._connection.put_file(source, tmp_src)
sudoable = True
# set file permissions, more permissive when the copy is done as a different user
if ((self._connection_info.sudo and self._connection_info.sudo_user != 'root') or
(self._connection_info.su and self._connection_info.su_user != 'root')):
chmod_mode = 'a+rx'
sudoable = False
else:
chmod_mode = '+rx'
self._remote_chmod(tmp, chmod_mode, tmp_src, sudoable=sudoable)
# add preparation steps to one ssh roundtrip executing the script
env_string = self._compute_environment_string()
script_cmd = ' '.join([env_string, tmp_src, args])
result = self._low_level_execute_command(cmd=script_cmd, tmp=None, sudoable=sudoable)
# clean up after
if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES:
self._remove_tmp_path(tmp)
result['changed'] = True
return result

View file

@ -0,0 +1,178 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012-2013, Timothy Appnel <tim@appnel.com>
#
# 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.path
from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
class ActionModule(ActionBase):
def _get_absolute_path(self, path, task_vars):
if 'vars' in task_vars:
if '_original_file' in task_vars['vars']:
# roles
original_path = path
path = self._loader.path_dwim_relative(task_vars['_original_file'], 'files', path, self.runner.basedir)
if original_path and original_path[-1] == '/' and path[-1] != '/':
# make sure the dwim'd path ends in a trailing "/"
# if the original path did
path += '/'
return path
def _process_origin(self, host, path, user, task_vars):
if not host in ['127.0.0.1', 'localhost']:
if user:
return '%s@%s:%s' % (user, host, path)
else:
return '%s:%s' % (host, path)
else:
if not ':' in path:
if not path.startswith('/'):
path = self._get_absolute_path(path=path, task_vars=task_vars)
return path
def _process_remote(self, host, path, user, task_vars):
transport = self._connection_info.connection
return_data = None
if not host in ['127.0.0.1', 'localhost'] or transport != "local":
if user:
return_data = '%s@%s:%s' % (user, host, path)
else:
return_data = '%s:%s' % (host, path)
else:
return_data = path
if not ':' in return_data:
if not return_data.startswith('/'):
return_data = self._get_absolute_path(path=return_data, task_vars=task_vars)
return return_data
def run(self, tmp=None, task_vars=dict()):
''' generates params and passes them on to the rsync module '''
original_transport = task_vars.get('ansible_connection', self._connection_info.connection)
transport_overridden = False
if task_vars.get('delegate_to') is None:
task_vars['delegate_to'] = '127.0.0.1'
# IF original transport is not local, override transport and disable sudo.
if original_transport != 'local':
task_vars['ansible_connection'] = 'local'
self.transport_overridden = True
self.runner.sudo = False
src = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
# FIXME: this doesn't appear to be used anywhere?
local_rsync_path = task_vars.get('ansible_rsync_path')
# from the perspective of the rsync call the delegate is the localhost
src_host = '127.0.0.1'
dest_host = task_vars.get('ansible_ssh_host', task_vars.get('inventory_hostname'))
# allow ansible_ssh_host to be templated
# FIXME: does this still need to be templated?
#dest_host = template.template(self.runner.basedir, dest_host, task_vars, fail_on_undefined=True)
dest_is_local = dest_host in ['127.0.0.1', 'localhost']
# CHECK FOR NON-DEFAULT SSH PORT
dest_port = self._task.args.get('dest_port')
inv_port = task_vars.get('ansible_ssh_port', task_vars.get('inventory_hostname'))
if inv_port != dest_port and inv_port != task_vars.get('inventory_hostname'):
dest_port = inv_port
# edge case: explicit delegate and dest_host are the same
if dest_host == task_vars.get('delegate_to'):
dest_host = '127.0.0.1'
# SWITCH SRC AND DEST PER MODE
if self._task.args.get('mode', 'push') == 'pull':
(dest_host, src_host) = (src_host, dest_host)
# CHECK DELEGATE HOST INFO
use_delegate = False
# FIXME: not sure if this is in connection info yet or not...
#if conn.delegate != conn.host:
# if 'hostvars' in task_vars:
# if conn.delegate in task_vars['hostvars'] and self.original_transport != 'local':
# # use a delegate host instead of localhost
# use_delegate = True
# COMPARE DELEGATE, HOST AND TRANSPORT
process_args = False
if not dest_host is src_host and self.original_transport != 'local':
# interpret and task_vars remote host info into src or dest
process_args = True
# MUNGE SRC AND DEST PER REMOTE_HOST INFO
if process_args or use_delegate:
user = None
if boolean(options.get('set_remote_user', 'yes')):
if use_delegate:
user = task_vars['hostvars'][conn.delegate].get('ansible_ssh_user')
if not use_delegate or not user:
user = task_vars.get('ansible_ssh_user', self.runner.remote_user)
if use_delegate:
# FIXME
private_key = task_vars.get('ansible_ssh_private_key_file', self.runner.private_key_file)
else:
private_key = task_vars.get('ansible_ssh_private_key_file', self.runner.private_key_file)
private_key = template.template(self.runner.basedir, private_key, task_vars, fail_on_undefined=True)
if not private_key is None:
private_key = os.path.expanduser(private_key)
# use the mode to define src and dest's url
if self._task.args.get('mode', 'push') == 'pull':
# src is a remote path: <user>@<host>, dest is a local path
src = self._process_remote(src_host, src, user, task_vars)
dest = self._process_origin(dest_host, dest, user, task_vars)
else:
# src is a local path, dest is a remote path: <user>@<host>
src = self._process_origin(src_host, src, user, task_vars)
dest = self._process_remote(dest_host, dest, user, task_vars)
# Allow custom rsync path argument.
rsync_path = self._task.args.get('rsync_path', None)
# If no rsync_path is set, sudo was originally set, and dest is remote then add 'sudo rsync' argument.
if not rsync_path and self.transport_overridden and self._connection_info.sudo and not dest_is_local:
self._task.args['rsync_path'] = 'sudo rsync'
# make sure rsync path is quoted.
if rsync_path:
rsync_path = '"%s"' % rsync_path
# FIXME: noop stuff still needs to be figured out
#module_args = ""
#if self.runner.noop_on_check(task_vars):
# module_args = "CHECKMODE=True"
# run the module and store the result
result = self.runner._execute_module('synchronize', tmp=tmpmodule_args, complex_args=options, task_vars=task_vars)
return result

View file

@ -0,0 +1,165 @@
# (c) 2015, Michael DeHaan <michael.dehaan@gmail.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 os
from ansible.plugins.action import ActionBase
from ansible.template import Templar
from ansible.utils.hashing import checksum_s
class ActionModule(ActionBase):
TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=dict()):
''' handler for template operations '''
source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
if (source is None and 'first_available_file' not in task_vars) or dest is None:
return dict(failed=True, msg="src and dest are required")
if tmp is None:
tmp = self._make_tmp_path()
##################################################################################################
# FIXME: this all needs to be sorted out
##################################################################################################
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
#if 'first_available_file' in task_vars:
# found = False
# for fn in self.runner.module_vars.get('first_available_file'):
# fn_orig = fn
# fnt = template.template(self.runner.basedir, fn, task_vars)
# fnd = utils.path_dwim(self.runner.basedir, fnt)
# if not os.path.exists(fnd) and '_original_file' in task_vars:
# fnd = utils.path_dwim_relative(task_vars['_original_file'], 'templates', fnt, self.runner.basedir, check=False)
# if os.path.exists(fnd):
# source = fnd
# found = True
# break
# if not found:
# result = dict(failed=True, msg="could not find src in first_available_file list")
# return ReturnData(conn=conn, comm_ok=False, result=result)
#else:
# source = template.template(self.runner.basedir, source, task_vars)
#
# if '_original_file' in task_vars:
# source = utils.path_dwim_relative(task_vars['_original_file'], 'templates', source, self.runner.basedir)
# else:
# source = utils.path_dwim(self.runner.basedir, source)
##################################################################################################
source = self._loader.path_dwim(source)
##################################################################################################
# Expand any user home dir specification
dest = self._remote_expand_user(dest, tmp)
if dest.endswith("/"): # CCTODO: Fix path for Windows hosts.
base = os.path.basename(source)
dest = os.path.join(dest, base)
# template the source data locally & get ready to transfer
templar = Templar(basedir=self._loader.get_basedir(), variables=task_vars)
try:
with open(source, 'r') as f:
template_data = f.read()
resultant = templar.template(template_data, preserve_trailing_newlines=True)
except Exception, e:
return dict(failed=True, msg=type(e).__name__ + ": " + str(e))
local_checksum = checksum_s(resultant)
remote_checksum = self._remote_checksum(tmp, dest)
if remote_checksum in ('0', '2', '3', '4'):
# Note: 1 means the file is not present which is fine; template will create it
return dict(failed=True, msg="failed to checksum remote file. Checksum error code: %s" % remote_checksum)
if local_checksum != remote_checksum:
# if showing diffs, we need to get the remote value
dest_contents = ''
# FIXME: still need to implement diff mechanism
#if self.runner.diff:
# # using persist_files to keep the temp directory around to avoid needing to grab another
# dest_result = self.runner._execute_module(conn, tmp, 'slurp', "path=%s" % dest, task_vars=task_vars, persist_files=True)
# if 'content' in dest_result.result:
# dest_contents = dest_result.result['content']
# if dest_result.result['encoding'] == 'base64':
# dest_contents = base64.b64decode(dest_contents)
# else:
# raise Exception("unknown encoding, failed: %s" % dest_result.result)
xfered = self._transfer_data(self._shell.join_path(tmp, 'source'), resultant)
# fix file permissions when the copy is done as a different user
if self._connection_info.sudo and self._connection_info.sudo_user != 'root' or self._connection_info.su and self._connection_info.su_user != 'root':
self._remote_chmod('a+r', xfered, tmp)
# run the copy module
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=xfered,
dest=dest,
original_basename=os.path.basename(source),
follow=True,
),
)
# FIXME: noop stuff needs to be sorted out
#if self.runner.noop_on_check(task_vars):
# return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True), diff=dict(before_header=dest, after_header=source, before=dest_contents, after=resultant))
#else:
# res = self.runner._execute_module(conn, tmp, 'copy', module_args_tmp, task_vars=task_vars, complex_args=complex_args)
# if res.result.get('changed', False):
# res.diff = dict(before=dest_contents, after=resultant)
# return res
result = self._execute_module(module_name='copy', module_args=new_module_args)
if result.get('changed', False):
result['diff'] = dict(before=dest_contents, after=resultant)
return result
else:
# when running the file module based on the template data, we do
# not want the source filename (the name of the template) to be used,
# since this would mess up links, so we clear the src param and tell
# the module to follow links. When doing that, we have to set
# original_basename to the template just in case the dest is
# a directory.
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=None,
original_basename=os.path.basename(source),
follow=True,
),
)
# FIXME: this may not be required anymore, as the checkmod params
# should be in the regular module args?
# be sure to task_vars the check mode param into the module args and
# rely on the file module to report its changed status
#if self.runner.noop_on_check(task_vars):
# new_module_args['CHECKMODE'] = True
return self._execute_module(module_name='file', module_args=new_module_args)

View file

@ -0,0 +1,118 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# (c) 2013, Dylan Martin <dmartin@seattlecentral.edu>
#
# 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
from ansible.plugins.action import ActionBase
## fixes https://github.com/ansible/ansible/issues/3518
# http://mypy.pythonblogs.com/12_mypy/archive/1253_workaround_for_python_bug_ascii_codec_cant_encode_character_uxa0_in_position_111_ordinal_not_in_range128.html
import sys
reload(sys)
sys.setdefaultencoding("utf8")
import pipes
class ActionModule(ActionBase):
TRANSFERS_FILES = True
def run(self, tmp=None, task_vars=dict()):
''' handler for unarchive operations '''
source = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
copy = self._task.args.get('copy', True)
creates = self._task.args.get('creates', None)
if source is None or dest is None:
return dict(failed=True, msg="src (or content) and dest are required")
if not tmp:
tmp = self._make_tmp_path()
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
module_args_tmp = "path=%s" % creates
result = self._execute_module(module_name='stat', module_args=dict(path=creates))
stat = result.get('stat', None)
if stat and stat.get('exists', False):
return dict(skipped=True, msg=("skipped, since %s exists" % creates))
dest = self._remote_expand_user(dest, tmp) # CCTODO: Fix path for Windows hosts.
source = os.path.expanduser(source)
if copy:
# FIXME: the original file stuff needs to be reworked
if '_original_file' in task_vars:
source = self._loader.path_dwim_relative(task_vars['_original_file'], 'files', source)
else:
source = self._loader.path_dwim(source)
remote_checksum = self._remote_checksum(tmp, dest)
if remote_checksum != '3':
return dict(failed=True, msg="dest '%s' must be an existing dir" % dest)
elif remote_checksum == '4':
return dict(failed=True, msg="python isn't present on the system. Unable to compute checksum")
if copy:
# transfer the file to a remote tmp location
tmp_src = tmp + 'source'
self._connection.put_file(source, tmp_src)
# handle diff mode client side
# handle check mode client side
# fix file permissions when the copy is done as a different user
if copy:
if self._connection_info.sudo and self._connection_info.sudo_user != 'root' or self._connection_info.su and self._connection_info.su_user != 'root':
# FIXME: noop stuff needs to be reworked
#if not self.runner.noop_on_check(task_vars):
# self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp)
self._remote_chmod(tmp, 'a+r', tmp_src)
# Build temporary module_args.
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
src=tmp_src,
original_basename=os.path.basename(source),
),
)
# make sure checkmod is passed on correctly
# FIXME: noop again, probably doesn't need to be done here anymore?
#if self.runner.noop_on_check(task_vars):
# new_module_args['CHECKMODE'] = True
else:
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
original_basename=os.path.basename(source),
),
)
# make sure checkmod is passed on correctly
# FIXME: noop again, probably doesn't need to be done here anymore?
#if self.runner.noop_on_check(task_vars):
# module_args += " CHECKMODE=True"
# execute the unarchive module now, with the updated args
return self._execute_module(module_args=new_module_args)