Very basic --diff option for showing what happens when templates change.
Probably output is not useful if not used with --limit Works well with --check mode
This commit is contained in:
parent
3d6993221e
commit
a9162a86f2
8 changed files with 95 additions and 18 deletions
18
bin/ansible
18
bin/ansible
|
@ -45,8 +45,18 @@ class Cli(object):
|
|||
def parse(self):
|
||||
''' create an options parser for bin/ansible '''
|
||||
|
||||
parser = utils.base_parser(constants=C, runas_opts=True, subset_opts=True, async_opts=True,
|
||||
output_opts=True, connect_opts=True, check_opts=True, usage='%prog <host-pattern> [options]')
|
||||
parser = utils.base_parser(
|
||||
constants=C,
|
||||
runas_opts=True,
|
||||
subset_opts=True,
|
||||
async_opts=True,
|
||||
output_opts=True,
|
||||
connect_opts=True,
|
||||
check_opts=True,
|
||||
diff_opts=False,
|
||||
usage='%prog <host-pattern> [options]'
|
||||
)
|
||||
|
||||
parser.add_option('-a', '--args', dest='module_args',
|
||||
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
|
||||
parser.add_option('-m', '--module-name', dest='module_name',
|
||||
|
@ -54,6 +64,7 @@ class Cli(object):
|
|||
default=C.DEFAULT_MODULE_NAME)
|
||||
parser.add_option('--list-hosts', dest='listhosts', action='store_true',
|
||||
help="dump out a list of hosts matching input pattern, does not execute any modules!")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
self.callbacks.options = options
|
||||
|
||||
|
@ -112,7 +123,8 @@ class Cli(object):
|
|||
callbacks=self.callbacks, sudo=options.sudo,
|
||||
sudo_pass=sudopass,sudo_user=options.sudo_user,
|
||||
transport=options.connection, subset=options.subset,
|
||||
check=options.check
|
||||
check=options.check,
|
||||
diff=options.check
|
||||
)
|
||||
|
||||
if options.seconds:
|
||||
|
|
|
@ -52,8 +52,15 @@ def main(args):
|
|||
|
||||
# create parser for CLI options
|
||||
usage = "%prog playbook.yml"
|
||||
parser = utils.base_parser(constants=C, usage=usage, connect_opts=True,
|
||||
runas_opts=True, subset_opts=True, check_opts=True)
|
||||
parser = utils.base_parser(
|
||||
constants=C,
|
||||
usage=usage,
|
||||
connect_opts=True,
|
||||
runas_opts=True,
|
||||
subset_opts=True,
|
||||
check_opts=True,
|
||||
diff_opts=True
|
||||
)
|
||||
parser.add_option('-e', '--extra-vars', dest="extra_vars", default=None,
|
||||
help="set additional key=value variables from the CLI")
|
||||
parser.add_option('-t', '--tags', dest='tags', default='all',
|
||||
|
@ -122,7 +129,8 @@ def main(args):
|
|||
extra_vars=extra_vars,
|
||||
private_key_file=options.private_key_file,
|
||||
only_tags=only_tags,
|
||||
check=options.check
|
||||
check=options.check,
|
||||
diff=options.diff
|
||||
)
|
||||
|
||||
if options.listhosts:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
|
||||
# (C) 2012-2013, Michael DeHaan, <michael.dehaan@gmail.com>
|
||||
|
||||
# This file is part of Ansible
|
||||
#
|
||||
|
@ -35,6 +35,15 @@ elif os.path.exists("/usr/local/bin/cowsay"):
|
|||
# BSD path for cowsay
|
||||
cowsay = "/usr/local/bin/cowsay"
|
||||
|
||||
|
||||
# ****************************************************************************
|
||||
# 1.1 DEV NOTES
|
||||
# FIXME -- in order to make an ideal callback system, all of these should have
|
||||
# access to the current task and/or play and host objects. We need to this
|
||||
# while keeping present callbacks functionally intact and will do so.
|
||||
# ****************************************************************************
|
||||
|
||||
|
||||
def call_callback_module(method_name, *args, **kwargs):
|
||||
|
||||
for callback_plugin in utils.plugins.callback_loader.all():
|
||||
|
@ -209,6 +218,9 @@ class DefaultRunnerCallbacks(object):
|
|||
def on_async_failed(self, host, res, jid):
|
||||
call_callback_module('runner_on_async_failed', host, res, jid)
|
||||
|
||||
def on_file_diff(self, host, before_string, after_string):
|
||||
call_callback_module('runner_on_file_diff', before_string, after_string)
|
||||
|
||||
########################################################################
|
||||
|
||||
class CliRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
|
@ -273,6 +285,11 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
|
|||
if self.options.tree:
|
||||
utils.write_tree_file(self.options.tree, host, utils.jsonify(result2,format=True))
|
||||
|
||||
def on_file_diff(self, host, before_string, after_string):
|
||||
if self.options.diff:
|
||||
print utils.get_diff(before_string, after_string)
|
||||
super(CliRunnerCallbacks, self).on_file_diff(host, before_string, after_string)
|
||||
|
||||
########################################################################
|
||||
|
||||
class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
|
@ -404,6 +421,9 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
|||
print stringc(msg, 'red')
|
||||
super(PlaybookRunnerCallbacks, self).on_async_failed(host,res,jid)
|
||||
|
||||
def on_file_diff(self, host, before_string, after_string):
|
||||
print utils.get_diff(before_string, after_string)
|
||||
super(PlaybookRunnerCallbacks, self).on_file_diff(host, before_string, after_string)
|
||||
|
||||
########################################################################
|
||||
|
||||
|
|
|
@ -62,7 +62,8 @@ class PlayBook(object):
|
|||
only_tags = None,
|
||||
subset = C.DEFAULT_SUBSET,
|
||||
inventory = None,
|
||||
check = False):
|
||||
check = False,
|
||||
diff = False):
|
||||
|
||||
"""
|
||||
playbook: path to a playbook file
|
||||
|
@ -94,6 +95,7 @@ class PlayBook(object):
|
|||
only_tags = [ 'all' ]
|
||||
|
||||
self.check = check
|
||||
self.diff = diff
|
||||
self.module_path = module_path
|
||||
self.forks = forks
|
||||
self.timeout = timeout
|
||||
|
@ -271,7 +273,7 @@ class PlayBook(object):
|
|||
conditional=task.only_if, callbacks=self.runner_callbacks,
|
||||
sudo=task.sudo, sudo_user=task.sudo_user,
|
||||
transport=task.transport, sudo_pass=task.sudo_pass, is_playbook=True,
|
||||
check=self.check
|
||||
check=self.check, diff=self.diff
|
||||
)
|
||||
|
||||
if task.async_seconds == 0:
|
||||
|
@ -377,7 +379,7 @@ class PlayBook(object):
|
|||
remote_pass=self.remote_pass, remote_port=play.remote_port, private_key_file=self.private_key_file,
|
||||
setup_cache=self.SETUP_CACHE, callbacks=self.runner_callbacks, sudo=play.sudo, sudo_user=play.sudo_user,
|
||||
transport=play.transport, sudo_pass=self.sudo_pass, is_playbook=True, module_vars=play.vars,
|
||||
check=self.check
|
||||
check=self.check, diff=self.diff
|
||||
).run()
|
||||
self.stats.compute(setup_results, setup=True)
|
||||
|
||||
|
|
|
@ -118,11 +118,13 @@ class Runner(object):
|
|||
is_playbook=False, # running from playbook or not?
|
||||
inventory=None, # reference to Inventory object
|
||||
subset=None, # subset pattern
|
||||
check=False # don't make any changes, just try to probe for potential changes
|
||||
check=False, # don't make any changes, just try to probe for potential changes
|
||||
diff=False
|
||||
):
|
||||
|
||||
# storage & defaults
|
||||
self.check = check
|
||||
self.diff = diff
|
||||
self.setup_cache = utils.default(setup_cache, lambda: collections.defaultdict(dict))
|
||||
self.basedir = utils.default(basedir, lambda: os.getcwd())
|
||||
self.callbacks = utils.default(callbacks, lambda: DefaultRunnerCallbacks())
|
||||
|
@ -192,7 +194,7 @@ class Runner(object):
|
|||
# *****************************************************
|
||||
|
||||
def _execute_module(self, conn, tmp, module_name, args,
|
||||
async_jid=None, async_module=None, async_limit=None, inject=None):
|
||||
async_jid=None, async_module=None, async_limit=None, inject=None, persist_files=False):
|
||||
|
||||
''' runs a module that has already been transferred '''
|
||||
|
||||
|
@ -233,7 +235,7 @@ class Runner(object):
|
|||
raise errors.AnsibleError("module is missing interpreter line")
|
||||
|
||||
cmd = shebang.replace("#!","") + " " + cmd
|
||||
if tmp.find("tmp") != -1 and C.DEFAULT_KEEP_REMOTE_FILES != '1':
|
||||
if tmp.find("tmp") != -1 and C.DEFAULT_KEEP_REMOTE_FILES != '1' and not persist_files:
|
||||
cmd = cmd + "; rm -rf %s >/dev/null 2>&1" % tmp
|
||||
res = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
|
||||
data = utils.parse_json(res['stdout'])
|
||||
|
|
|
@ -20,6 +20,7 @@ import os
|
|||
from ansible import utils
|
||||
from ansible import errors
|
||||
from ansible.runner.return_data import ReturnData
|
||||
import base64
|
||||
|
||||
class ActionModule(object):
|
||||
|
||||
|
@ -46,6 +47,7 @@ class ActionModule(object):
|
|||
|
||||
# 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 inject:
|
||||
found = False
|
||||
for fn in self.runner.module_vars.get('first_available_file'):
|
||||
|
@ -79,7 +81,19 @@ class ActionModule(object):
|
|||
|
||||
# template is different from the remote value
|
||||
|
||||
xfered = self.runner._transfer_str(conn, tmp, 'source', resultant)
|
||||
# if showing diffs, we need to get the remote value
|
||||
dest_contents = None
|
||||
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, inject=inject, persist_files=True)
|
||||
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.runner._transfer_str(conn, tmp, source, resultant)
|
||||
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root':
|
||||
self.runner._low_level_exec_command(conn, "chmod a+r %s" % xfered, tmp)
|
||||
|
@ -88,9 +102,14 @@ class ActionModule(object):
|
|||
module_args = "%s src=%s dest=%s" % (module_args, xfered, dest)
|
||||
|
||||
if self.runner.check:
|
||||
if self.runner.diff:
|
||||
self.runner.callbacks.on_file_diff(conn.host, dest_contents, resultant)
|
||||
return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True))
|
||||
else:
|
||||
return self.runner._execute_module(conn, tmp, 'copy', module_args, inject=inject)
|
||||
res = self.runner._execute_module(conn, tmp, 'copy', module_args, inject=inject)
|
||||
if self.runner.diff:
|
||||
self.runner.callbacks.on_file_diff(conn.host, dest_contents, resultant)
|
||||
return res
|
||||
else:
|
||||
return ReturnData(conn=conn, comm_ok=True, result=dict(changed=False))
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import termios
|
|||
import tty
|
||||
import pipes
|
||||
import random
|
||||
import difflib
|
||||
|
||||
VERBOSITY=0
|
||||
|
||||
|
@ -395,7 +396,7 @@ def increment_debug(option, opt, value, parser):
|
|||
VERBOSITY += 1
|
||||
|
||||
def base_parser(constants=C, usage="", output_opts=False, runas_opts=False,
|
||||
async_opts=False, connect_opts=False, subset_opts=False, check_opts=False):
|
||||
async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, diff_opts=False):
|
||||
''' create an options parser for any ansible script '''
|
||||
|
||||
parser = SortedOptParser(usage, version=version("%prog"))
|
||||
|
@ -457,6 +458,12 @@ def base_parser(constants=C, usage="", output_opts=False, runas_opts=False,
|
|||
help="don't make any changes, instead try to predict some of the changes that may occur"
|
||||
)
|
||||
|
||||
if diff_opts:
|
||||
parser.add_option("-D", "--diff", default=False, dest='diff', action='store_true',
|
||||
help="when changing (small) files and templates, show the differences in those files, works great with --check"
|
||||
)
|
||||
|
||||
|
||||
return parser
|
||||
|
||||
def do_encrypt(result, encrypt, salt_size=None, salt=None):
|
||||
|
@ -602,3 +609,10 @@ def make_sudo_cmd(sudo_user, executable, cmd):
|
|||
C.DEFAULT_SUDO_EXE, C.DEFAULT_SUDO_EXE, C.DEFAULT_SUDO_FLAGS,
|
||||
prompt, sudo_user, executable or '$SHELL', pipes.quote(cmd))
|
||||
return ('/bin/sh -c ' + pipes.quote(sudocmd), prompt)
|
||||
|
||||
def get_diff(before_string, after_string):
|
||||
# called by --diff usage in playbook and runner via callbacks
|
||||
# include names in diffs 'before' and 'after' and do diff -U 10
|
||||
differ = difflib.unified_diff(before_string.split("\n"), after_string.split("\n"), 'before', 'after', '', '', 10)
|
||||
return "\n".join(list(differ))
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ author: Michael DeHaan
|
|||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
src = dict(required=True),
|
||||
src = dict(required=True, aliases=['path']),
|
||||
)
|
||||
)
|
||||
source = module.params['src']
|
||||
|
|
Loading…
Reference in a new issue