Commit Graph

55 Commits

Author SHA1 Message Date
David Wilson ac9b84d237 issue #321: 2.4+ compatibility fixes, disable test on Vanilla. 2018-08-19 19:26:54 +01:00
David Wilson a6995a5288 issue #338: refactor env handling into class and fix tests. 2018-08-18 16:43:59 +01:00
David Wilson b521f215fd ansible: handle >2.6 magic exceptions + sys.excepthook damage
Closes #332.
2018-08-18 16:43:59 +01:00
David Wilson 1473f49505 ansible: emulate /etc/environment reloading behaviour of vanilla.
This change is relatively incomplete -- ideally we could snapshot
os.environ and /etc/environment at startup and respect key deletions
too, but that's a lot more work. Wait for a bug report instead.

Closes #338.
2018-08-18 16:43:59 +01:00
David Wilson 3e0de9790c issue #324: fix Python 3 fallout for custom module_utils.
Also enable at last one of its tests.
2018-07-28 16:21:09 -07:00
David Wilson de0d526487 issue #291: restore behaviour of invoking binaries via /bin/sh
This ensures failed task output matches vanilla Ansible exactly (as it
did before starting #291).
2018-07-23 21:42:20 -07:00
David Wilson 2c74eac19a issue #291: more Ansible-compatible script invocation
When running any kind of script, rewrite the hashbang like Ansible does,
but subsequently ignore it and explicitly use a fragment of shell from
the ansible_*_interpreter variable to call the interpreter, just like
Ansible does.

This fixes hashbangs containing '/usr/bin/env A=1 bash' on Linux, where
putting that into a hashbang line results in an infinite loop.
2018-07-23 17:40:17 -07:00
David Wilson 15d68b3c32 issue #309: fix environment cleanup regression.
Closes #309.
2018-07-19 10:59:18 -04:00
David Wilson 124d8023a2 issue #297: don't bother trying to restore old CWD. 2018-07-10 19:28:12 +01:00
David Wilson d8e0c9e12c issue #297: local commands must execute with WorkerProcess environment. 2018-07-10 18:42:09 +01:00
David Wilson 012745efea issue #297: local actions must execute with fixed directory.
Local actions must execute in the the parent directory of the playbook
that defines the action.
2018-07-10 17:56:05 +01:00
David Wilson 34751c38b0 ansible: cStringIO interprets unicode as a buffer obj on 2.x 2018-07-07 14:55:47 +01:00
David Wilson 410016ff47 Initial Python 3.x port work.
* ansible: use unicode_literals everywhere since it only needs to be
  compatible back to 2.6.
* compat/collections.py: delete this entirely and rip out the parts of
  functools that require it.
* Introduce serializable Kwargs dict subclass that translates keys to
  Unicode on instantiation.
* enable_debug_logging() must set _v/_vv globals.
* cStringIO does not exist in 3.x.
* Treat IOLogger and LogForwarder input as latin-1.
* Avoid ResourceWarnings in first stage by explicitly closing fps.
* Fix preamble_size.py syntax errors.
2018-07-07 14:55:47 +01:00
David Wilson 2fbe1f1b54 Get integration tests running under 2.6.
Closes #270
Closes #273
2018-06-19 18:46:30 +01:00
David Wilson 45b748833d ansible: don't randomly fail due to temp directory cleanup.
Happens about 1 time in 3 when async task times out.
2018-06-10 15:25:11 +01:00
David Wilson e35694acd5 ansible: flake8 fixes. 2018-06-10 01:22:46 +01:00
David Wilson 4bd992e35a issue #186: move module code fetch back to overridden method 2018-06-10 00:59:24 +01:00
David Wilson ae20a689ef issue #186: finally enable detach. 2018-06-10 00:05:24 +01:00
David Wilson caffaa79f7 issue #186: rework async/forked tasks again.
The controller must know the ID of the forked child in order to
propagate dependencies to it, so forking+starting the module run cannot
happen entirely on the target, without some additional mechanism to
wait-and-repropagate the deps as they arrive on the target.

Rework things so that init_child() also handles starting the fork parent,
and returns it along with the context's home directory in a single round
trip.

Now master knows the identity of the fork parent, it can directly create
fork children and call run_module_async() in them. This necessitates 2
roundtrips to start an asynchronous task.

This whole thing sucks and entirely needs simplified, but for now things
almost work, so keeping it.

connection.py:
  * Expect ContextService to return the entire dict return value of
    init_child(). Store the fork_contxt from the return value.

planner.py:
  * Rework Planner to store the invocation as an instance attribute, to
    simplify method calls.
  * Add Planner.get_push_files() and Planner.get_module_deps().
  * Add _propagate_deps() which takes a Planner and ensures the deps it
    describes are sent to a (non forked or forked) context.
  * Move async task logic out of target.py and into invoke() /
    _invoke_*().

process.py:
  * Services no longer need references to each other. planner.py handles
    sending module deps with one extra RPC.

services.py:
  * Return "init_child_result" key instead of simple "home_dir" key.
  * Get rid of dep propagation from ModuleDepService, it lives in
    planner.py now.

target.py:
  * Get rid of async task start logic, lives in planner.py now.
2018-06-09 22:11:26 +01:00
David Wilson 569c12a2d6 ansible: use PushFileService for module deps.
planner.py:
  * Rather than grant FileService access to a file for children, use
    PushFileService to trigger deduplicating send of the file through
    the hierarchy immediately.
  * Send the complete list of Ansible module imports to the target so
    runner.py knows which files and scripts must be loaded via
    PushFileService prior to detaching.

runner.py:
  * Teach NewStyleRunner to use the full module map to block until
    everything is loaded prior to detach().

target.py:
  * Delete old _get_file(), replace get_file() with get_small_file()
    which uses PushFileService instead.

Closes #186
2018-06-07 16:48:42 +01:00
David Wilson 1745c3aff0 issue #186: ansible: detach asynchronous tasks
After Runner.setup() has executed, but before the module executes. This
relies on subsequent commits to ensure all files are preloaded.
2018-06-07 16:31:01 +01:00
David Wilson d9087c510b ansible: move FileService into mitogen.service. 2018-05-29 17:07:58 +01:00
David Wilson a578250bfb ansible: remove indirect master.py imports.
Avoids sending 10 modules:

77d76
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'ansible_mitogen.module_finder')
79d77
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'ansible_mitogen.services')
81,84d78
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.compat')
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.compat.collections')
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.compat.functools')
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.compat.tokenize')
86,87d79
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.master')
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.minify')
89,90d80
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.select')
< _send_load_module(mitogen.ssh.Stream(u'ssh.localhost'), 'mitogen.service')
2018-05-29 13:05:52 +01:00
David Wilson 30034877a5 issue #217: ansible: working, if extremely inefficient implementation 2018-05-13 01:47:11 +01:00
David Wilson 81b62d9a1a issue #217: ansible: beginnings of ModuleDepService. 2018-05-12 15:12:39 +01:00
David Wilson 2ad0d0521d ansible: reload /etc/resolv.conf for each task.
The OpenShift installer modifies /etc/resolv.conf then tests the new
resolver configuration, however, there was no mechanism to reload
resolv.conf in our reuseable interpreter.

