hacking/test-module
ee274b58
 #!/usr/bin/env python
a735dd2b
 
 # (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:
fc96b882
 #    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
a735dd2b
 
 import sys
fc96b882
 import base64
a735dd2b
 import os
 import subprocess
 import traceback
ade0233d
 import optparse
d0f43587
 import ansible.utils as utils
 import ansible.module_common as module_common
06e54c0b
 import ansible.constants as C
a735dd2b
 
 try:
     import json
 except ImportError:
     import simplejson as json
 
ade0233d
 def parse():
     """parse command line
 
     :return : (options, args)"""
     parser = optparse.OptionParser()
 
738cea9c
     parser.usage = "%prog -[options] (-h for help)"
ade0233d
 
e8583833
     parser.add_option('-m', '--module-path', dest='module_path',
738cea9c
         help="REQUIRED: full path of module source to execute")
ade0233d
     parser.add_option('-a', '--args', dest='module_args', default="",
738cea9c
         help="module argument string")
ade0233d
     parser.add_option('-D', '--debugger', dest='debugger', 
         help="path to python debugger (e.g. /usr/bin/pdb)")
     options, args = parser.parse_args()
e8583833
     if not options.module_path:
ade0233d
         parser.print_help()
         sys.exit(1)
     else:
         return options, args
 
af2fb56a
 def write_argsfile(argstring, json=False):
fc96b882
     """ Write args to a file for old-style module's use. """
ade0233d
     argspath = os.path.expanduser("~/.ansible_test_module_arguments")
     argsfile = open(argspath, 'w')
af2fb56a
     if json:
         args = utils.parse_kv(argstring)
         argstring = utils.jsonify(args)
ade0233d
     argsfile.write(argstring)
     argsfile.close()
     return argspath
 
fc96b882
 def boilerplate_module(modfile, args):
     """ simulate what ansible does with new style modules """
 
d34a26e3
     #module_fh = open(modfile)
     #module_data = module_fh.read()
     #module_fh.close()
 
     replacer = module_common.ModuleReplacer()
 
     #included_boilerplate = module_data.find(module_common.REPLACER) != -1 or module_data.find("import ansible.module_utils") != -1
 
     complex_args = {}
     inject = {}
     (module_data, module_style, shebang) = replacer.modify_module(
         modfile, 
         complex_args,
         args,
         inject 
     )
af2fb56a
 
d34a26e3
     modfile2_path = os.path.expanduser("~/.ansible_module_generated")
     print "* including generated source, if any, saving to: %s" % modfile2_path
     print "* this may offset any line numbers in tracebacks/debuggers!"
     modfile2 = open(modfile2_path, 'w')
     modfile2.write(module_data)
     modfile2.close()
     modfile = modfile2_path
9858b1f2
 
d34a26e3
     return (modfile2_path, module_style)
ade0233d
 
e8583833
 def runtest( modfile, argspath):
     """Test run a module, piping it's output for reporting."""
fc96b882
 
ade0233d
     os.system("chmod +x %s" % modfile)
fc96b882
 
     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)
ade0233d
     (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)
be55145a
 
ade0233d
     print "***********************************"
     print "PARSED OUTPUT"
     print utils.jsonify(results,format=True)
a735dd2b
 
e8583833
 def rundebug(debugger, modfile, argspath):
     """Run interactively with console debugger."""
 
fc96b882
     if argspath is not None:
         subprocess.call("%s %s %s" % (debugger, modfile, argspath), shell=True)
     else:
         subprocess.call("%s %s" % (debugger, modfile), shell=True)
e8583833
 
fc96b882
 def main(): 
e8583833
 
fc96b882
     options, args = parse()
d34a26e3
     (modfile, module_style) = boilerplate_module(options.module_path, options.module_args)
af2fb56a
 
fc96b882
     argspath=None
d34a26e3
     if module_style != 'new':
         if module_style == 'non_native_want_json':
af2fb56a
             argspath = write_argsfile(options.module_args, json=True)
d34a26e3
         elif module_style == 'old':
d154bf87
             argspath = write_argsfile(options.module_args, json=False)
d34a26e3
         else:
             raise Exception("internal error, unexpected module style: %s" % module_style)
e8583833
     if options.debugger: 
fc96b882
         rundebug(options.debugger, modfile, argspath)
e8583833
     else:
fc96b882
         runtest(modfile, argspath)
d34a26e3
         
ade0233d
 if __name__ == "__main__":
     main()
a735dd2b