#!/usr/bin/python # I am an Ansible new-style Python module. I should receive an encoding string. # See also custom_python_new_style_module, we should be updated in tandem. import io import json import select import signal import sys import warnings # Ansible 2.7 changed how new style modules are invoked. It seems that module # parameters are *sometimes* read before the module runs. Modules that try # to read directly from stdin, such as this, are unable to. However it doesn't # always fail, influences seem to include Ansible & Python version. As noted # in ansible.module_utils.basic._load_params() we should probably use that. # I think (medium confidence) I narrowed the inflection (with git bisect) to # https://github.com/ansible/ansible/commit/52449cc01a71778ef94ea0237eed0284f5d75582 # This is the magic marker Ansible looks for: # from ansible.module_utils. # These timeouts should prevent hard-to-attribute, 2+ hour CI job timeouts. # Previously this module has waited on stdin forever (timeoutInMinutes=120). SELECT_TIMEOUT = 5.0 # seconds SIGNAL_TIMEOUT = 10 # seconds def fail_json(msg, **kwargs): kwargs.update(failed=True, msg=msg) print(json.dumps(kwargs, sys.stdout, indent=2, sort_keys=True)) sys.exit(1) def sigalrm_handler(signum, frame): fail_json("Still executing after SIGNAL_TIMEOUT=%ds" % (SIGNAL_TIMEOUT,)) def usage(): sys.stderr.write('Usage: %s \n' % (sys.argv[0],)) sys.exit(1) # Wait SIGNAL_TIMEOUT seconds, exit with failure if still running. signal.signal(signal.SIGALRM, sigalrm_handler) signal.alarm(SIGNAL_TIMEOUT) # Wait SELECT_TIMEOUT seconds, exit with failure if no data appears on stdin. # TODO Combine select() & read() in a loop, to handle slow trickle of data. # Consider buffering, line buffering, `f.read()` vs `f.read1()`. # TODO Document that sys.stdin may be a StringIO under Ansible + Mitogen. try: inputs_ready, _, _ = select.select([sys.stdin], [], [], SELECT_TIMEOUT) except (AttributeError, TypeError, io.UnsupportedOperation) as exc: # sys.stdin.fileno() doesn't exist or can't return a real file descriptor. warnings.warn("Could not wait on sys.stdin=%r: %r" % (sys.stdin, exc)) else: if not inputs_ready: fail_json("Gave up waiting on sys.stdin after SELECT_TIMEOUT=%ds" % (SELECT_TIMEOUT,)) # Read all data on stdin. May block forever, if EOF is not reached. input_json = sys.stdin.read() print("{") print(" \"changed\": false,") # v2.5.1. apt.py started depending on this. # https://github.com/dw/mitogen/issues/210 print(" \"__file__\": \"%s\"," % (__file__,)) # Python sets this during a regular import. print(" \"__package__\": \"%s\"," % (__package__,)) print(" \"msg\": \"Here is my input\",") print(" \"input\": [%s]" % (input_json,)) print("}") # Ansible since 2.7.0/52449cc01a7 broke __file__ and *requires* the module # process to exit itself. So needless. sys.exit(0)