https://github.com/openshift/openshift-ansible/blob/release-3.9/roles/openshift_web_console/tasks/install.yml#L137

This inserts an explicit call to res_init() for every new style
invocation, with an approximate cost of ~1usec on Linux since glibc
verifies resolv.conf has changed before reloading it.

There is little to be done for users of the thread-safe resolver APIs,
their state is hidden from us. If bugs like that manifest, whack-a-mole
style 'del sys.modules[thatmod]' patches may suffice.
2018-05-07 23:14:27 +01:00
David Wilson 69f58875c8 tests: more ansible fixes from recent brakage. 2018-05-04 17:55:23 +01:00
David Wilson ad1f624750 ansible: document and rearrange Runner params.
Move emulate_tty to where it's used.
2018-05-04 16:50:38 +01:00
David Wilson 1186b927f9 ansible: remove seemingly unused raw_params
Traced git log all the way back to beginning of time, and checked
Ansible versions starting Jan 2016. Zero clue where this came from, but
the convention suggests it came from Ansible at some point.
2018-05-04 16:35:35 +01:00
David Wilson f737ff5276 ansible: stop passing through remote_tmp variable
Ansiballz does not use remote_tmp so neither should we, per #239.
2018-05-04 15:30:51 +01:00
David Wilson f9e1905ec6 issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.

