#!/usr/bin/env python # (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/>. # # this script is for testing modules without running through the # entire guts of ansible, and is very helpful for when developing # modules # # example: # test-module -m ../library/command -a "/bin/sleep 3" # test-module -m ../library/service -a "name=httpd ensure=restarted" # test-module -m ../library/service -a "name=httpd ensure=restarted" --debugger /usr/bin/pdb import sys import base64 import os import subprocess import traceback import optparse import ansible.utils as utils import ansible.module_common as module_common import ansible.constants as C try: import json except ImportError: import simplejson as json def parse(): """parse command line :return : (options, args)""" parser = optparse.OptionParser() parser.usage = "%prog -[options] (-h for help)" parser.add_option('-m', '--module-path', dest='module_path', help="REQUIRED: full path of module source to execute") parser.add_option('-a', '--args', dest='module_args', default="", help="module argument string") parser.add_option('-D', '--debugger', dest='debugger', help="path to python debugger (e.g. /usr/bin/pdb)") options, args = parser.parse_args() if not options.module_path: parser.print_help() sys.exit(1) else: return options, args def write_argsfile(argstring, json=False): """ Write args to a file for old-style module's use. """ argspath = os.path.expanduser("~/.ansible_test_module_arguments") argsfile = open(argspath, 'w') if json: args = utils.parse_kv(argstring) argstring = utils.jsonify(args) argsfile.write(argstring) argsfile.close() return argspath def boilerplate_module(modfile, args): """ simulate what ansible does with new style modules """ module_fh = open(modfile) module_data = module_fh.read() included_boilerplate = module_data.find(module_common.REPLACER) != -1 module_fh.close() if included_boilerplate: module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON) encoded_args = repr(str(args)) module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args) encoded_lang = repr(C.DEFAULT_MODULE_LANG) empty_complex = repr("{}") module_data = module_data.replace(module_common.REPLACER_LANG, encoded_lang) module_data = module_data.replace('syslog.LOG_USER', "syslog.%s" % C.DEFAULT_SYSLOG_FACILITY) module_data = module_data.replace(module_common.REPLACER_COMPLEX, empty_complex) modfile2_path = os.path.expanduser("~/.ansible_module_generated") print "* including generated source, if any, saving to: %s" % modfile2_path print "* this will offset any line numbers in tracebacks/debuggers!" modfile2 = open(modfile2_path, 'w') modfile2.write(module_data) modfile2.close() modfile = modfile2_path return (modfile2_path, included_boilerplate, False) else: old_style_but_json = False if 'WANT_JSON' in module_data: old_style_but_json = True print "* module boilerplate substitution not requested in module, line numbers will be unaltered" return (modfile, included_boilerplate, old_style_but_json) def runtest( modfile, argspath): """Test run a module, piping it's output for reporting.""" os.system("chmod +x %s" % modfile) invoke = "%s" % (modfile) if argspath is not None: invoke = "%s %s" % (modfile, argspath) cmd = subprocess.Popen(invoke, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = cmd.communicate() try: print "***********************************" print "RAW OUTPUT" print out print err results = utils.parse_json(out) except: print "***********************************" print "INVALID OUTPUT FORMAT" print out traceback.print_exc() sys.exit(1) print "***********************************" print "PARSED OUTPUT" print utils.jsonify(results,format=True) def rundebug(debugger, modfile, argspath): """Run interactively with console debugger.""" if argspath is not None: subprocess.call("%s %s %s" % (debugger, modfile, argspath), shell=True) else: subprocess.call("%s %s" % (debugger, modfile), shell=True) def main(): options, args = parse() (modfile, is_new_style, old_style_but_json) = boilerplate_module(options.module_path, options.module_args) argspath=None if not is_new_style: if old_style_but_json: argspath = write_argsfile(options.module_args, json=True) else: argspath = write_argsfile(options.module_args, json=False) if options.debugger: rundebug(options.debugger, modfile, argspath) else: runtest(modfile, argspath) if __name__ == "__main__": main()