From 33d2728879f8655337bc2157b723f1efa3a1920a Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Tue, 2 Jul 2019 12:18:40 -0700 Subject: [PATCH] Rename python files in hacking/ directory to have .py suffix ansible-test only passes files which have the .py suffix for sanity tests on python files. This change will allow sanity tests to run on the Python files in hacking/ * Rename test-module to test-module.py * Symlink test-module for backwards compat since end users may be using test-module * Fix test-module sanity errors that are now triggered * Rename ansible_profile to ansible-profile.py * Rename build-ansible --- MANIFEST.in | 2 +- docs/docsite/rst/dev_guide/debugging.rst | 4 +- .../developing_modules_best_practices.rst | 2 +- hacking/README.md | 10 +- hacking/{ansible_profile => ansible-profile} | 0 hacking/{build-ansible => build-ansible.py} | 2 +- .../build_library/build_ansible/commands.py | 4 +- hacking/return_skeleton_generator.py | 2 +- hacking/test-module | 275 +---------------- hacking/test-module.py | 277 ++++++++++++++++++ lib/ansible/executor/module_common.py | 2 +- lib/ansible/module_utils/basic.py | 4 +- lib/ansible/modules/system/openwrt_init.py | 2 +- lib/ansible/modules/system/service.py | 2 +- test/integration/targets/test_infra/aliases | 2 +- test/integration/targets/test_infra/runme.sh | 12 +- test/runner/requirements/sanity.txt | 2 +- test/sanity/code-smell/shebang.py | 2 +- 18 files changed, 305 insertions(+), 301 deletions(-) rename hacking/{ansible_profile => ansible-profile} (100%) rename hacking/{build-ansible => build-ansible.py} (98%) mode change 100755 => 120000 hacking/test-module create mode 100755 hacking/test-module.py diff --git a/MANIFEST.in b/MANIFEST.in index f70bced34a..da554f16c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -26,4 +26,4 @@ include contrib/README.md recursive-include contrib/inventory * exclude test/sanity/code-smell/botmeta.* recursive-include hacking/build_library *.py -include hacking/build-ansible +include hacking/build-ansible.py diff --git a/docs/docsite/rst/dev_guide/debugging.rst b/docs/docsite/rst/dev_guide/debugging.rst index e176740d2e..4234985270 100644 --- a/docs/docsite/rst/dev_guide/debugging.rst +++ b/docs/docsite/rst/dev_guide/debugging.rst @@ -36,11 +36,11 @@ Debugging AnsibleModule-based modules .. tip:: - If you're using the :file:`hacking/test-module` script then most of this + If you're using the :file:`hacking/test-module.py` script then most of this is taken care of for you. If you need to do some debugging of the module on the remote machine that the module will actually run on or when the module is used in a playbook then you may need to use this information - instead of relying on test-module. + instead of relying on :file:`test-module.py`. Starting with Ansible 2.1, AnsibleModule-based modules are put together as a zip file consisting of the module file and the various python module diff --git a/docs/docsite/rst/dev_guide/developing_modules_best_practices.rst b/docs/docsite/rst/dev_guide/developing_modules_best_practices.rst index e18a6f36ae..ad56d6338e 100644 --- a/docs/docsite/rst/dev_guide/developing_modules_best_practices.rst +++ b/docs/docsite/rst/dev_guide/developing_modules_best_practices.rst @@ -33,7 +33,7 @@ General guidelines & tips * Each module should be self-contained in one file, so it can be be auto-transferred by Ansible. * Module name MUST use underscores instead of hyphens or spaces as a word separator. Using hyphens and spaces will prevent Ansible from importing your module. -* Always use the ``hacking/test-module`` script when developing modules - it will warn you about common pitfalls. +* Always use the ``hacking/test-module.py`` script when developing modules - it will warn you about common pitfalls. * If you have a local module that returns facts specific to your installations, a good name for this module is ``site_facts``. * Eliminate or minimize dependencies. If your module has dependencies, document them at the top of the module file and raise JSON error messages when dependency import fails. * Don't write to files directly; use a temporary file and then use the ``atomic_move`` function from ``ansible.module_utils.basic`` to move the updated temporary file into place. This prevents data corruption and ensures that the correct context for the file is kept. diff --git a/hacking/README.md b/hacking/README.md index 3df98a3c8a..b31c884f0f 100644 --- a/hacking/README.md +++ b/hacking/README.md @@ -21,15 +21,15 @@ can install them from pip From there, follow ansible instructions on docs.ansible.com as normal. -test-module ------------ +test-module.py +-------------- -'test-module' is a simple program that allows module developers (or testers) to run +'test-module.py' is a simple program that allows module developers (or testers) to run a module outside of the ansible program, locally, on the current machine. Example: - $ ./hacking/test-module -m lib/ansible/modules/commands/command.py -a "echo hi" + $ ./hacking/test-module.py -m lib/ansible/modules/commands/command.py -a "echo hi" This is a good way to insert a breakpoint into a module, for instance. @@ -46,7 +46,7 @@ parent: Use: - $ ./hacking/test-module -m module \ + $ ./hacking/test-module.py -m module \ -a '{"parent": {"child": [{"item": "first", "val": "foo"}, {"item": "second", "val": "bar"}]}}' return_skeleton_generator.py diff --git a/hacking/ansible_profile b/hacking/ansible-profile similarity index 100% rename from hacking/ansible_profile rename to hacking/ansible-profile diff --git a/hacking/build-ansible b/hacking/build-ansible.py similarity index 98% rename from hacking/build-ansible rename to hacking/build-ansible.py index 69f98c6f48..1a3517ae2b 100755 --- a/hacking/build-ansible +++ b/hacking/build-ansible.py @@ -56,7 +56,7 @@ def main(): arg_parser = create_arg_parser(os.path.basename(sys.argv[0])) subparsers = arg_parser.add_subparsers(title='Subcommands', dest='command', - help='for help use build-ansible SUBCOMMANDS -h') + help='for help use build-ansible.py SUBCOMMANDS -h') subcommands.pipe('init_parser', subparsers.add_parser) if argcomplete: diff --git a/hacking/build_library/build_ansible/commands.py b/hacking/build_library/build_ansible/commands.py index 363e8056fc..e689e25a37 100644 --- a/hacking/build_library/build_ansible/commands.py +++ b/hacking/build_library/build_ansible/commands.py @@ -12,9 +12,9 @@ from abc import ABCMeta, abstractmethod, abstractproperty class Command: """ - Subcommands of :program:`build-ansible`. + Subcommands of :program:`build-ansible.py`. - This defines an interface that all subcommands must conform to. :program:`build-ansible` will + This defines an interface that all subcommands must conform to. :program:`build-ansible.py` will require that these things are present in order to proceed. """ @staticmethod diff --git a/hacking/return_skeleton_generator.py b/hacking/return_skeleton_generator.py index e464300837..adfe3d76c1 100755 --- a/hacking/return_skeleton_generator.py +++ b/hacking/return_skeleton_generator.py @@ -21,7 +21,7 @@ # and creates a starting point for the RETURNS section of a module. # This can be provided as stdin or a file argument # -# The easiest way to obtain the JSON output is to use hacking/test-module +# The easiest way to obtain the JSON output is to use hacking/test-module.py # # You will likely want to adjust this to remove sensitive data or # ensure the `returns` value is correct, and to write a useful description diff --git a/hacking/test-module b/hacking/test-module deleted file mode 100755 index 1eb9c91252..0000000000 --- a/hacking/test-module +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python - -# (c) 2012, Michael DeHaan -# -# 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 . -# - -# this script is for testing modules without running through the -# entire guts of ansible, and is very helpful for when developing -# modules -# -# example: -# ./hacking/test-module -m lib/ansible/modules/commands/command.py -a "/bin/sleep 3" -# ./hacking/test-module -m lib/ansible/modules/commands/command.py -a "/bin/sleep 3" --debugger /usr/bin/pdb -# ./hacking/test-module -m lib/ansible/modules/files/lineinfile.py -a "dest=/etc/exports line='/srv/home hostname1(rw,sync)'" --check -# ./hacking/test-module -m lib/ansible/modules/commands/command.py -a "echo hello" -n -o "test_hello" - -import optparse -import os -import subprocess -import sys -import traceback -import shutil - -from ansible.release import __version__ -import ansible.utils.vars as utils_vars -from ansible.parsing.dataloader import DataLoader -from ansible.parsing.utils.jsonify import jsonify -from ansible.parsing.splitter import parse_kv -import ansible.executor.module_common as module_common -import ansible.constants as C -from ansible.module_utils._text import to_native, to_text -from ansible.template import Templar - -import 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)") - parser.add_option('-I', '--interpreter', dest='interpreter', - help="path to interpreter to use for this module (e.g. ansible_python_interpreter=/usr/bin/python)", - metavar='INTERPRETER_TYPE=INTERPRETER_PATH', - default="ansible_python_interpreter=%s" % (sys.executable if sys.executable else '/usr/bin/python')) - parser.add_option('-c', '--check', dest='check', action='store_true', - help="run the module in check mode") - parser.add_option('-n', '--noexecute', dest='execute', action='store_false', - default=True, help="do not run the resulting module") - parser.add_option('-o', '--output', dest='filename', - help="Filename for resulting module", - default="~/.ansible_module_generated") - 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 = parse_kv(argstring) - argstring = jsonify(args) - argsfile.write(argstring) - argsfile.close() - return argspath - - -def get_interpreters(interpreter): - result = dict() - if interpreter: - if '=' not in interpreter: - print("interpreter must by in the form of ansible_python_interpreter=/usr/bin/python") - sys.exit(1) - interpreter_type, interpreter_path = interpreter.split('=') - if not interpreter_type.startswith('ansible_'): - interpreter_type = 'ansible_%s' % interpreter_type - if not interpreter_type.endswith('_interpreter'): - interpreter_type = '%s_interpreter' % interpreter_type - result[interpreter_type] = interpreter_path - return result - - -def boilerplate_module(modfile, args, interpreters, check, destfile): - """ simulate what ansible does with new style modules """ - - # module_fh = open(modfile) - # module_data = module_fh.read() - # module_fh.close() - - # replacer = module_common.ModuleReplacer() - loader = DataLoader() - - # included_boilerplate = module_data.find(module_common.REPLACER) != -1 or module_data.find("import ansible.module_utils") != -1 - - complex_args = {} - - # default selinux fs list is pass in as _ansible_selinux_special_fs arg - complex_args['_ansible_selinux_special_fs'] = C.DEFAULT_SELINUX_SPECIAL_FS - complex_args['_ansible_tmpdir'] = C.DEFAULT_LOCAL_TMP - complex_args['_ansible_keep_remote_files'] = C.DEFAULT_KEEP_REMOTE_FILES - complex_args['_ansible_version'] = __version__ - - if args.startswith("@"): - # Argument is a YAML file (JSON is a subset of YAML) - complex_args = utils_vars.combine_vars(complex_args, loader.load_from_file(args[1:])) - args='' - elif args.startswith("{"): - # Argument is a YAML document (not a file) - complex_args = utils_vars.combine_vars(complex_args, loader.load(args)) - args='' - - if args: - parsed_args = parse_kv(args) - complex_args = utils_vars.combine_vars(complex_args, parsed_args) - - task_vars = interpreters - - if check: - complex_args['_ansible_check_mode'] = True - - modname = os.path.basename(modfile) - modname = os.path.splitext(modname)[0] - (module_data, module_style, shebang) = module_common.modify_module( - modname, - modfile, - complex_args, - Templar(loader=loader), - task_vars=task_vars - ) - - if module_style == 'new' and '_ANSIBALLZ_WRAPPER = True' in to_native(module_data): - module_style = 'ansiballz' - - modfile2_path = os.path.expanduser(destfile) - print("* including generated source, if any, saving to: %s" % modfile2_path) - if module_style not in ('ansiballz', 'old'): - print("* this may offset any line numbers in tracebacks/debuggers!") - modfile2 = open(modfile2_path, 'wb') - modfile2.write(module_data) - modfile2.close() - modfile = modfile2_path - - return (modfile2_path, modname, module_style) - - -def ansiballz_setup(modfile, modname, interpreters): - os.system("chmod +x %s" % modfile) - - if 'ansible_python_interpreter' in interpreters: - command = [interpreters['ansible_python_interpreter']] - else: - command = [] - command.extend([modfile, 'explode']) - - cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = cmd.communicate() - out, err = to_text(out, errors='surrogate_or_strict'), to_text(err) - lines = out.splitlines() - if len(lines) != 2 or 'Module expanded into' not in lines[0]: - print("*" * 35) - print("INVALID OUTPUT FROM ANSIBALLZ MODULE WRAPPER") - print(out) - sys.exit(err) - debug_dir = lines[1].strip() - - argsfile = os.path.join(debug_dir, 'args') - modfile = os.path.join(debug_dir, '__main__.py') - - print("* ansiballz module detected; extracted module source to: %s" % debug_dir) - return modfile, argsfile - - -def runtest(modfile, argspath, modname, module_style, interpreters): - """Test run a module, piping it's output for reporting.""" - invoke = "" - if module_style == 'ansiballz': - modfile, argspath = ansiballz_setup(modfile, modname, interpreters) - if 'ansible_python_interpreter' in interpreters: - invoke = "%s " % interpreters['ansible_python_interpreter'] - - os.system("chmod +x %s" % modfile) - - invoke = "%s%s" % (invoke, modfile) - if argspath is not None: - invoke = "%s %s" % (invoke, argspath) - - cmd = subprocess.Popen(invoke, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - out, err = to_text(out), to_text(err) - - try: - print("*" * 35) - print("RAW OUTPUT") - print(out) - print(err) - results = json.loads(out) - except Exception: - print("*" * 35) - print("INVALID OUTPUT FORMAT") - print(out) - traceback.print_exc() - sys.exit(1) - - print("*" * 35) - print("PARSED OUTPUT") - print(jsonify(results,format=True)) - - -def rundebug(debugger, modfile, argspath, modname, module_style, interpreters): - """Run interactively with console debugger.""" - - if module_style == 'ansiballz': - modfile, argspath = ansiballz_setup(modfile, modname, interpreters) - - 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() - interpreters = get_interpreters(options.interpreter) - (modfile, modname, module_style) = boilerplate_module(options.module_path, options.module_args, interpreters, options.check, options.filename) - - argspath = None - if module_style not in ('new', 'ansiballz'): - if module_style in ('non_native_want_json', 'binary'): - argspath = write_argsfile(options.module_args, json=True) - elif module_style == 'old': - argspath = write_argsfile(options.module_args, json=False) - else: - raise Exception("internal error, unexpected module style: %s" % module_style) - - if options.execute: - if options.debugger: - rundebug(options.debugger, modfile, argspath, modname, module_style, interpreters) - else: - runtest(modfile, argspath, modname, module_style, interpreters) - -if __name__ == "__main__": - try: - main() - finally: - shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) diff --git a/hacking/test-module b/hacking/test-module new file mode 120000 index 0000000000..1deb52b467 --- /dev/null +++ b/hacking/test-module @@ -0,0 +1 @@ +test-module.py \ No newline at end of file diff --git a/hacking/test-module.py b/hacking/test-module.py new file mode 100755 index 0000000000..72b387cc92 --- /dev/null +++ b/hacking/test-module.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python + +# (c) 2012, Michael DeHaan +# +# 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 . +# + +# this script is for testing modules without running through the +# entire guts of ansible, and is very helpful for when developing +# modules +# +# example: +# ./hacking/test-module.py -m lib/ansible/modules/commands/command.py -a "/bin/sleep 3" +# ./hacking/test-module.py -m lib/ansible/modules/commands/command.py -a "/bin/sleep 3" --debugger /usr/bin/pdb +# ./hacking/test-module.py -m lib/ansible/modules/files/lineinfile.py -a "dest=/etc/exports line='/srv/home hostname1(rw,sync)'" --check +# ./hacking/test-module.py -m lib/ansible/modules/commands/command.py -a "echo hello" -n -o "test_hello" + +import optparse +import os +import subprocess +import sys +import traceback +import shutil + +from ansible.release import __version__ +import ansible.utils.vars as utils_vars +from ansible.parsing.dataloader import DataLoader +from ansible.parsing.utils.jsonify import jsonify +from ansible.parsing.splitter import parse_kv +import ansible.executor.module_common as module_common +import ansible.constants as C +from ansible.module_utils._text import to_native, to_text +from ansible.template import Templar + +import 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)") + parser.add_option('-I', '--interpreter', dest='interpreter', + help="path to interpreter to use for this module" + " (e.g. ansible_python_interpreter=/usr/bin/python)", + metavar='INTERPRETER_TYPE=INTERPRETER_PATH', + default="ansible_python_interpreter=%s" % + (sys.executable if sys.executable else '/usr/bin/python')) + parser.add_option('-c', '--check', dest='check', action='store_true', + help="run the module in check mode") + parser.add_option('-n', '--noexecute', dest='execute', action='store_false', + default=True, help="do not run the resulting module") + parser.add_option('-o', '--output', dest='filename', + help="Filename for resulting module", + default="~/.ansible_module_generated") + 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 = parse_kv(argstring) + argstring = jsonify(args) + argsfile.write(argstring) + argsfile.close() + return argspath + + +def get_interpreters(interpreter): + result = dict() + if interpreter: + if '=' not in interpreter: + print("interpreter must by in the form of ansible_python_interpreter=/usr/bin/python") + sys.exit(1) + interpreter_type, interpreter_path = interpreter.split('=') + if not interpreter_type.startswith('ansible_'): + interpreter_type = 'ansible_%s' % interpreter_type + if not interpreter_type.endswith('_interpreter'): + interpreter_type = '%s_interpreter' % interpreter_type + result[interpreter_type] = interpreter_path + return result + + +def boilerplate_module(modfile, args, interpreters, check, destfile): + """ simulate what ansible does with new style modules """ + + # module_fh = open(modfile) + # module_data = module_fh.read() + # module_fh.close() + + # replacer = module_common.ModuleReplacer() + loader = DataLoader() + + # included_boilerplate = module_data.find(module_common.REPLACER) != -1 or module_data.find("import ansible.module_utils") != -1 + + complex_args = {} + + # default selinux fs list is pass in as _ansible_selinux_special_fs arg + complex_args['_ansible_selinux_special_fs'] = C.DEFAULT_SELINUX_SPECIAL_FS + complex_args['_ansible_tmpdir'] = C.DEFAULT_LOCAL_TMP + complex_args['_ansible_keep_remote_files'] = C.DEFAULT_KEEP_REMOTE_FILES + complex_args['_ansible_version'] = __version__ + + if args.startswith("@"): + # Argument is a YAML file (JSON is a subset of YAML) + complex_args = utils_vars.combine_vars(complex_args, loader.load_from_file(args[1:])) + args = '' + elif args.startswith("{"): + # Argument is a YAML document (not a file) + complex_args = utils_vars.combine_vars(complex_args, loader.load(args)) + args = '' + + if args: + parsed_args = parse_kv(args) + complex_args = utils_vars.combine_vars(complex_args, parsed_args) + + task_vars = interpreters + + if check: + complex_args['_ansible_check_mode'] = True + + modname = os.path.basename(modfile) + modname = os.path.splitext(modname)[0] + (module_data, module_style, shebang) = module_common.modify_module( + modname, + modfile, + complex_args, + Templar(loader=loader), + task_vars=task_vars + ) + + if module_style == 'new' and '_ANSIBALLZ_WRAPPER = True' in to_native(module_data): + module_style = 'ansiballz' + + modfile2_path = os.path.expanduser(destfile) + print("* including generated source, if any, saving to: %s" % modfile2_path) + if module_style not in ('ansiballz', 'old'): + print("* this may offset any line numbers in tracebacks/debuggers!") + modfile2 = open(modfile2_path, 'wb') + modfile2.write(module_data) + modfile2.close() + modfile = modfile2_path + + return (modfile2_path, modname, module_style) + + +def ansiballz_setup(modfile, modname, interpreters): + os.system("chmod +x %s" % modfile) + + if 'ansible_python_interpreter' in interpreters: + command = [interpreters['ansible_python_interpreter']] + else: + command = [] + command.extend([modfile, 'explode']) + + cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = cmd.communicate() + out, err = to_text(out, errors='surrogate_or_strict'), to_text(err) + lines = out.splitlines() + if len(lines) != 2 or 'Module expanded into' not in lines[0]: + print("*" * 35) + print("INVALID OUTPUT FROM ANSIBALLZ MODULE WRAPPER") + print(out) + sys.exit(err) + debug_dir = lines[1].strip() + + argsfile = os.path.join(debug_dir, 'args') + modfile = os.path.join(debug_dir, '__main__.py') + + print("* ansiballz module detected; extracted module source to: %s" % debug_dir) + return modfile, argsfile + + +def runtest(modfile, argspath, modname, module_style, interpreters): + """Test run a module, piping it's output for reporting.""" + invoke = "" + if module_style == 'ansiballz': + modfile, argspath = ansiballz_setup(modfile, modname, interpreters) + if 'ansible_python_interpreter' in interpreters: + invoke = "%s " % interpreters['ansible_python_interpreter'] + + os.system("chmod +x %s" % modfile) + + invoke = "%s%s" % (invoke, modfile) + if argspath is not None: + invoke = "%s %s" % (invoke, argspath) + + cmd = subprocess.Popen(invoke, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = cmd.communicate() + out, err = to_text(out), to_text(err) + + try: + print("*" * 35) + print("RAW OUTPUT") + print(out) + print(err) + results = json.loads(out) + except Exception: + print("*" * 35) + print("INVALID OUTPUT FORMAT") + print(out) + traceback.print_exc() + sys.exit(1) + + print("*" * 35) + print("PARSED OUTPUT") + print(jsonify(results, format=True)) + + +def rundebug(debugger, modfile, argspath, modname, module_style, interpreters): + """Run interactively with console debugger.""" + + if module_style == 'ansiballz': + modfile, argspath = ansiballz_setup(modfile, modname, interpreters) + + 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() + interpreters = get_interpreters(options.interpreter) + (modfile, modname, module_style) = boilerplate_module(options.module_path, options.module_args, interpreters, options.check, options.filename) + + argspath = None + if module_style not in ('new', 'ansiballz'): + if module_style in ('non_native_want_json', 'binary'): + argspath = write_argsfile(options.module_args, json=True) + elif module_style == 'old': + argspath = write_argsfile(options.module_args, json=False) + else: + raise Exception("internal error, unexpected module style: %s" % module_style) + + if options.execute: + if options.debugger: + rundebug(options.debugger, modfile, argspath, modname, module_style, interpreters) + else: + runtest(modfile, argspath, modname, module_style, interpreters) + + +if __name__ == "__main__": + try: + main() + finally: + shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) diff --git a/lib/ansible/executor/module_common.py b/lib/ansible/executor/module_common.py index 69b409615b..62e815558b 100644 --- a/lib/ansible/executor/module_common.py +++ b/lib/ansible/executor/module_common.py @@ -87,7 +87,7 @@ _MODULE_UTILS_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils ANSIBALLZ_TEMPLATE = u'''%(shebang)s %(coding)s -_ANSIBALLZ_WRAPPER = True # For test-module script to tell this is a ANSIBALLZ_WRAPPER +_ANSIBALLZ_WRAPPER = True # For test-module.py script to tell this is a ANSIBALLZ_WRAPPER # This code is part of Ansible, but is an independent component. # The code in this particular templatable string, and this templatable string # only, is BSD licensed. Modules which end up using this snippet, which is diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 291ccadf96..120f1e2acb 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -2511,9 +2511,9 @@ class AnsibleModule(object): old_env_vals['PATH'] = os.environ['PATH'] os.environ['PATH'] = "%s:%s" % (path_prefix, os.environ['PATH']) - # If using test-module and explode, the remote lib path will resemble ... + # If using test-module.py and explode, the remote lib path will resemble: # /tmp/test_module_scratch/debug_dir/ansible/module_utils/basic.py - # If using ansible or ansible-playbook with a remote system ... + # If using ansible or ansible-playbook with a remote system: # /tmp/ansible_vmweLQ/ansible_modlib.zip/ansible/module_utils/basic.py # Clean out python paths set by ansiballz diff --git a/lib/ansible/modules/system/openwrt_init.py b/lib/ansible/modules/system/openwrt_init.py index 60652ae4d6..4398b3cc0b 100644 --- a/lib/ansible/modules/system/openwrt_init.py +++ b/lib/ansible/modules/system/openwrt_init.py @@ -161,7 +161,7 @@ def main(): lines = psout.split("\n") for line in lines: if module.params['pattern'] in line and "pattern=" not in line: - # so as to not confuse ./hacking/test-module + # so as to not confuse ./hacking/test-module.py running = True break else: diff --git a/lib/ansible/modules/system/service.py b/lib/ansible/modules/system/service.py index c9936e9f74..dc60d16620 100644 --- a/lib/ansible/modules/system/service.py +++ b/lib/ansible/modules/system/service.py @@ -320,7 +320,7 @@ class Service(object): lines = psout.split("\n") for line in lines: if self.pattern in line and "pattern=" not in line: - # so as to not confuse ./hacking/test-module + # so as to not confuse ./hacking/test-module.py self.running = True break diff --git a/test/integration/targets/test_infra/aliases b/test/integration/targets/test_infra/aliases index e3073ace3d..d342950a28 100644 --- a/test/integration/targets/test_infra/aliases +++ b/test/integration/targets/test_infra/aliases @@ -1,3 +1,3 @@ shippable/posix/group3 -needs/file/hacking/test-module +needs/file/hacking/test-module.py needs/file/lib/ansible/modules/system/ping.py diff --git a/test/integration/targets/test_infra/runme.sh b/test/integration/targets/test_infra/runme.sh index 9c86a5d3fd..7996a43140 100755 --- a/test/integration/targets/test_infra/runme.sh +++ b/test/integration/targets/test_infra/runme.sh @@ -26,14 +26,14 @@ set -e PING_MODULE_PATH="../../../../lib/ansible/modules/system/ping.py" -# ensure test-module script works without passing Python interpreter path -../../../../hacking/test-module -m "$PING_MODULE_PATH" +# ensure test-module.py script works without passing Python interpreter path +../../../../hacking/test-module.py -m "$PING_MODULE_PATH" -# ensure test-module script works well -../../../../hacking/test-module -m "$PING_MODULE_PATH" -I ansible_python_interpreter="$(which python)" +# ensure test-module.py script works well +../../../../hacking/test-module.py -m "$PING_MODULE_PATH" -I ansible_python_interpreter="$(which python)" -# ensure module.ansible_version is defined when using test-module -../../../../hacking/test-module -m library/test.py -I ansible_python_interpreter="$(which python)" <<< '{"ANSIBLE_MODULE_ARGS": {}}' +# ensure module.ansible_version is defined when using test-module.py +../../../../hacking/test-module.py -m library/test.py -I ansible_python_interpreter="$(which python)" <<< '{"ANSIBLE_MODULE_ARGS": {}}' # ensure exercising module code locally works python -m ansible.modules.files.file <<< '{"ANSIBLE_MODULE_ARGS": {"path": "/path/to/file", "state": "absent"}}' diff --git a/test/runner/requirements/sanity.txt b/test/runner/requirements/sanity.txt index cda20ef3c9..0ea1bdeda1 100644 --- a/test/runner/requirements/sanity.txt +++ b/test/runner/requirements/sanity.txt @@ -8,7 +8,7 @@ pytest rstcheck ; python_version >= '2.7' # rstcheck requires python 2.7+ sphinx sphinx-notfound-page -straight.plugin # needed for hacking/build-ansible which will host changelog generation +straight.plugin # needed for hacking/build-ansible.py which will host changelog generation virtualenv voluptuous ; python_version >= '2.7' # voluptuous 0.11.0 and later require python 2.7+ yamllint diff --git a/test/sanity/code-smell/shebang.py b/test/sanity/code-smell/shebang.py index 04cfed8a39..9e7d1f136f 100755 --- a/test/sanity/code-smell/shebang.py +++ b/test/sanity/code-smell/shebang.py @@ -39,7 +39,7 @@ def main(): 'test/utils/shippable/timing.py', 'test/integration/targets/old_style_modules_posix/library/helloworld.sh', # The following are Python 3.6+. Only run by release engineers - 'hacking/build-ansible', + 'hacking/build-ansible.py', ]) # see https://unicode.org/faq/utf_bom.html#bom1