While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.

Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.

This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.

Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 06:16:25 +01:00
David Wilson 1bc08323bf ansible: more compatible module script naming. 2018-05-04 01:50:48 +01:00
David Wilson 3203846708 issue #239: ansible: ignore remote_tmp in new style runner. 2018-05-01 14:06:09 +01:00
David Wilson 86c9978e09 Ensure mod.__file__ is set; closes #210. 2018-04-22 13:29:50 +01:00
David Wilson 2f02b5c0b1 ansible: prevent Unicode strings leaking into sys.argv
The module name comes from YAML via Jinja2.. it's always Unicode. Mixing
it into a temporary directory name produces a Unicode tempdir name,
which ends up in sys.argv via TemporaryArgv.
2018-04-21 20:26:53 +01:00
David Wilson e7831a801f issue #195: handle non-ASCII scripts in runner.py. 2018-04-20 20:47:07 +01:00
David Wilson 85e1f5f515 ansible: remove JobResultService, more compatible async jobs; closes #191.
And by "compatible" I mean "terrible". This does not implement async job
timeouts, but I'm not going to bother, upstream async implementation is
so buggy and inconsistent it resists even having its behaviour captured
in tests.
2018-04-20 17:02:48 +01:00
David Wilson 117607d742 ansible: fix new-style module argv[0]
Previously it was the absolute path to the module on the master. This is
wrong, it should be the temporary file name on the target.
2018-04-17 15:31:58 +01:00
David Wilson 9eacf8faa9 ansible: forward exceptions as job results too.
Ref #191.
2018-04-17 13:01:50 +01:00
David Wilson 3613162bc0 ansible: enable forking when requested and for async jobs.
Closes #105.
References #155.

mitogen/service.py:
    Refactor services to support individually exposed methods with
    different security policies for each method.

    - @mitogen.service.expose() to expose a method and set its policy
    - @mitogen.service.arg_spec() to validate input.
    - Require basic service message format to be a tuple of
      `(method, kwargs)`, where kwargs is always a dict.
    - Update DeduplicatingService to match the new scheme.

ansible_mitogen/connection.py:
    - Rename 'method' to 'method_name' to disambiguate it from the
      service.call()'s method= argument.

ansible_mitogen/planner.py:
    - Generate an ID for every job, sync or not, and fetch job results
      from JobResultService rather than via the initiating function
      call's return value.
    - Planner subclasses now get to select whether their Runner should
      run in a forked process. The base implementation requests this if
      the 'mitogen_isolation_mode=fork' task variable is present.

ansible_mitogen/runner.py:
    Teach runners to deliver their result via JobResultService executing
    in their indirect parent mux process.

ansible_mitogen/plugins/actions/mitogen_async_status.py:
    Split the implementation up into methods, and more compatibly
    emulate Ansible's existing output.

ansible_mitogen/process.py:
    Mux processes now host JobResultService.

ansible_mitogen/services.py:
    Update existing services to the new mitogen.service scheme, and
    implement JobResultService:

    * listen() method for synchronous jobs. planner.invoke() registers a
      Sender with the service prior to invoking the job, then sleeps
      waiting for the service to write the job result to the
      corresponding Receiver.

    * Non-blocking get() method for implementing mitogen_async_status
      action.

    * Child-accessible push() method for delivering task results.

ansible_mitogen/target.py:
    New helpers for spawning a virginal subprocess on startup, from
    which asynchronous and mitogen_task_isolation=fork jobs are forked.
    Necessary to avoid a task inheriting potentially
    polluted/monkey-patched parent environment, since remaining jobs
    continue to run in the original child process.

docs/ansible.rst:
    Add/merge/remove some behaviours/risks.

tests/ansible/integration:
    New tests for forking/async.
2018-04-09 00:03:09 +01:00
David Wilson 71057c78f9 ansible: rename helpers.py to target.py, to reflect its purpose 2018-04-08 19:34:03 +01:00
David Wilson 6aeb4e9f05 issue #164: precisely emulate Ansible's stdio behaviour.
* Use identical logic to select when stdout/stderr are merged, so
  'stdout', 'stdout_lines', 'stderr', 'stderr_lines' contain the same
  output before/after the extension.

* When stdout/stderr are merged, synthesize carriage returns just like
  the TTY layer.

* Mimic the SSH connection multiplexing message on stderr. Not really
  for user code, but so compare_output_test.sh needs fewer fixups.
2018-04-05 01:16:34 +01:00
David Wilson e0381606af Ensure remote_tmp is respected everywhere.
Logic is still somewhat different from Ansible: we don't have to care
about sudo/non-sudo cases, etc.
2018-04-04 14:05:57 +01:00
David Wilson a731be32a2 ansible: use _ansible_shell_executable in ScriptRunner.
Now we match Ansible error output and exit status. Ansible:

    $ ansible localhost -e end=2 -m custom_binary_single_null
    localhost | FAILED! => {
        "changed": false,
        "module_stderr": "Shared connection to localhost closed.\r\n",
        "module_stdout": "/bin/sh: /Users/dmw/.ansible/tmp/ansible-tmp-1522661797.42-158833651208060/custom_binary_single_null: cannot execute binary file\r\n",
        "msg": "MODULE FAILURE",
        "rc": 126
    }

Mitogen now:

    localhost | FAILED! => {
        "changed": false,
        "module_stderr": "/bin/sh: /var/folders/gw/f6w3dgy16fsg5y4kdthbqycw0000gn/T/ansible_mitogenAYF8LM-binary: cannot execute binary file\n",
        "module_stdout": "",
        "msg": "MODULE FAILURE",
        "rc": 126
    }

Previously:

    localhost | FAILED! => {
        "changed": false,
        "module_stderr": "<type 'exceptions.OSError'>: [Errno 8] Exec format error",
        "module_stdout": "",
        "msg": "MODULE FAILURE",
        "rc": 1
    }
2018-04-02 10:37:23 +01:00
David Wilson 1fab6d9c25 ansible: permit execve() of temporary files on Linux. 2018-04-02 08:59:36 +01:00
David Wilson a5e4a6f346 issue #106: move helpers.get_bytecode() into NewStyleRunner 2018-04-01 21:40:06 +01:00
David Wilson a98a51a328 issue #106: handle JSONARGS modules too. 2018-04-01 21:20:58 +01:00
David Wilson 8cc20856a8 issue #106: support custom new-style modules + 'reexec by default'
Rather than assume any structure about the Python code:

* Delete the exit_json/fail_json monkeypatches.
* Patch SystemExit rather than a magic monkeypatch-thrown exception
* Setup fake cStringIO stdin, stdout, stderr and return those along with
  SystemExit exit status
* Setup _ANSIBLE_ARGS as we used to, since we still want to override
  that with '{}' to prevent accidental import hangs, but also provide
  the same string via sys.stdin.
* Compile the module bytecode once and re-execute it for every
  invocation. May change this back again later, once some benchmarks are
  done.
* Remove the fixups stuff for now, it's handled by ^ above.

Should support any "somewhat new style" Python module, including those
that just give up and dump stuff to stdout directly.
2018-04-01 21:10:04 +01:00
David Wilson a954d54644 issue #106: support old-style too. 2018-04-01 19:01:18 +01:00
David Wilson 16b64392e2 issue #106: support WANT_JSON modules. 2018-04-01 18:19:34 +01:00