Compare commits

...

49 Commits

Author SHA1 Message Date
markafarrell 908efa0653
Merge branch 'mitogen-hq:master' into master 1 month ago
Alex Willmer c4cf0d5ba2 ci: Use profile_tasks callback as rough benchmark of Ansible tests 1 month ago
Alex Willmer e1b2f38c8e tox: Add python2 & python3 to adhoc install hint 1 month ago
Alex Willmer 37ebce7e6e Begin 0.3.8dev 1 month ago
Alex Willmer a3644963c4 Prepare v0.3.7 1 month ago
Alex Willmer cca651da1f ansible_mitogen: Ansible 9 (ansible-core 2.16) support 2 months ago
Alex Willmer 45c42d386a tests: Replace uses of ``include:``, unify skipping of mitogen only tests
The tag mitogen_only is only informational for now. It may be possible to use
it with ANSIBLE_SKIP_TAGS in the future.
2 months ago
Alex Willmer fa1d21747f ansible_mitogen: Declare Ansible 8 (ansible-core 2.15) support
refs #1021
2 months ago
Alex Willmer 2333b9aced ci: Exclude docs-master branch 2 months ago
Alexandre Detiste fe54b0ac3f prefer newer unittest.mock from the standad library 2 months ago
Alexandre Detiste 58235e3675 add Python3 compatibility 2 months ago
Alex Willmer 933477fcbe Begin 0.3.7dev 2 months ago
Alex Willmer 5d789faee5 Prepare 0.3.6 2 months ago
Alex Willmer b822f20007 ansible_mitogen: Handle AnsibleUnsafeText et al in Ansible >= 7
Follwing fixes in Ansible 7-9 for CVE-2023-5764 cating `AnsibleUnsafeBytes` &
`AnsibleUnsafeText` to `bytes()` or `str()` requires special handling. The
handling is Ansible specific, so it shouldn't go in the mitogen package but
rather the ansible_mitogen package.

`ansible_mitogen.utils.unsafe.cast()` is most like `mitogen.utils.cast()`.
During development it began as `ansible_mitogen.utils.unsafe.unwrap_var()`,
closer to an inverse of `ansible.utils.unsafe_procy.wrap_var()`. Future
enhancements may move in this direction.

refs #977, refs #1046

See also
- https://github.com/advisories/GHSA-7j69-qfc3-2fq9
- https://github.com/ansible/ansible/pull/82293
- https://github.com/mitogen-hq/mitogen/wiki/AnsibleUnsafe-notes
2 months ago
Alex Willmer 813f253d6b ansible_mitogen: Make ansible_mitogens.utils a package
Prep work for ansible_mitogen.utils.unsafe
2 months ago
Alex Willmer d7979c3597 mitogen: Raise TypeError on `mitogen.utils.cast(custom_str)` failures
If casting a string fails then raise a TypeError. This is potentially an API
breaking change; chosen as the lesser evil vs. allowing silent errors.

`cast()` relies on `bytes(obj)` & `str(obj)` returning the respective
supertype. That's no longer the case for `AnsibleUnsafeBytes` &
`AnsibleUnsafeText`; since fixes/mitigations for  CVE-2023-5764.

fixes #1046, refs #977

See also
- https://github.com/advisories/GHSA-7j69-qfc3-2fq9
- https://github.com/ansible/ansible/pull/82293
2 months ago
Orion Poplawski dfc3c7d516 ansible_mitogen: Add Ansible 7 support
Co-authored-by: Orion Poplawski <orion@nwra.com>
2 months ago
Alex Willmer 21e874e60c
Merge pull request #1047 from mitogen-hq/changlog-pep451
docs: Correct PEP 451 hyperlink
2 months ago
Alex Willmer 50efa53f8f
docs: Correct PEP 451 hyperlink 2 months ago
Alex Willmer a9d32a7708
Merge pull request #1043 from moreati/rel-0.3.5
Prepare 0.3.5 release, start 0.3.6 development
2 months ago
Alex Willmer fc24b3f25e Start v0.3.6 development 2 months ago
Alex Willmer e97ab2f597 Prepare v0.3.5 2 months ago
Alex Willmer a210c37f70
Merge pull request #1032 from moreati/docs-download-url
Python 3.12 support
2 months ago
Alex Willmer 123efa7510 mitogen: Support Python 3.12
Most of the necessary changes were made in recent PEP 451 commits. This bumps
the CI jobs, and declares the support. Test dependendancies are bumped to
latest supportted/available versions.

refs #1033
2 months ago
Alex Willmer fe8a3a71fc ansible_mitogen: Remove use of distutils, which was removed in Python 3.12 2 months ago
Alex Willmer 92c00d913e tests: Skip "discovered python matches invoked" on macOS 11/Python 2.7/Vanilla 2 months ago
Alex Willmer 5ad3d14ceb mitogen: Support PEP 451 ModuleSpec API, required for Python 3.12
importlib.machinery.ModuleSpec and find_spec() were introduced in Python 3.4
under PEP 451. They replace the find_module() API of PEP 302, which was
deprecated from Python 3.4. They were removed in Python 3.12 along with the
imp module.

This change adds support for the PEP 451 APIs. Mitogen should no longer import
imp on Python versions that support ModuleSpec. Tests have been added to cover
the new APIs.

CI jobs have been added to cover Python 3.x on macOS.

Refs #1033
Co-authored-by: Witold Baryluk <witold.baryluk@gmail.com>
2 months ago
Alex Willmer 3a31a7d886 mitogen: Workaround CPython importlib PermissionError when cwd is unreadable
On macOS when using a become plugin as an unprivileged user, to another
unprivileged user it is likely that the current working directory can't be
read. In this case os.cwd() raises PermissionError.

On versions of Python currently in the wild (March 2024, CPython <= 3.13) if
any non-builtin or non-frozen module (e.g. zlib, base64) is imported then
`importlib._bootstrap_external.PathFinder._path_importer_cache()` attempts to
call os.cwd() without catching PermissionError.

The previous comment about needing an extra .encode() appears to be wrong,
atleast for Python 3.x >= 3.6.

Command size increased by 54 bytes, bootstrap by 804 bytes. Changed from
codecs module to binascii & zlib because they're extensions, and importing
them triggers fewer supporting imports (e.g. encodings module).

Before

```
✗ ./preamble_size.py
SSH command size: 705
Bootstrap (mitogen.core) size: 17078 (16.68KiB)

                              Original          Minimized           Compressed
mitogen.parent            97884 95.6KiB  50515 49.3KiB 51.6%  12727 12.4KiB
13.0%
mitogen.fork               8436  8.2KiB   4130  4.0KiB 49.0%   1648  1.6KiB
19.5%
mitogen.ssh               10892 10.6KiB   6952  6.8KiB 63.8%   2113  2.1KiB
19.4%
mitogen.sudo              12089 11.8KiB   5924  5.8KiB 49.0%   2249  2.2KiB
18.6%
mitogen.select            12325 12.0KiB   2929  2.9KiB 23.8%    964  0.9KiB
7.8%
mitogen.service           41699 40.7KiB  22477 22.0KiB 53.9%   5885  5.7KiB
14.1%
mitogen.fakessh           15577 15.2KiB   7989  7.8KiB 51.3%   2623  2.6KiB
16.8%
mitogen.master            51398 50.2KiB  25715 25.1KiB 50.0%   6886  6.7KiB
13.4%
```

After

```
✗ ./preamble_size.py
SSH command size: 759
Bootstrap (mitogen.core) size: 17882 (17.46KiB)

                              Original          Minimized           Compressed
mitogen.parent            98173 95.9KiB  50571 49.4KiB 51.5%  12747 12.4KiB
13.0%
mitogen.fork               8436  8.2KiB   4130  4.0KiB 49.0%   1648  1.6KiB
19.5%
mitogen.ssh               10892 10.6KiB   6952  6.8KiB 63.8%   2113  2.1KiB
19.4%
mitogen.sudo              12089 11.8KiB   5924  5.8KiB 49.0%   2249  2.2KiB
18.6%
mitogen.select            12325 12.0KiB   2929  2.9KiB 23.8%    964  0.9KiB
7.8%
mitogen.service           41699 40.7KiB  22477 22.0KiB 53.9%   5885  5.7KiB
14.1%
mitogen.fakessh           15577 15.2KiB   7989  7.8KiB 51.3%   2623  2.6KiB
16.8%
mitogen.master            56116 54.8KiB  29427 28.7KiB 52.4%   7627  7.4KiB
13.6%
```

Fixes #885
Refs https://github.com/python/cpython/issues/115911
2 months ago
Alex Willmer 1031551dd9 tests: Clarify transport config tests optimisation & correct value
The ini inventory parser doesn't support comments after a value, so the value
parsed was "python3000  # Not expected to exist".
3 months ago
Alex Willmer 2973d90670 tests: Enable su tests under vanilla Ansible >= 2.11
cwd_show was useful when debugging these tests, worth keeping around.
3 months ago
Alex Willmer e2f4d9275c tests: Fix ansible_python_interpreter & discovered_interpreter_python tests on macOS
Should account for fiddling in mitogen.parent.Connection._first_stage() and
symlinks. I won't be surprised if it breaks again soon and often.
3 months ago
Alex Willmer c2ad52e54e tests: Fix tests using get_url across Python versions
Using https:// requires certificate store management and additional parameter
passing that changed across Ansible and Python versions. Using http:// allows
the same tests to be used across wider spans of Python version on the
controller, and Python verison on the targets.

Python 3.12 on a target + get_uri needs Ansible >= 8 (ansible-core >= 2.15).
Python 3.12 removed deprecated httplib.HTTPSConnection() arguments.
https://github.com/ansible/ansible/pull/80751
3 months ago
Alex Willmer a6a5c5bb97 tests: Clarify status/purpose of Python 2.x era Ansible Module workaround 3 months ago
Alex Willmer 2839954559 tests: Account for /tmp symlink in virtualenv test on macOS 3 months ago
Alex Willmer adfd4e17f3 tests: Declare inventory file types to Visual Studio Code and Vim
Works with the VS Code modeline extension. Enables syntax highlighting.
3 months ago
Alex Willmer 591152bef0 tests: Avoid intermittant 2 hour timeout in new style Ansible module tests
This has been lurking for years, raising it's head at unpredictable times.
This change doesn't fix it, but it should make it a lot less mysterious.
3 months ago
Alex Willmer a6c89751f9 tests: Cleanup ansible-lint errors & warnings in user creation playbook
Task " Install slow profile for one account" removed because it duplicates
earlier work.
3 months ago
Alex Willmer 8b574f234d tests: Report Ansible controller parameters before image prep & user creation 3 months ago
Alex Willmer bde7f062b9 tests: Fix Ansible module shebangs
With https://github.com/ansible/ansible/pull/76677 Ansible
fixed shebang substitution for Ansible modules and tightened
up what shebang is allowed.

Changing these fixes the tests using them with vanilla Ansible.

https://docs.ansible.com/ansible/latest/dev_guide/testing/sanity/shebang.html
3 months ago
Alex Willmer 9a9dd66ba0 Ignore Ansible retry files 3 months ago
Alex Willmer fc3e788cb4 non functional: Add comments about imp module removal in Python 3.12 4 months ago
Alex Willmer f9a6748154 ci: Fix Python 2.7 builds on macOS 11
With current macOS 11 runner images (20231216.1) the `python` on `$PATH` is
Python 3.12 and setuptools isn't installed by default. E.g.

```
python -mtox -e "py27-mode_localhost-ansible4"
========================== Starting Command Output ===========================
/bin/bash --noprofile --norc
/Users/runner/work/_temp/93a29c4c-f606-45e4-8dbd-a4a5f51b8730.sh
GLOB sdist-make: /Users/runner/work/1/s/setup.py
ERROR: invocation failed (exit code 1), logfile:
/Users/runner/work/1/s/.tox/log/GLOB-0.log
================================== log start
===================================
Traceback (most recent call last):
  File "/Users/runner/work/1/s/setup.py", line 32, in <module>
    from setuptools import find_packages, setup
ModuleNotFoundError: No module named 'setuptools'
```

Installing setuptools under Python 3.12 chooses package versions incompatible
with Python 2.7. Additionally Mitogen isn't yet compatible with Python 3.12
(#1033), so tests that call a local context with `python` fail.
4 months ago
Alex Willmer b7188c1cad docs: Decouple website download version from package version
This prevents unreleased versions appearing on the website (e.g. 0.3.5.dev0),
but introduces the risk of forgetting to update the website after a release.
A better fix requires deeper design/workflow thought.

refs #1028
6 months ago
Alex Willmer e580258071 docs: Bypass networkgenomics.com/try/ -> PyPI redirect
refs #1028
6 months ago
Alex Willmer 798032b979
Merge pull request #1027 from moreati/pyver-token
ci: Authenticate UsePythonVersion requests to Github
8 months ago
Alex Willmer 3f105d5169 ci: Authenticate UsePythonVersion requests to Github
This should address the warning in Azure Pipelines

> You should provide GitHub token if you want to download a python release.
> Otherwise you may hit the GitHub anonymous download limit.

The token is provided from a secret variable in the pipeline.
8 months ago
Alex Willmer d839cbfaf2
Merge pull request #1026 from moreati/netlify
docs: Fix generating static website on Netlify
8 months ago
Alex Willmer 63457b4866 docs: Update external URLs (e.g. dw/mitogen -> mitogen-hq/mitogen)
Found with sphinx-build -b linkcheck. Not all flagged URLs have been changed,
e.g. Ansible plugins, deleted Github users.
9 months ago
Alex Willmer 6aa4fd3573 docs: Fix generation of static website
Bare minimum syntax errors and requirements constraints to work with Netlify
hosting.
9 months ago

@ -2,6 +2,7 @@
# https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/?view=azure-pipelines&viewFallbackFrom=azure-devops#tool
# `{script: ...}` is shorthand for `{task: CmdLine@<mumble>, inputs: {script: ...}}`.
# The shell is bash.
# https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps-script?view=azure-pipelines
# https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/cmd-line-v2?view=azure-pipelines
@ -9,24 +10,85 @@ steps:
- task: UsePythonVersion@0
displayName: Install python
inputs:
githubToken: '$(GITHUB_PYVER_TOKEN)'
versionSpec: '$(python.version)'
condition: ne(variables['python.version'], '')
- script: |
type python
python --version
displayName: Show python version
set -o errexit
set -o nounset
set -o pipefail
- script: |
sudo apt-get update
sudo apt-get install -y python2-dev python3-pip virtualenv
displayName: Install build deps
condition: and(eq(variables['python.version'], ''), eq(variables['Agent.OS'], 'Linux'))
- script: python -mpip install "tox<4.0"
- script: |
set -o errexit
set -o nounset
set -o pipefail
# macOS builders lack a realpath command
type python && python -c"import os.path;print(os.path.realpath('$(type -p python)'))" && python --version
type python2 && python2 -c"import os.path;print(os.path.realpath('$(type -p python2)'))" && python2 --version
type python3 && python3 -c"import os.path;print(os.path.realpath('$(type -p python3)'))" && python3 --version
echo
if [ -e /usr/bin/python ]; then
echo "/usr/bin/python: sys.executable: $(/usr/bin/python -c 'import sys; print(sys.executable)')"
fi
if [ -e /usr/bin/python2 ]; then
echo "/usr/bin/python2: sys.executable: $(/usr/bin/python2 -c 'import sys; print(sys.executable)')"
fi
if [ -e /usr/bin/python2.7 ]; then
echo "/usr/bin/python2.7: sys.executable: $(/usr/bin/python2.7 -c 'import sys; print(sys.executable)')"
fi
displayName: Show python versions
- script: |
set -o errexit
set -o nounset
set -o pipefail
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "$(tox.env)"))')
if [[ -z $PYTHON ]]; then
echo 1>&2 "Python interpreter could not be determined"
exit 1
fi
if [[ $PYTHON == "python2.7" && $(uname) == "Darwin" ]]; then
"$PYTHON" -m ensurepip --user --altinstall --no-default-pip
"$PYTHON" -m pip install --user -r "tests/requirements-tox.txt"
elif [[ $PYTHON == "python2.7" ]]; then
curl "https://bootstrap.pypa.io/pip/2.7/get-pip.py" --output "get-pip.py"
"$PYTHON" get-pip.py --user --no-python-version-warning
# Avoid Python 2.x pip masking system pip
rm -f ~/.local/bin/{easy_install,pip,wheel}
"$PYTHON" -m pip install --user -r "tests/requirements-tox.txt"
else
"$PYTHON" -m pip install -r "tests/requirements-tox.txt"
fi
displayName: Install tooling
- script: python -mtox -e "$(tox.env)"
- script: |
set -o errexit
set -o nounset
set -o pipefail
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "$(tox.env)"))')
if [[ -z $PYTHON ]]; then
echo 1>&2 "Python interpreter could not be determined"
exit 1
fi
"$PYTHON" -m tox -e "$(tox.env)"
displayName: "Run tests"
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)

@ -8,6 +8,13 @@
#variables:
#ANSIBLE_VERBOSITY: 3
trigger:
branches:
include:
- "*"
exclude:
- docs-master
jobs:
- job: mac11
# vanilla Ansible is really slow
@ -21,25 +28,21 @@ jobs:
matrix:
Mito_27:
tox.env: py27-mode_mitogen
Mito_311:
python.version: '3.11'
tox.env: py311-mode_mitogen
Mito_312:
python.version: '3.12'
tox.env: py312-mode_mitogen
# TODO: test python3, python3 tests are broken
Loc_27_210:
tox.env: py27-mode_localhost-ansible2.10
Loc_27_4:
tox.env: py27-mode_localhost-ansible4
Loc_312_9:
python.version: '3.12'
tox.env: py312-mode_localhost-ansible9
# NOTE: this hangs when ran in Ubuntu 18.04
Van_27_210:
tox.env: py27-mode_localhost-ansible2.10
STRATEGY: linear
ANSIBLE_SKIP_TAGS: resource_intensive
Van_27_4:
tox.env: py27-mode_localhost-ansible4
STRATEGY: linear
ANSIBLE_SKIP_TAGS: resource_intensive
tox.env: py27-mode_localhost-ansible2.10-strategy_linear
Van_312_9:
python.version: '3.12'
tox.env: py312-mode_localhost-ansible9-strategy_linear
- job: Linux
pool:
@ -96,33 +99,33 @@ jobs:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_ubuntu2004
Mito_311_centos6:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_centos6
Mito_311_centos7:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_centos7
Mito_311_centos8:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_centos8
Mito_311_debian9:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_debian9
Mito_311_debian10:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_debian10
Mito_311_debian11:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_debian11
Mito_311_ubuntu1604:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_ubuntu1604
Mito_311_ubuntu1804:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_ubuntu1804
Mito_311_ubuntu2004:
python.version: '3.11'
tox.env: py311-mode_mitogen-distro_ubuntu2004
Mito_312_centos6:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_centos6
Mito_312_centos7:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_centos7
Mito_312_centos8:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_centos8
Mito_312_debian9:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_debian9
Mito_312_debian10:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_debian10
Mito_312_debian11:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_debian11
Mito_312_ubuntu1604:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_ubuntu1604
Mito_312_ubuntu1804:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_ubuntu1804
Mito_312_ubuntu2004:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_ubuntu2004
Ans_27_210:
tox.env: py27-mode_ansible-ansible2.10
@ -148,6 +151,15 @@ jobs:
Ans_311_5:
python.version: '3.11'
tox.env: py311-mode_ansible-ansible5
Ans_311_6:
python.version: '3.11'
tox.env: py311-mode_ansible-ansible6
Ans_312_6:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible6
Ans_312_7:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible7
Ans_312_8:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible8
Ans_312_9:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible9

@ -1,6 +1,10 @@
#!/usr/bin/env python
# Run tests/ansible/all.yml under Ansible and Ansible-Mitogen
from __future__ import print_function
import getpass
import io
import os
import subprocess
import sys
@ -53,6 +57,38 @@ with ci_lib.Fold('machine_prep'):
os.chdir(IMAGE_PREP_DIR)
ci_lib.run("ansible-playbook -c local -i localhost, _user_accounts.yml")
# FIXME Don't hardcode https://github.com/mitogen-hq/mitogen/issues/1022
# and os.environ['USER'] is not populated on Azure macOS runners.
os.chdir(HOSTS_DIR)
with io.open('default.hosts', 'r+', encoding='utf-8') as f:
user = getpass.getuser()
content = f.read()
content = content.replace("{{ lookup('pipe', 'whoami') }}", user)
f.seek(0)
f.write(content)
f.truncate()
ci_lib.dump_file('default.hosts')
cmd = ';'.join([
'from __future__ import print_function',
'import os, sys',
'print(sys.executable, os.path.realpath(sys.executable))',
])
for interpreter in ['/usr/bin/python', '/usr/bin/python2', '/usr/bin/python2.7']:
print(interpreter)
try:
subprocess.call([interpreter, '-c', cmd])
except OSError as exc:
print(exc)
print(interpreter, 'with PYTHON_LAUNCHED_FROM_WRAPPER=1')
environ = os.environ.copy()
environ['PYTHON_LAUNCHED_FROM_WRAPPER'] = '1'
try:
subprocess.call([interpreter, '-c', cmd], env=environ)
except OSError as exc:
print(exc)
with ci_lib.Fold('ansible'):
os.chdir(TESTS_DIR)

1
.gitignore vendored

@ -6,6 +6,7 @@ venvs/**
*.pyc
*.pyd
*.pyo
*.retry
MANIFEST
build/
dist/

@ -43,7 +43,6 @@ import ansible.errors
import ansible.plugins.connection
import mitogen.core
import mitogen.utils
import ansible_mitogen.mixins
import ansible_mitogen.parsing
@ -51,6 +50,7 @@ import ansible_mitogen.process
import ansible_mitogen.services
import ansible_mitogen.target
import ansible_mitogen.transport_config
import ansible_mitogen.utils.unsafe
LOG = logging.getLogger(__name__)
@ -797,7 +797,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
call_context=self.binding.get_service_context(),
service_name='ansible_mitogen.services.ContextService',
method_name='get',
stack=mitogen.utils.cast(list(stack)),
stack=ansible_mitogen.utils.unsafe.cast(list(stack)),
)
except mitogen.core.CallError:
LOG.warning('Connection failed; stack configuration was:\n%s',
@ -848,7 +848,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
inventory_name, stack = self._build_stack()
worker_model = ansible_mitogen.process.get_worker_model()
self.binding = worker_model.get_binding(
mitogen.utils.cast(inventory_name)
ansible_mitogen.utils.unsafe.cast(inventory_name)
)
self._connect_stack(stack)
@ -933,7 +933,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
call_context=binding.get_service_context(),
service_name='ansible_mitogen.services.ContextService',
method_name='reset',
stack=mitogen.utils.cast(list(stack)),
stack=ansible_mitogen.utils.unsafe.cast(list(stack)),
)
finally:
binding.close()
@ -1011,8 +1011,8 @@ class Connection(ansible.plugins.connection.ConnectionBase):
emulate_tty = (not in_data and sudoable)
rc, stdout, stderr = self.get_chain().call(
ansible_mitogen.target.exec_command,
cmd=mitogen.utils.cast(cmd),
in_data=mitogen.utils.cast(in_data),
cmd=ansible_mitogen.utils.unsafe.cast(cmd),
in_data=ansible_mitogen.utils.unsafe.cast(in_data),
chdir=mitogen_chdir or self.get_default_cwd(),
emulate_tty=emulate_tty,
)
@ -1039,7 +1039,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
ansible_mitogen.target.transfer_file(
context=self.context,
# in_path may be AnsibleUnicode
in_path=mitogen.utils.cast(in_path),
in_path=ansible_mitogen.utils.unsafe.cast(in_path),
out_path=out_path
)
@ -1057,7 +1057,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
"""
self.get_chain().call_no_reply(
ansible_mitogen.target.write_path,
mitogen.utils.cast(out_path),
ansible_mitogen.utils.unsafe.cast(out_path),
mitogen.core.Blob(data),
mode=mode,
utimes=utimes,
@ -1119,7 +1119,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
call_context=self.binding.get_service_context(),
service_name='mitogen.service.FileService',
method_name='register',
path=mitogen.utils.cast(in_path)
path=ansible_mitogen.utils.unsafe.cast(in_path)
)
# For now this must remain synchronous, as the action plug-in may have

@ -49,7 +49,7 @@ __all__ = [
ANSIBLE_VERSION_MIN = (2, 10)
ANSIBLE_VERSION_MAX = (2, 13)
ANSIBLE_VERSION_MAX = (2, 16)
NEW_VERSION_MSG = (
"Your Ansible version (%s) is too recent. The most recent version\n"

@ -50,12 +50,12 @@ import ansible.plugins.action
import mitogen.core
import mitogen.select
import mitogen.utils
import ansible_mitogen.connection
import ansible_mitogen.planner
import ansible_mitogen.target
import ansible_mitogen.utils
import ansible_mitogen.utils.unsafe
from ansible.module_utils._text import to_text
@ -187,7 +187,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
LOG.debug('_remote_file_exists(%r)', path)
return self._connection.get_chain().call(
ansible_mitogen.target.file_exists,
mitogen.utils.cast(path)
ansible_mitogen.utils.unsafe.cast(path)
)
def _configure_module(self, module_name, module_args, task_vars=None):
@ -324,7 +324,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
# ~root/.ansible -> /root/.ansible
return self._connection.get_chain(use_login=(not sudoable)).call(
os.path.expanduser,
mitogen.utils.cast(path),
ansible_mitogen.utils.unsafe.cast(path),
)
def get_task_timeout_secs(self):
@ -387,11 +387,11 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
ansible_mitogen.planner.Invocation(
action=self,
connection=self._connection,
module_name=mitogen.core.to_text(module_name),
module_args=mitogen.utils.cast(module_args),
module_name=ansible_mitogen.utils.unsafe.cast(mitogen.core.to_text(module_name)),
module_args=ansible_mitogen.utils.unsafe.cast(module_args),
task_vars=task_vars,
templar=self._templar,
env=mitogen.utils.cast(env),
env=ansible_mitogen.utils.unsafe.cast(env),
wrap_async=wrap_async,
timeout_secs=self.get_task_timeout_secs(),
)

@ -31,15 +31,31 @@ from __future__ import unicode_literals
__metaclass__ = type
import collections
import imp
import logging
import os
import re
import sys
try:
# Python >= 3.4, PEP 451 ModuleSpec API
import importlib.machinery
import importlib.util
except ImportError:
# Python < 3.4, PEP 302 Import Hooks
import imp
import mitogen.master
LOG = logging.getLogger(__name__)
PREFIX = 'ansible.module_utils.'
# Analog of `importlib.machinery.ModuleSpec` or `pkgutil.ModuleInfo`.
# name Unqualified name of the module.
# path Filesystem path of the module.
# kind One of the constants in `imp`, as returned in `imp.find_module()`
# parent `ansible_mitogen.module_finder.Module` of parent package (if any).
Module = collections.namedtuple('Module', 'name path kind parent')
@ -119,14 +135,121 @@ def find_relative(parent, name, path=()):
def scan_fromlist(code):
"""Return an iterator of (level, name) for explicit imports in a code
object.
Not all names identify a module. `from os import name, path` generates
`(0, 'os.name'), (0, 'os.path')`, but `os.name` is usually a string.
>>> src = 'import a; import b.c; from d.e import f; from g import h, i\\n'
>>> code = compile(src, '<str>', 'exec')
>>> list(scan_fromlist(code))
[(0, 'a'), (0, 'b.c'), (0, 'd.e.f'), (0, 'g.h'), (0, 'g.i')]
"""
for level, modname_s, fromlist in mitogen.master.scan_code_imports(code):
for name in fromlist:
yield level, '%s.%s' % (modname_s, name)
yield level, str('%s.%s' % (modname_s, name))
if not fromlist:
yield level, modname_s
def walk_imports(code, prefix=None):
"""Return an iterator of names for implicit parent imports & explicit
imports in a code object.
If a prefix is provided, then only children of that prefix are included.
Not all names identify a module. `from os import name, path` generates
`'os', 'os.name', 'os.path'`, but `os.name` is usually a string.
>>> source = 'import a; import b; import b.c; from b.d import e, f\\n'
>>> code = compile(source, '<str>', 'exec')
>>> list(walk_imports(code))
['a', 'b', 'b', 'b.c', 'b', 'b.d', 'b.d.e', 'b.d.f']
>>> list(walk_imports(code, prefix='b'))
['b.c', 'b.d', 'b.d.e', 'b.d.f']
"""
if prefix is None:
prefix = ''
pattern = re.compile(r'(^|\.)(\w+)')
start = len(prefix)
for _, name, fromlist in mitogen.master.scan_code_imports(code):
if not name.startswith(prefix):
continue
for match in pattern.finditer(name, start):
yield name[:match.end()]
for leaf in fromlist:
yield str('%s.%s' % (name, leaf))
def scan(module_name, module_path, search_path):
# type: (str, str, list[str]) -> list[(str, str, bool)]
"""Return a list of (name, path, is_package) for ansible.module_utils
imports used by an Ansible module.
"""
log = LOG.getChild('scan')
log.debug('%r, %r, %r', module_name, module_path, search_path)
if sys.version_info >= (3, 4):
result = _scan_importlib_find_spec(
module_name, module_path, search_path,
)
log.debug('_scan_importlib_find_spec %r', result)
else:
result = _scan_imp_find_module(module_name, module_path, search_path)
log.debug('_scan_imp_find_module %r', result)
return result
def _scan_importlib_find_spec(module_name, module_path, search_path):
# type: (str, str, list[str]) -> list[(str, str, bool)]
module = importlib.machinery.ModuleSpec(
module_name, loader=None, origin=module_path,
)
prefix = importlib.machinery.ModuleSpec(
PREFIX.rstrip('.'), loader=None,
)
prefix.submodule_search_locations = search_path
queue = collections.deque([module])
specs = {prefix.name: prefix}
while queue:
spec = queue.popleft()
if spec.origin is None:
continue
try:
with open(spec.origin, 'rb') as f:
code = compile(f.read(), spec.name, 'exec')
except Exception as exc:
raise ValueError((exc, module, spec, specs))
for name in walk_imports(code, prefix.name):
if name in specs:
continue
parent_name = name.rpartition('.')[0]
parent = specs[parent_name]
if parent is None or not parent.submodule_search_locations:
specs[name] = None
continue
child = importlib.util._find_spec(
name, parent.submodule_search_locations,
)
if child is None or child.origin is None:
specs[name] = None
continue
specs[name] = child
queue.append(child)
del specs[prefix.name]
return sorted(
(spec.name, spec.origin, spec.submodule_search_locations is not None)
for spec in specs.values() if spec is not None
)
def _scan_imp_find_module(module_name, module_path, search_path):
# type: (str, str, list[str]) -> list[(str, str, bool)]
module = Module(module_name, module_path, imp.PY_SOURCE, None)
stack = [module]
seen = set()

@ -40,7 +40,6 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import atexit
import imp
import json
import os
import re
@ -64,6 +63,14 @@ except ImportError:
# Python 2.4
ctypes = None
try:
# Python >= 3.4, PEP 451 ModuleSpec API
import importlib.machinery
import importlib.util
except ImportError:
# Python < 3.4, PEP 302 Import Hooks
import imp
try:
# Cannot use cStringIO as it does not support Unicode.
from StringIO import StringIO
@ -514,10 +521,71 @@ class ModuleUtilsImporter(object):
sys.modules.pop(fullname, None)
def find_module(self, fullname, path=None):
"""
Return a loader for the module with fullname, if we will load it.
Implements importlib.abc.MetaPathFinder.find_module().
Deprecrated in Python 3.4+, replaced by find_spec().
Raises ImportWarning in Python 3.10+. Removed in Python 3.12.
"""
if fullname in self._by_fullname:
return self
def find_spec(self, fullname, path, target=None):
"""
Return a `ModuleSpec` for module with `fullname` if we will load it.
Otherwise return `None`.
Implements importlib.abc.MetaPathFinder.find_spec(). Python 3.4+.
"""
if fullname.endswith('.'):
return None
try:
module_path, is_package = self._by_fullname[fullname]
except KeyError:
LOG.debug('Skipping %s: not present', fullname)
return None
LOG.debug('Handling %s', fullname)
origin = 'master:%s' % (module_path,)
return importlib.machinery.ModuleSpec(
fullname, loader=self, origin=origin, is_package=is_package,
)
def create_module(self, spec):
"""
Return a module object for the given ModuleSpec.
Implements PEP-451 importlib.abc.Loader API introduced in Python 3.4.
Unlike Loader.load_module() this shouldn't populate sys.modules or
set module attributes. Both are done by Python.
"""
module = types.ModuleType(spec.name)
# FIXME create_module() shouldn't initialise module attributes
module.__file__ = spec.origin
return module
def exec_module(self, module):
"""
Execute the module to initialise it. Don't return anything.
Implements PEP-451 importlib.abc.Loader API, introduced in Python 3.4.
"""
spec = module.__spec__
path, _ = self._by_fullname[spec.name]
source = ansible_mitogen.target.get_small_file(self._context, path)
code = compile(source, path, 'exec', 0, 1)
exec(code, module.__dict__)
self._loaded.add(spec.name)
def load_module(self, fullname):
"""
Return the loaded module specified by fullname.
Implements PEP 302 importlib.abc.Loader.load_module().
Deprecated in Python 3.4+, replaced by create_module() & exec_module().
"""
path, is_pkg = self._by_fullname[fullname]
source = ansible_mitogen.target.get_small_file(self._context, path)
code = compile(source, path, 'exec', 0, 1)
@ -818,12 +886,17 @@ class NewStyleRunner(ScriptRunner):
synchronization mechanism by importing everything the module will need
prior to detaching.
"""
# I think "custom" means "found in custom module_utils search path",
# e.g. playbook relative dir, ~/.ansible/..., Ansible collection.
for fullname, _, _ in self.module_map['custom']:
mitogen.core.import_module(fullname)
# I think "builtin" means "part of ansible/ansible-base/ansible-core",
# as opposed to Python builtin modules such as sys.
for fullname in self.module_map['builtin']:
try:
mitogen.core.import_module(fullname)
except ImportError:
except ImportError as exc:
# #590: Ansible 2.8 module_utils.distro is a package that
# replaces itself in sys.modules with a non-package during
# import. Prior to replacement, it is a real package containing
@ -834,8 +907,18 @@ class NewStyleRunner(ScriptRunner):
# loop progresses to the next entry and attempts to preload
# 'distro._distro', the import mechanism will fail. So here we
# silently ignore any failure for it.
if fullname != 'ansible.module_utils.distro._distro':
raise
if fullname == 'ansible.module_utils.distro._distro':
continue
# ansible.module_utils.compat.selinux raises ImportError if it
# can't load libselinux.so. The importer would usually catch
# this & skip selinux operations. We don't care about selinux,
# we're using import to get a copy of the module.
if (fullname == 'ansible.module_utils.compat.selinux'
and exc.msg == 'unable to load libselinux.so'):
continue
raise
def _setup_excepthook(self):
"""

@ -52,10 +52,10 @@ import ansible.constants
import mitogen.core
import mitogen.service
import mitogen.utils
import ansible_mitogen.loaders
import ansible_mitogen.module_finder
import ansible_mitogen.target
import ansible_mitogen.utils.unsafe
LOG = logging.getLogger(__name__)
@ -91,7 +91,7 @@ def _get_candidate_temp_dirs():
remote_tmp = ansible.constants.DEFAULT_REMOTE_TMP
system_tmpdirs = ('/var/tmp', '/tmp')
return mitogen.utils.cast([remote_tmp] + list(system_tmpdirs))
return ansible_mitogen.utils.unsafe.cast([remote_tmp] + list(system_tmpdirs))
def key_from_dict(**kwargs):

@ -1,14 +0,0 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import distutils.version
import ansible
__all__ = [
'ansible_version',
]
ansible_version = tuple(distutils.version.LooseVersion(ansible.__version__).version)
del distutils
del ansible

@ -0,0 +1,29 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
import ansible
__all__ = [
'ansible_version',
]
def _parse(v_string):
# Adapted from distutils.version.LooseVersion.parse()
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
for component in component_re.split(v_string):
if not component or component == '.':
continue
try:
yield int(component)
except ValueError:
yield component
ansible_version = tuple(_parse(ansible.__version__))
del _parse
del re
del ansible

@ -0,0 +1,79 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import ansible
import ansible.utils.unsafe_proxy
import ansible_mitogen.utils
import mitogen
import mitogen.core
import mitogen.utils
__all__ = [
'cast',
]
def _cast_to_dict(obj): return {cast(k): cast(v) for k, v in obj.items()}
def _cast_to_list(obj): return [cast(v) for v in obj]
def _cast_unsafe(obj): return obj._strip_unsafe()
def _passthrough(obj): return obj
# A dispatch table to cast objects based on their exact type.
# This is an optimisation, reliable fallbacks are required (e.g. isinstance())
_CAST_DISPATCH = {
bytes: bytes,
dict: _cast_to_dict,
list: _cast_to_list,
tuple: _cast_to_list,
mitogen.core.UnicodeType: mitogen.core.UnicodeType,
}
_CAST_DISPATCH.update({t: _passthrough for t in mitogen.utils.PASSTHROUGH})
if hasattr(ansible.utils.unsafe_proxy.AnsibleUnsafeText, '_strip_unsafe'):
_CAST_DISPATCH.update({
ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: _cast_unsafe,
ansible.utils.unsafe_proxy.AnsibleUnsafeText: _cast_unsafe,
ansible.utils.unsafe_proxy.NativeJinjaUnsafeText: _cast_unsafe,
})
elif ansible_mitogen.utils.ansible_version[:2] <= (2, 16):
_CAST_DISPATCH.update({
ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: bytes,
ansible.utils.unsafe_proxy.AnsibleUnsafeText: mitogen.core.UnicodeType,
})
else:
mitogen_ver = '.'.join(str(v) for v in mitogen.__version__)
raise ImportError("Mitogen %s can't unwrap Ansible %s AnsibleUnsafe objects"
% (mitogen_ver, ansible.__version__))
def cast(obj):
"""
Return obj (or a copy) with subtypes of builtins cast to their supertype.
This is an enhanced version of :func:`mitogen.utils.cast`. In addition it
handles ``ansible.utils.unsafe_proxy.AnsibleUnsafeText`` and variants.
There are types handled by :func:`ansible.utils.unsafe_proxy.wrap_var()`
that this function currently does not handle (e.g. `set()`), or preserve
preserve (e.g. `tuple()`). Future enhancements may change this.
:param obj:
Object to undecorate.
:returns:
Undecorated object.
"""
# Fast path: obj is a known type, dispatch directly
try:
unwrapper = _CAST_DISPATCH[type(obj)]
except KeyError:
pass
else:
return unwrapper(obj)
# Slow path: obj is some unknown subclass
if isinstance(obj, dict): return _cast_to_dict(obj)
if isinstance(obj, (list, tuple)): return _cast_to_list(obj)
return mitogen.utils.cast(obj)

@ -1,4 +1,4 @@
<p>
<br>
<a class="github-button" href="https://github.com/dw/mitogen/" data-size="large" data-show-count="true" aria-label="Star dw/mitogen on GitHub">Star</a>
<a class="github-button" href="https://github.com/mitogen-hq/mitogen/" data-size="large" data-show-count="true" aria-label="Star mitogen on GitHub">Star</a>
</p>

@ -18,7 +18,7 @@ The extension is considered stable and real-world use is encouraged.
.. _Ansible: https://www.ansible.com/
.. _Bug reports: https://goo.gl/yLKZiJ
.. _Bug reports: https://github.com/mitogen-hq/mitogen/issues/new/choose
Overview
@ -147,8 +147,27 @@ Noteworthy Differences
* Mitogen 0.2.x supports Ansible 2.3-2.9; with Python 2.6, 2.7, or 3.6.
Mitogen 0.3.1+ supports
- Ansible 2.10, 3, and 4; with Python 2.7, or 3.6-3.11
- Ansible 5 and 6; with Python 3.8-3.11
+-----------------+-----------------+
| Ansible version | Python versions |
+=================+=================+
| 2.10 | |
+-----------------+ |
| 3 | 2.7, 3.6 - 3.11 |
+-----------------+ |
| 4 | |
+-----------------+-----------------+
| 5 | 3.8 - 3.11 |
+-----------------+-----------------+
| 6 | |
+-----------------+ 3.8 - 3.12 |
| 7 | |
+-----------------+-----------------+
| 8 | 3.9 - 3.12 |
+-----------------+-----------------+
| 9 | 3.10 - 3.12 |
+-----------------+-----------------+
Verify your installation is running one of these versions by checking
``ansible --version`` output.
@ -1291,7 +1310,7 @@ Sample Profiles
---------------
The summaries below may be reproduced using data and scripts maintained in the
`pcaps branch <https://github.com/dw/mitogen/tree/pcaps/>`_. Traces were
`pcaps branch <https://github.com/mitogen-hq/mitogen/tree/pcaps/>`_. Traces were
recorded using Ansible 2.5.14.
@ -1300,7 +1319,7 @@ Trivial Loop: Local Host
This demonstrates Mitogen vs. SSH pipelining to the local machine running
`bench/loop-100-items.yml
<https://github.com/dw/mitogen/blob/master/tests/ansible/bench/loop-100-items.yml>`_,
<https://github.com/mitogen-hq/mitogen/blob/master/tests/ansible/bench/loop-100-items.yml>`_,
executing a simple command 100 times. Most Ansible controller overhead is
isolated, characterizing just module executor and connection layer performance.
Mitogen requires **63x less bandwidth and 5.9x less time**.
@ -1328,7 +1347,7 @@ File Transfer: UK to France
~~~~~~~~~~~~~~~~~~~~~~~~~~~
`This playbook
<https://github.com/dw/mitogen/blob/master/tests/ansible/regression/issue_140__thread_pileup.yml>`_
<https://github.com/mitogen-hq/mitogen/blob/master/tests/ansible/regression/issue_140__thread_pileup.yml>`_
was used to compare file transfer performance over a ~26 ms link. It uses the
``with_filetree`` loop syntax to copy a directory of 1,000 0-byte files to the
target.

@ -15,12 +15,42 @@ Release Notes
</style>
To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub <https://github.com/dw/mitogen/>`_.
`directly from GitHub <https://github.com/mitogen-hq/mitogen/>`_.
Unreleased
----------
v0.3.7 (2024-04-08)
-------------------
* :gh:issue:`1021` Support for Ansible 8 (ansible-core 2.15)
* tests: Replace uses of ``include:`` & ``import:``, unsupported in Ansible 9
* :gh:issue:`1053` Support for Ansible 9 (ansible-core 2.16)
v0.3.6 (2024-04-04)
-------------------
* :gh:issue:`974` Support Ansible 7
* :gh:issue:`1046` Raise :py:exc:`TypeError` in :func:`<mitogen.util.cast()>`
when casting a string subtype to `bytes()` or `str()` fails. This is
potentially an API breaking change. Failures previously passed silently.
* :gh:issue:`1046` Add :func:`<ansible_mitogen.util.cast()>`, to cast
:class:`ansible.utils.unsafe_proxy.AnsibleUnsafe` objects in Ansible 7+.
v0.3.5 (2024-03-17)
-------------------
* :gh:issue:`987` Support Python 3.11
* :gh:issue:`885` Fix :py:exc:`PermissionError` in :py:mod:`importlib` when
becoming an unprivileged user with Python 3.x
* :gh:issue:`1033` Support `PEP 451 <https://peps.python.org/pep-0451/>`_,
required by Python 3.12
* :gh:issue:`1033` Support Python 3.12
v0.3.4 (2023-07-02)
@ -71,7 +101,7 @@ v0.3.0 (2021-11-24)
-------------------
This release separates itself from the v0.2.X releases. Ansible's API changed too much to support backwards compatibility so from now on, v0.2.X releases will be for Ansible < 2.10 and v0.3.X will be for Ansible 2.10+.
`See here for details <https://github.com/dw/mitogen/pull/715#issuecomment-750697248>`_.
`See here for details <https://github.com/mitogen-hq/mitogen/pull/715#issuecomment-750697248>`_.
* :gh:issue:`827` NewStylePlanner: detect `ansible_collections` imports
* :gh:issue:`770` better check for supported Ansible version
@ -92,7 +122,7 @@ v0.2.10 (2021-11-24)
* :gh:issue:`756` ssh connections with `check_host_keys='accept'` would
timeout, when using recent OpenSSH client versions.
* :gh:issue:`758` fix initilialisation of callback plugins in test suite, to address a `KeyError` in
:method:`ansible.plugins.callback.CallbackBase.v2_runner_on_start`
:py:meth:`ansible.plugins.callback.CallbackBase.v2_runner_on_start`
* :gh:issue:`775` Test with Python 3.9
* :gh:issue:`775` Add msvcrt to the default module deny list
@ -178,7 +208,7 @@ Mitogen for Ansible
:linux:man7:`unix` sockets across privilege domains.
* :gh:issue:`467`: an incompatibility running Mitogen under `Molecule
<https://molecule.readthedocs.io/en/stable/>`_ was resolved.
<https://ansible.readthedocs.io/projects/molecule/>`_ was resolved.
* :gh:issue:`547`, :gh:issue:`598`: fix a deadlock during initialization of
connections, ``async`` tasks, tasks using custom :mod:`module_utils`,
@ -1230,9 +1260,8 @@ Core Library
parameter may specify an argument vector prefix rather than a string program
path.
* :gh:issue:`300`: the broker could crash on
OS X during shutdown due to scheduled `kqueue
<https://www.freebsd.org/cgi/man.cgi?query=kqueue>`_ filter changes for
* :gh:issue:`300`: the broker could crash on OS X during shutdown due to
scheduled :freebsd:man2:`kqueue` filter changes for
descriptors that were closed before the IO loop resumes. As a temporary
workaround, kqueue's bulk change feature is not used.

@ -1,10 +1,8 @@
import os
import sys
sys.path.append('..')
sys.path.append('.')
import mitogen
VERSION = '%s.%s.%s' % mitogen.__version__
VERSION = '0.3.7'
author = u'Network Genomics'
copyright = u'2021, the Mitogen authors'
@ -44,15 +42,15 @@ version = VERSION
domainrefs = {
'gh:commit': {
'text': '%s',
'url': 'https://github.com/dw/mitogen/commit/%s',
'url': 'https://github.com/mitogen-hq/mitogen/commit/%s',
},
'gh:issue': {
'text': '#%s',
'url': 'https://github.com/dw/mitogen/issues/%s',
'url': 'https://github.com/mitogen-hq/mitogen/issues/%s',
},
'gh:pull': {
'text': '#%s',
'url': 'https://github.com/dw/mitogen/pull/%s',
'url': 'https://github.com/mitogen-hq/mitogen/pull/%s',
},
'ans:mod': {
'text': '%s module',
@ -64,30 +62,36 @@ domainrefs = {
},
'freebsd:man2': {
'text': '%s(2)',
'url': 'https://www.freebsd.org/cgi/man.cgi?query=%s',
'url': 'https://man.freebsd.org/cgi/man.cgi?query=%s',
},
'linux:man1': {
'text': '%s(1)',
'url': 'http://man7.org/linux/man-pages/man1/%s.1.html',
'url': 'https://man7.org/linux/man-pages/man1/%s.1.html',
},
'linux:man2': {
'text': '%s(2)',
'url': 'http://man7.org/linux/man-pages/man2/%s.2.html',
'url': 'https://man7.org/linux/man-pages/man2/%s.2.html',
},
'linux:man3': {
'text': '%s(3)',
'url': 'http://man7.org/linux/man-pages/man3/%s.3.html',
'url': 'https://man7.org/linux/man-pages/man3/%s.3.html',
},
'linux:man7': {
'text': '%s(7)',
'url': 'http://man7.org/linux/man-pages/man7/%s.7.html',
'url': 'https://man7.org/linux/man-pages/man7/%s.7.html',
},
}
# > ## Official guidance
# > Query PyPIs JSON API to determine where to download files from.
# > ## Predictable URLs
# > You can use our conveyor service to fetch this file, which exists for
# > cases where using the API is impractical or impossible.
# > -- https://warehouse.pypa.io/api-reference/integration-guide.html#predictable-urls
rst_epilog = """
.. |mitogen_version| replace:: %(VERSION)s
.. |mitogen_url| replace:: `mitogen-%(VERSION)s.tar.gz <https://networkgenomics.com/try/mitogen-%(VERSION)s.tar.gz>`__
.. |mitogen_url| replace:: `mitogen-%(VERSION)s.tar.gz <https://files.pythonhosted.org/packages/source/m/mitogen/mitogen-%(VERSION)s.tar.gz>`__
""" % locals()

@ -130,6 +130,7 @@ sponsorship and outstanding future-thinking of its early adopters.
<li>luto</li>
<li><a href="https://mayeu.me/">Mayeu a.k.a Matthieu Maury</a></li>
<li><a href="https://twitter.com/nathanhruby">@nathanhruby</a></li>
<li><a href="https://github.com/opoplawski">Orion Poplawski</a></li>
<li><a href="http://pageflows.com/">Ramy</a></li>
<li>Scott Vokes</li>
<li><a href="https://twitter.com/sirtux">Tom Eichhorn</a></li>
@ -138,4 +139,5 @@ sponsorship and outstanding future-thinking of its early adopters.
<li>randy &mdash; <em>desperate for automation</em></li>
<li>Michael & Vicky Twomey-Lee</li>
<li><a href="http://www.wezm.net/">Wesley Moore</a></li>
<li><a href="https://github.com/baryluk">Witold Baryluk</a></li>
</ul>

@ -26,7 +26,7 @@ and efficient low-level API on which tools like `Salt`_, `Ansible`_, or
`Fabric`_ can be built, and while the API is quite friendly and comparable to
`Fabric`_, ultimately it is not intended for direct use by consumer software.
.. _Salt: https://docs.saltstack.com/en/latest/
.. _Salt: https://docs.saltproject.io/en/latest/
.. _Ansible: https://docs.ansible.com/
.. _Fabric: https://www.fabfile.org/

@ -1,3 +1,6 @@
docutils<0.18
Jinja2<3
MarkupSafe<2.1
Sphinx==2.1.2; python_version > '3.0'
sphinxcontrib-programoutput==0.14; python_version > '3.0'
alabaster==0.7.10; python_version > '3.0'

@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup.
#: Library version as a tuple.
__version__ = (0, 3, 5, 'dev0')
__version__ = (0, 3, 8, 'dev')
#: This is :data:`False` in slave contexts. Previously it was used to prevent

@ -34,6 +34,34 @@ non-essential code in order to reduce its size, since it is also serves as the
bootstrap implementation sent to every new slave context.
"""
import sys
try:
import _frozen_importlib_external
except ImportError:
pass
else:
class MonkeyPatchedPathFinder(_frozen_importlib_external.PathFinder):
"""
Meta path finder for sys.path and package __path__ attributes.
Patched for https://github.com/python/cpython/issues/115911.
"""
@classmethod
def _path_importer_cache(cls, path):
if path == '':
try:
path = _frozen_importlib_external._os.getcwd()
except (FileNotFoundError, PermissionError):
return None
return super()._path_importer_cache(path)
if sys.version_info[:2] <= (3, 12):
for i, mpf in enumerate(sys.meta_path):
if mpf is _frozen_importlib_external.PathFinder:
sys.meta_path[i] = MonkeyPatchedPathFinder
del i, mpf
import binascii
import collections
import encodings.latin_1
@ -49,18 +77,22 @@ import pstats
import signal
import socket
import struct
import sys
import syslog
import threading
import time
import traceback
import types
import warnings
import weakref
import zlib
# Python >3.7 deprecated the imp module.
warnings.filterwarnings('ignore', message='the imp module is deprecated')
import imp
try:
# Python >= 3.4, PEP 451 ModuleSpec API
import importlib.machinery
import importlib.util
except ImportError:
# Python < 3.4, PEP 302 Import Hooks
import imp
# Absolute imports for <2.5.
select = __import__('select')
@ -1353,6 +1385,19 @@ class Importer(object):
def __repr__(self):
return 'Importer'
@staticmethod
def _loader_from_module(module, default=None):
"""Return the loader for a module object."""
try:
return module.__spec__.loader
except AttributeError:
pass
try:
return module.__loader__
except AttributeError:
pass
return default
def builtin_find_module(self, fullname):
# imp.find_module() will always succeed for __main__, because it is a
# built-in module. That means it exists on a special linked list deep
@ -1360,12 +1405,19 @@ class Importer(object):
if fullname == '__main__':
raise ModuleNotFoundError()
# For a module inside a package (e.g. pkg_a.mod_b) use the search path
# of that package (e.g. ['/usr/lib/python3.11/site-packages/pkg_a']).
parent, _, modname = str_rpartition(fullname, '.')
if parent:
path = sys.modules[parent].__path__
else:
path = None
# For a top-level module search builtin modules, frozen modules,
# system specific locations (e.g. Windows registry, site-packages).
# Otherwise use search path of the parent package.
# Works for both stdlib modules & third-party modules.
# If the search is unsuccessful then raises ImportError.
fp, pathname, description = imp.find_module(modname, path)
if fp:
fp.close()
@ -1377,8 +1429,9 @@ class Importer(object):
Implements importlib.abc.MetaPathFinder.find_module().
Deprecrated in Python 3.4+, replaced by find_spec().
Raises ImportWarning in Python 3.10+.
Removed in Python 3.12.
fullname A (fully qualified?) module name, e.g. "os.path".
fullname Fully qualified module name, e.g. "os.path".
path __path__ of parent packge. None for a top level module.
"""
if hasattr(_tls, 'running'):
@ -1388,14 +1441,13 @@ class Importer(object):
try:
#_v and self._log.debug('Python requested %r', fullname)
fullname = to_text(fullname)
pkgname, dot, _ = str_rpartition(fullname, '.')
pkgname, _, suffix = str_rpartition(fullname, '.')
pkg = sys.modules.get(pkgname)
if pkgname and getattr(pkg, '__loader__', None) is not self:
self._log.debug('%s is submodule of a locally loaded package',
fullname)
return None
suffix = fullname[len(pkgname+dot):]
if pkgname and suffix not in self._present.get(pkgname, ()):
self._log.debug('%s has no submodule %s', pkgname, suffix)
return None
@ -1415,6 +1467,66 @@ class Importer(object):
finally:
del _tls.running
def find_spec(self, fullname, path, target=None):
"""
Return a `ModuleSpec` for module with `fullname` if we will load it.
Otherwise return `None`, allowing other finders to try.
fullname Fully qualified name of the module (e.g. foo.bar.baz)
path Path entries to search. None for a top-level module.
target Existing module to be reloaded (if any).
Implements importlib.abc.MetaPathFinder.find_spec()
Python 3.4+.
"""
# Presence of _tls.running indicates we've re-invoked importlib.
# Abort early to prevent infinite recursion. See below.
if hasattr(_tls, 'running'):
return None
log = self._log.getChild('find_spec')
if fullname.endswith('.'):
return None
pkgname, _, modname = fullname.rpartition('.')
if pkgname and modname not in self._present.get(pkgname, ()):
log.debug('Skipping %s. Parent %s has no submodule %s',
fullname, pkgname, modname)
return None
pkg = sys.modules.get(pkgname)
pkg_loader = self._loader_from_module(pkg)
if pkgname and pkg_loader is not self:
log.debug('Skipping %s. Parent %s was loaded by %r',
fullname, pkgname, pkg_loader)
return None
# #114: whitelisted prefixes override any system-installed package.
if self.whitelist != ['']:
if any(s and fullname.startswith(s) for s in self.whitelist):
log.debug('Handling %s. It is whitelisted', fullname)
return importlib.machinery.ModuleSpec(fullname, loader=self)
if fullname == '__main__':
log.debug('Handling %s. A special case', fullname)
return importlib.machinery.ModuleSpec(fullname, loader=self)
# Re-invoke the import machinery to allow other finders to try.
# Set a guard, so we don't infinitely recurse. See top of this method.
_tls.running = True
try:
spec = importlib.util._find_spec(fullname, path, target)
finally:
del _tls.running
if spec:
log.debug('Skipping %s. Available as %r', fullname, spec)
return spec
log.debug('Handling %s. Unavailable locally', fullname)
return importlib.machinery.ModuleSpec(fullname, loader=self)
blacklisted_msg = (
'%r is present in the Mitogen importer blacklist, therefore this '
'context will not attempt to request it from the master, as the '
@ -1501,6 +1613,64 @@ class Importer(object):
if present:
callback()
def create_module(self, spec):
"""
Return a module object for the given ModuleSpec.
Implements PEP-451 importlib.abc.Loader API introduced in Python 3.4.
Unlike Loader.load_module() this shouldn't populate sys.modules or
set module attributes. Both are done by Python.
"""
self._log.debug('Creating module for %r', spec)
# FIXME Should this be done in find_spec()? Can it?
self._refuse_imports(spec.name)
# FIXME "create_module() should properly handle the case where it is
# called more than once for the same spec/module." -- PEP-451
event = threading.Event()
self._request_module(spec.name, callback=event.set)
event.wait()
# 0:fullname 1:pkg_present 2:path 3:compressed 4:related
_, pkg_present, path, _, _ = self._cache[spec.name]
if path is None:
raise ImportError(self.absent_msg % (spec.name))
spec.origin = self.get_filename(spec.name)
if pkg_present is not None:
# TODO Namespace packages
spec.submodule_search_locations = []
self._present[spec.name] = pkg_present
module = types.ModuleType(spec.name)
# FIXME create_module() shouldn't initialise module attributes
module.__file__ = spec.origin
return module
def exec_module(self, module):
"""
Execute the module to initialise it. Don't return anything.
Implements PEP-451 importlib.abc.Loader API, introduced in Python 3.4.
"""
name = module.__spec__.name
origin = module.__spec__.origin
self._log.debug('Executing %s from %s', name, origin)
source = self.get_source(name)
try:
# Compile the source into a code object. Don't add any __future__
# flags and don't inherit any from this module.
# FIXME Should probably be exposed as get_code()
code = compile(source, origin, 'exec', flags=0, dont_inherit=True)
except SyntaxError:
# FIXME Why is this LOG, rather than self._log?
LOG.exception('while importing %r', name)
raise
exec(code, module.__dict__)
def load_module(self, fullname):
"""
Return the loaded module specified by fullname.
@ -1516,11 +1686,11 @@ class Importer(object):
self._request_module(fullname, event.set)
event.wait()
ret = self._cache[fullname]
if ret[2] is None:
# 0:fullname 1:pkg_present 2:path 3:compressed 4:related
_, pkg_present, path, _, _ = self._cache[fullname]
if path is None:
raise ModuleNotFoundError(self.absent_msg % (fullname,))
pkg_present = ret[1]
mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
mod.__file__ = self.get_filename(fullname)
mod.__loader__ = self
@ -3921,7 +4091,7 @@ class ExternalContext(object):
def _setup_package(self):
global mitogen
mitogen = imp.new_module('mitogen')
mitogen = types.ModuleType('mitogen')
mitogen.__package__ = 'mitogen'
mitogen.__path__ = []
mitogen.__loader__ = self.importer

@ -37,7 +37,6 @@ contexts.
import dis
import errno
import imp
import inspect
import itertools
import logging
@ -50,6 +49,16 @@ import threading
import types
import zlib
try:
# Python >= 3.4, PEP 451 ModuleSpec API
import importlib.machinery
import importlib.util
from _imp import is_builtin as _is_builtin
except ImportError:
# Python < 3.4, PEP 302 Import Hooks
import imp
from imp import is_builtin as _is_builtin
try:
import sysconfig
except ImportError:
@ -122,14 +131,16 @@ def is_stdlib_name(modname):
"""
Return :data:`True` if `modname` appears to come from the standard library.
"""
# `imp.is_builtin()` isn't a documented as part of Python's stdlib API.
# `(_imp|imp).is_builtin()` isn't a documented part of Python's stdlib.
# Returns 1 if modname names a module that is "builtin" to the the Python
# interpreter (e.g. '_sre'). Otherwise 0 (e.g. 're', 'netifaces').
#
# """
# Main is a little special - imp.is_builtin("__main__") will return False,
# but BuiltinImporter is still the most appropriate initial setting for
# its __loader__ attribute.
# """ -- comment in CPython pylifecycle.c:add_main_module()
if imp.is_builtin(modname) != 0:
if _is_builtin(modname) != 0:
return True
module = sys.modules.get(modname)
@ -460,6 +471,9 @@ class FinderMethod(object):
name according to the running Python interpreter. You'd think this was a
simple task, right? Naive young fellow, welcome to the real world.
"""
def __init__(self):
self.log = LOG.getChild(self.__class__.__name__)
def __repr__(self):
return '%s()' % (type(self).__name__,)
@ -641,7 +655,7 @@ class SysModulesMethod(FinderMethod):
return path, source, is_pkg
class ParentEnumerationMethod(FinderMethod):
class ParentImpEnumerationMethod(FinderMethod):
"""
Attempt to fetch source code by examining the module's (hopefully less
insane) parent package, and if no insane parents exist, simply use
@ -759,6 +773,7 @@ class ParentEnumerationMethod(FinderMethod):
def _find_one_component(self, modname, search_path):
try:
#fp, path, (suffix, _, kind) = imp.find_module(modname, search_path)
# FIXME The imp module was removed in Python 3.12.
return imp.find_module(modname, search_path)
except ImportError:
e = sys.exc_info()[1]
@ -770,6 +785,9 @@ class ParentEnumerationMethod(FinderMethod):
"""
See implementation for a description of how this works.
"""
if sys.version_info >= (3, 4):
return None
#if fullname not in sys.modules:
# Don't attempt this unless a module really exists in sys.modules,
# else we could return junk.
@ -798,6 +816,99 @@ class ParentEnumerationMethod(FinderMethod):
return self._found_module(fullname, path, fp)
class ParentSpecEnumerationMethod(ParentImpEnumerationMethod):
def _find_parent_spec(self, fullname):
#history = []
debug = self.log.debug
children = []
for parent_name, child_name in self._iter_parents(fullname):
children.insert(0, child_name)
if not parent_name:
debug('abandoning %r, reached top-level', fullname)
return None, children
try:
parent = sys.modules[parent_name]
except KeyError:
debug('skipping %r, not in sys.modules', parent_name)
continue
try:
spec = parent.__spec__
except AttributeError:
debug('skipping %r: %r.__spec__ is absent',
parent_name, parent)
continue
if not spec:
debug('skipping %r: %r.__spec__=%r',
parent_name, parent, spec)
continue
if spec.name != parent_name:
debug('skipping %r: %r.__spec__.name=%r does not match',
parent_name, parent, spec.name)
continue
if not spec.submodule_search_locations:
debug('skipping %r: %r.__spec__.submodule_search_locations=%r',
parent_name, parent, spec.submodule_search_locations)
continue
return spec, children
raise ValueError('%s._find_parent_spec(%r) unexpectedly reached bottom'
% (self.__class__.__name__, fullname))
def find(self, fullname):
# Returns absolute path, ParentImpEnumerationMethod returns relative
# >>> spec_pem.find('six_brokenpkg._six')[::2]
# ('/Users/alex/src/mitogen/tests/data/importer/six_brokenpkg/_six.py', False)
if sys.version_info < (3, 4):
return None
fullname = to_text(fullname)
spec, children = self._find_parent_spec(fullname)
for child_name in children:
if spec:
name = '%s.%s' % (spec.name, child_name)
submodule_search_locations = spec.submodule_search_locations
else:
name = child_name
submodule_search_locations = None
spec = importlib.util._find_spec(name, submodule_search_locations)
if spec is None:
self.log.debug('%r spec unavailable from %s', fullname, spec)
return None
is_package = spec.submodule_search_locations is not None
if name != fullname:
if not is_package:
self.log.debug('%r appears to be child of non-package %r',
fullname, spec)
return None
continue
if not spec.has_location:
self.log.debug('%r.origin cannot be read as a file', spec)
return None
if os.path.splitext(spec.origin)[1] != '.py':
self.log.debug('%r.origin does not contain Python source code',
spec)
return None
# FIXME This should use loader.get_source()
with open(spec.origin, 'rb') as f:
source = f.read()
return spec.origin, source, is_package
raise ValueError('%s.find(%r) unexpectedly reached bottom'
% (self.__class__.__name__, fullname))
class ModuleFinder(object):
"""
Given the name of a loaded module, make a best-effort attempt at finding
@ -838,7 +949,8 @@ class ModuleFinder(object):
DefectivePython3xMainMethod(),
PkgutilMethod(),
SysModulesMethod(),
ParentEnumerationMethod(),
ParentSpecEnumerationMethod(),
ParentImpEnumerationMethod(),
]
def get_module_source(self, fullname):

@ -34,7 +34,7 @@ sent to any child context that is due to become a parent, due to recursive
connection.
"""
import codecs
import binascii
import errno
import fcntl
import getpass
@ -1027,7 +1027,7 @@ class KqueuePoller(mitogen.core.Poller):
class EpollPoller(mitogen.core.Poller):
"""
Poller based on the Linux :linux:man2:`epoll` interface.
Poller based on the Linux :linux:man7:`epoll` interface.
"""
SUPPORTED = hasattr(select, 'epoll')
_repr = 'EpollPoller()'
@ -1405,10 +1405,14 @@ class Connection(object):
# file descriptor 0 as 100, creates a pipe, then execs a new interpreter
# with a custom argv.
# * Optimized for minimum byte count after minification & compression.
# The script preamble_size.py measures this.
# * 'CONTEXT_NAME' and 'PREAMBLE_COMPRESSED_LEN' are substituted with
# their respective values.
# * CONTEXT_NAME must be prefixed with the name of the Python binary in
# order to allow virtualenvs to detect their install prefix.
#
# macOS tweaks for Python 2.7 must be kept in sync with the the Ansible
# module test_echo_module, used by the integration tests.
# * macOS <= 10.14 (Darwin <= 18) install an unreliable Python version
# switcher as /usr/bin/python, which introspects argv0. To workaround
# it we redirect attempts to call /usr/bin/python with an explicit
@ -1417,7 +1421,8 @@ class Connection(object):
# do something slightly different. The Python executable is patched to
# perform an extra execvp(). I don't fully understand the details, but
# setting PYTHON_LAUNCHED_FROM_WRAPPER=1 avoids it.
# * macOS 13.x (Darwin 22?) may remove python 2.x entirely.
# * macOS 12.3+ (Darwin 21.4+, Monterey) doesn't ship Python.
# https://developer.apple.com/documentation/macos-release-notes/macos-12_3-release-notes#Python
#
# Locals:
# R: read side of interpreter stdin.
@ -1445,7 +1450,7 @@ class Connection(object):
os.environ['ARGV0']=sys.executable
os.execl(sys.executable,sys.executable+'(mitogen:CONTEXT_NAME)')
os.write(1,'MITO000\n'.encode())
C=_(os.fdopen(0,'rb').read(PREAMBLE_COMPRESSED_LEN),'zip')
C=zlib.decompress(os.fdopen(0,'rb').read(PREAMBLE_COMPRESSED_LEN))
fp=os.fdopen(W,'wb',0)
fp.write(C)
fp.close()
@ -1477,16 +1482,16 @@ class Connection(object):
source = source.replace('PREAMBLE_COMPRESSED_LEN',
str(len(preamble_compressed)))
compressed = zlib.compress(source.encode(), 9)
encoded = codecs.encode(compressed, 'base64').replace(b('\n'), b(''))
# We can't use bytes.decode() in 3.x since it was restricted to always
# return unicode, so codecs.decode() is used instead. In 3.x
# codecs.decode() requires a bytes object. Since we must be compatible
# with 2.4 (no bytes literal), an extra .encode() either returns the
# same str (2.x) or an equivalent bytes (3.x).
encoded = binascii.b2a_base64(compressed).replace(b('\n'), b(''))
# Just enough to decode, decompress, and exec the first stage.
# Priorities: wider compatibility, faster startup, shorter length.
# `import os` here, instead of stage 1, to save a few bytes.
# `sys.path=...` for https://github.com/python/cpython/issues/115911.
return self.get_python_argv() + [
'-c',
'import codecs,os,sys;_=codecs.decode;'
'exec(_(_("%s".encode(),"base64"),"zip"))' % (encoded.decode(),)
'import sys;sys.path=[p for p in sys.path if p];import binascii,os,zlib;'
'exec(zlib.decompress(binascii.a2b_base64("%s")))' % (encoded.decode(),),
]
def get_econtext_config(self):

@ -752,10 +752,12 @@ class PushFileService(Service):
One size fits all method to ensure a target context has been preloaded
with a set of small files and Python modules.
overridden_sources: optional dict containing source code to override path's source code
extra_sys_paths: loads additional sys paths for use in finding modules; beneficial
in situations like loading Ansible Collections because source code
dependencies come from different file paths than where the source lives
:param dict overridden_sources:
Optional dict containing source code to override path's source code
:param extra_sys_paths:
Loads additional sys paths for use in finding modules; beneficial
in situations like loading Ansible Collections because source code
dependencies come from different file paths than where the source lives
"""
for path in paths:
overridden_source = None

@ -190,10 +190,13 @@ PASSTHROUGH = (
def cast(obj):
"""
Return obj (or a copy) with subtypes of builtins cast to their supertype.
Subtypes of those in :data:`PASSTHROUGH` are not modified.
Many tools love to subclass built-in types in order to implement useful
functionality, such as annotating the safety of a Unicode string, or adding
additional methods to a dict. However, cPickle loves to preserve those
subtypes during serialization, resulting in CallError during :meth:`call
additional methods to a dict. However :py:mod:`pickle` serializes these
exactly, leading to :exc:`mitogen.CallError` during :meth:`Context.call
<mitogen.parent.Context.call>` in the target when it tries to deserialize
the data.
@ -201,6 +204,9 @@ def cast(obj):
custom sub-types removed. The functionality is not default since the
resulting walk may be computationally expensive given a large enough graph.
Raises :py:exc:`TypeError` if an unknown subtype is encountered, or
casting does not return the desired supertype.
See :ref:`serialization-rules` for a list of supported types.
:param obj:
@ -215,8 +221,16 @@ def cast(obj):
if isinstance(obj, PASSTHROUGH):
return obj
if isinstance(obj, mitogen.core.UnicodeType):
return mitogen.core.UnicodeType(obj)
return _cast(obj, mitogen.core.UnicodeType)
if isinstance(obj, mitogen.core.BytesType):
return mitogen.core.BytesType(obj)
return _cast(obj, mitogen.core.BytesType)
raise TypeError("Cannot serialize: %r: %r" % (type(obj), obj))
def _cast(obj, desired_type):
result = desired_type(obj)
if type(result) is not desired_type:
raise TypeError("Cast of %r to %r failed, got %r"
% (type(obj), desired_type, type(result)))
return result

@ -6,6 +6,7 @@
# - apt-get source libpam0g
# - cd */po/
# - python ~/pogrep.py "Password: "
from __future__ import print_function
import sys
import shlex
@ -31,7 +32,7 @@ for path in glob.glob('*.po'):
if last_word == 'msgid' and word == 'msgstr':
if last_rest == sys.argv[1]:
thing = rest.rstrip(': ').decode('utf-8').lower().encode('utf-8').encode('base64').rstrip()
print ' %-60s # %s' % (repr(thing)+',', path)
print(' %-60s # %s' % (repr(thing)+',', path))
last_word = word
last_rest = rest

@ -78,6 +78,7 @@ setup(
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: System :: Distributed Computing',
'Topic :: System :: Systems Administration',

@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Show permissions and identities that impact the current working directory.
# On macOS libc cwd() can return EACCES after su or sudo.
# See also
# - https://github.com/ansible/ansible/pull/7078
# - https://github.com/python/cpython/issues/115911
set -o errexit
set -o nounset
set -o pipefail
whoami
groups
pwd
d=$(pwd)
while [[ "$d" != "/" && -n "$d" ]]; do
ls -ld "$d"
d=$(dirname "$d")
done
ls -ld /

@ -1,3 +1,5 @@
# code: language=ini
# vim: syntax=dosini
# become_same_user.yml
bsu-joe ansible_user=joe

@ -1,3 +1,4 @@
# code: language=ini
# vim: syntax=dosini
# Connection delegation scenarios. It's impossible to connect to them, but their would-be

@ -1,9 +1,12 @@
# code: language=ini
# vim: syntax=dosini
# When running the tests outside CI, make a single 'target' host which is the
# local machine. The ansible_user override is necessary since some tests want a
# fixed ansible.cfg remote_user setting to test against.
target ansible_host=localhost ansible_user="{{lookup('env', 'USER')}}"
# FIXME Hardcoded by replacement in some CI runs https://github.com/mitogen-hq/mitogen/issues/1022
# and os.environ['USER'] is not populated on Azure macOS runners.
target ansible_host=localhost ansible_user="{{ lookup('pipe', 'whoami') }}"
[test-targets]
target

@ -1,3 +1,4 @@
# code: language=ini
# vim: syntax=dosini
# Used for manual testing.

@ -1,3 +1,4 @@
# code: language=ini
# vim: syntax=dosini
# issue #511, #536: we must not define an explicit localhost, as some

@ -1,3 +1,6 @@
# code: language=ini
# vim: syntax=dosini
# integration/transport_config
# Hosts with twiddled configs that need to be checked somehow.
@ -17,11 +20,12 @@ tc_remote_user
tc_transport
[transport_config_undiscover:vars]
# If python interpreter path is unset, Ansible tries to connect & discover it.
# That causes approx 10 seconds timeout per task - there's no host to connect to.
# If ansible_*_interpreter isn't set Ansible tries to connect & discover it.
# If that target doesn't exist we must wait $timeout seconds for each attempt.
# Setting a known (invalid) interpreter skips discovery & the many timeouts.
# This optimisation should not be relied in any test.
# Note: tc-python-path-* are intentionally not included.
ansible_python_interpreter = python3000 # Not expected to exist
ansible_python_interpreter = python3000
[tc_transport]
tc-transport-unset

@ -10,8 +10,7 @@
- name: integration/action/make_tmp_path.yml
hosts: test-targets
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
#
# non-root
@ -161,3 +160,4 @@
fail_msg: out={{out}}
tags:
- make_tmp_path
- mitogen_only

@ -25,7 +25,7 @@
sudoable: false
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "Expand ~/foo with become active. ~ is become_user's home."
@ -48,7 +48,7 @@
sudoable: false
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "Expanding $HOME/foo has no effect."
@ -72,7 +72,7 @@
sudoable: true
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "sudoable; Expand ~/foo with become active. ~ is become_user's home."
@ -96,7 +96,7 @@
sudoable: true
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "sudoable; Expanding $HOME/foo has no effect."

@ -32,10 +32,12 @@
- async_out.invocation.module_args.creates == None
- async_out.invocation.module_args.executable == None
- async_out.invocation.module_args.removes == None
# In Ansible 4 (ansible-core 2.11) the warn parameter is deprecated and defaults to false.
# It's scheduled for removal in ansible-core 2.13.
- (ansible_version.full is version("2.11", "<", strict=True) and async_out.invocation.module_args.warn == True)
# | Ansible <= 3 | ansible-core <= 2.10 | present | True |
# | Ansible 4 - 6 | ansible-core 2.11 - 2.13 | deprecated | False |
# | Ansible >= 7 | ansible-core >= 2.14 | absent | n/a |
- (ansible_version.full is version("2.14", ">=", strict=True) and async_out.invocation.module_args.warn is not defined)
or (ansible_version.full is version("2.11", ">=", strict=True) and async_out.invocation.module_args.warn == False)
or (async_out.invocation.module_args.warn == True)
- async_out.rc == 0
- async_out.start.startswith("20")
- async_out.stderr == "there"

@ -3,6 +3,7 @@
- name: integration/async/runner_timeout_then_polling.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
# Verify async-with-timeout-then-poll behaviour.
# This is semi-broken in upstream Ansible, it does not bother to update the
@ -13,7 +14,6 @@
async: 1
poll: 0
register: job
when: is_mitogen
- name: busy-poll up to 500 times
async_status:
@ -22,7 +22,6 @@
until: result.finished
retries: 500
delay: 0
when: is_mitogen
ignore_errors: true
- assert:
@ -31,6 +30,6 @@
- result.finished == 1
- result.msg == "Job reached maximum time limit of 1 seconds."
fail_msg: result={{result}}
when: is_mitogen
tags:
- mitogen_only
- runner_timeout_then_polling

@ -1,5 +1,4 @@
# Verify passwordful su behaviour
# Ansible can't handle this on OS X. I don't care why.
- name: integration/become/su_password.yml
hosts: test-targets
@ -44,20 +43,54 @@
fail_msg: out={{out}}
when: is_mitogen
- name: Ensure password su succeeds.
- name: Ensure password su with chdir succeeds
shell: whoami
args:
chdir: ~mitogen__user1
become: true
become_user: mitogen__user1
register: out
vars:
ansible_become_pass: user1_password
when: is_mitogen
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen
- assert:
that:
- out.stdout == 'mitogen__user1'
fail_msg: out={{out}}
when: is_mitogen
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen
- name: Ensure password su without chdir succeeds
shell: whoami
become: true
become_user: mitogen__user1
register: out
vars:
ansible_become_pass: user1_password
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen
- assert:
that:
- out.stdout == 'mitogen__user1'
fail_msg: out={{out}}
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen
tags:
- su
- su_password

@ -3,6 +3,7 @@
- name: integration/become/sudo_requiretty.yml
hosts: test-targets
tasks:
# - include_tasks: ../_mitogen_only.yml
# TODO: https://github.com/dw/mitogen/issues/692
# - name: Verify we can login to a non-passworded requiretty account
@ -10,12 +11,10 @@
# become: true
# become_user: mitogen__require_tty
# register: out
# when: is_mitogen
# - assert:
# that:
# - out.stdout == 'mitogen__require_tty'
# when: is_mitogen
# ---------------
@ -28,12 +27,11 @@
# vars:
# ansible_become_pass: require_tty_pw_required_password
# register: out
# when: is_mitogen
# - assert:
# that:
# - out.stdout == 'mitogen__require_tty_pw_required'
# when: is_mitogen
tags:
- mitogen_only
- sudo
- sudo_requiretty

@ -5,13 +5,13 @@
hosts: bsu-joe
gather_facts: no
tasks:
- include_tasks: ../_mitogen_only.yml
# bsu-joe's login user is joe, so become should be ignored.
- mitogen_get_stack:
become: true
become_user: joe
register: out
when: is_mitogen
- assert:
that:
@ -19,7 +19,6 @@
- out.result[0].kwargs.username == "joe"
- out.result|length == 1 # no sudo
fail_msg: out={{out}}
when: is_mitogen
# Now try with a different account.
@ -27,7 +26,6 @@
become: true
become_user: james
register: out
when: is_mitogen
- assert:
that:
@ -37,6 +35,6 @@
- out.result[1].kwargs.username == "james"
- out.result|length == 2 # no sudo
fail_msg: out={{out}}
when: is_mitogen
tags:
- become_same_user
- mitogen_only

@ -6,8 +6,7 @@
gather_facts: no
any_errors_fatal: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- name: Run _disconnect_during_module.yml
delegate_to: localhost
@ -32,3 +31,4 @@
tags:
- disconnect
- disconnect_during_module
- mitogen_only

@ -13,8 +13,7 @@
hosts: test-targets
gather_facts: no
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_action_script:
script: |
@ -47,3 +46,4 @@
tags:
- disconnect
- disconnect_resets_connection
- mitogen_only

@ -3,18 +3,18 @@
- name: integration/connection/home_dir.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
- name: "Find out root's homedir."
# Runs first because it blats regular Ansible facts with junk, so
# non-become run fixes that up.
setup:
become: true
register: root_facts
when: is_mitogen
- name: "Find regular homedir"
setup:
register: user_facts
when: is_mitogen
- name: "Verify Connection.homedir correct when become:false"
mitogen_action_script:
@ -24,7 +24,6 @@
"connection homedir": self._connection.homedir,
"homedir from facts": "{{user_facts.ansible_facts.ansible_user_dir}}"
}
when: is_mitogen
- name: "Verify Connection.homedir correct when become:true"
become: true
@ -35,6 +34,6 @@
"connection homedir": self._connection.homedir,
"homedir from facts": "{{root_facts.ansible_facts.ansible_user_dir}}"
}
when: is_mitogen
tags:
- home_dir
- mitogen_only

@ -8,7 +8,7 @@
file_name: large-file
file_size: 512
tasks:
- include: _put_file.yml
- include_tasks: _put_file.yml
tags:
- put_file
- put_large_file

@ -8,7 +8,7 @@
file_name: small-file
file_size: 123
tasks:
- include: _put_file.yml
- include_tasks: _put_file.yml
tags:
- put_file
- put_small_file

@ -6,8 +6,7 @@
- name: integration/connection/reset.yml
hosts: test-targets
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- debug: msg="reset.yml skipped on Ansible<2.5.6"
when:
@ -47,4 +46,5 @@
- out_become.ppid != out_become2.ppid
fail_msg: out={{out}} out2={{out2}} out_become={{out_become}} out_become2={{out_become2}}
tags:
- mitogen_only
- reset

@ -15,8 +15,7 @@
hosts: test-targets
gather_facts: no
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
@ -41,7 +40,7 @@
'keepalive_count': 10,
'password': null,
'port': null,
'python_path': ["/usr/bin/python"],
'python_path': ["{{ ansible_facts.discovered_interpreter_python | default('/usr/bin/python') }}"],
'remote_name': null,
'ssh_args': [
-o, ControlMaster=auto,
@ -69,7 +68,7 @@
'keepalive_count': 10,
'password': null,
'port': null,
'python_path': ["/usr/bin/python"],
'python_path': ["{{ ansible_facts.discovered_interpreter_python | default('/usr/bin/python') }}"],
'remote_name': null,
'ssh_args': [
-o, ControlMaster=auto,
@ -88,3 +87,4 @@
]
tags:
- delegate_to_template
- mitogen_only

@ -3,8 +3,7 @@
- name: integration/connection_delegation/local_action.yml
hosts: cd-newuser-normal-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- local_action: mitogen_get_stack
become: true
@ -35,3 +34,4 @@
]
tags:
- local_action
- mitogen_only

@ -4,8 +4,7 @@
hosts: dtc-container-1
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
@ -28,5 +27,6 @@
},
]
tags:
- mitogen_only
- osa
- osa_container_standalone

@ -6,8 +6,7 @@
target: osa-container-1
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
delegate_to: "{{target}}"
@ -31,5 +30,6 @@
},
]
tags:
- mitogen_only
- osa
- osa_delegate_to_self

@ -19,9 +19,7 @@
- name: integration/connection_delegation/stack_construction.yml
hosts: cd-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
# used later for local_action test.
- local_action: custom_python_detect_environment
register: local_env
@ -31,9 +29,7 @@
- hosts: cd-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -52,14 +48,13 @@
}
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
delegate_to: cd-alias
register: out
@ -96,14 +91,13 @@
},
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-alias
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -139,14 +133,13 @@
},
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-normal-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -193,14 +186,13 @@
},
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-normal-alias
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -269,9 +261,7 @@
- hosts: cd-newuser-normal-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -318,14 +308,13 @@
},
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-newuser-normal-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
delegate_to: cd-alias
register: out
@ -362,14 +351,13 @@
},
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-newuser-normal-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- local_action: mitogen_get_stack
register: out
- assert_equal:
@ -383,14 +371,13 @@
},
]
tags:
- mitogen_only
- stack_construction
- hosts: cd-newuser-doas-normal
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -420,4 +407,5 @@
},
]
tags:
- mitogen_only
- stack_construction

@ -4,8 +4,7 @@
- name: integration/context_service/disconnect_cleanup.yml
hosts: test-targets[0]
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
@ -50,3 +49,4 @@
# that: out.dump|length == play_hosts|length # just the ssh account
tags:
- disconnect_cleanup
- mitogen_only

@ -3,8 +3,7 @@
- name: integration/context_service/remote_name.yml
hosts: test-targets[0]
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
# Too much hassle to make this work for OSX
- meta: end_play
@ -32,4 +31,5 @@
- out.stdout is match('.*python([0-9.]+)?\(mitogen:ansible\)')
fail_msg: out={{out}}
tags:
- mitogen_only
- remote_name

@ -37,6 +37,7 @@
vars:
ansible_python_interpreter: auto
test_echo_module:
facts_copy: "{{ ansible_facts }}"
register: echoout
# can't test this assertion:
@ -44,11 +45,24 @@
# because Mitogen's ansible_python_interpreter is a connection-layer configurable that
# "must be extracted during each task execution to form the complete connection-layer configuration".
# Discovery won't be reran though; the ansible_python_interpreter is read from the cache if already discovered
- assert:
- name: assert discovered python matches invoked python
assert:
that:
- auto_out.ansible_facts.discovered_interpreter_python is defined
- echoout.running_python_interpreter == auto_out.ansible_facts.discovered_interpreter_python
fail_msg: auto_out={{auto_out}} echoout={{echoout}}
- auto_out.ansible_facts.discovered_interpreter_python == echoout.discovered_python.as_seen
- echoout.discovered_python.resolved == echoout.running_python.sys.executable.resolved
fail_msg:
- "auto_out: {{ auto_out }}"
- "echoout: {{ echoout }}"
when:
# On macOS 11 (Darwin 20) CI runners the Python 2.7 binary always
# reports the same path. I can't reach via symlinks.
# >>> sys.executable
# /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
- is_mitogen
or echoout.running_python.sys.version_info.major != 2
or not (echoout.running_python.sys.platform == "darwin"
and echoout.running_python.platform.release.major == 20)
- name: test that auto_legacy gives a dep warning when /usr/bin/python present but != auto result
@ -128,7 +142,8 @@
- name: ensure modules can't set discovered_interpreter_X or ansible_X_interpreter
block:
- test_echo_module:
facts:
facts_copy: "{{ ansible_facts }}"
facts_to_override:
ansible_discovered_interpreter_bogus: from module
discovered_interpreter_bogus: from_module
ansible_bogus_interpreter: from_module
@ -189,13 +204,6 @@
- distro == 'ubuntu'
- distro_version is version('16.04', '>=', strict=True)
- name: mac assertions
assert:
that:
- auto_out.ansible_facts.discovered_interpreter_python == '/usr/bin/python'
fail_msg: auto_out={{auto_out}}
when: os_family == 'Darwin'
always:
- meta: clear_facts
when:

@ -4,6 +4,10 @@
- name: integration/interpreter_discovery/complex_args.yml
hosts: test-targets
gather_facts: true
environment:
http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}"
https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}"
no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}"
tasks:
- name: create temp file to source
file:
@ -21,28 +25,24 @@
# special_python: source /tmp/fake && python
- name: set python using sourced file
set_fact:
special_python: source /tmp/fake || true && python
# Avoid 2.x vs 3.x cross-compatiblity issues (that I can't remember the exact details of).
special_python: "source /tmp/fake || true && python{{ ansible_facts.python.version.major }}"
- name: run get_url with specially-sourced python
get_url:
url: https://google.com
# Plain http for wider Ansible & Python version compatibility
url: http://httpbin.org/get
dest: "/tmp/"
mode: 0644
# this url is the build pic from mitogen's github site; some python versions require ssl stuff installed so will disable need to validate certs
validate_certs: no
vars:
ansible_python_interpreter: "{{ special_python }}"
environment:
https_proxy: "{{ lookup('env', 'https_proxy')|default('') }}"
no_proxy: "{{ lookup('env', 'no_proxy')|default('') }}"
- name: run get_url with specially-sourced python including jinja
get_url:
url: https://google.com
# Plain http for wider Ansible & Python version compatibility
url: http://httpbin.org/get
dest: "/tmp/"
mode: 0644
# this url is the build pic from mitogen's github site; some python versions require ssl stuff installed so will disable need to validate certs
validate_certs: no
vars:
ansible_python_interpreter: >
{% if "1" == "1" %}
@ -50,8 +50,5 @@
{% else %}
python
{% endif %}
environment:
https_proxy: "{{ lookup('env', 'https_proxy')|default('') }}"
no_proxy: "{{ lookup('env', 'no_proxy')|default('') }}"
tags:
- complex_args

@ -2,6 +2,10 @@
- name: integration/runner/custom_python_new_style_module.yml
hosts: test-targets
tasks:
# FIXME Without Mitogen Ansible often reads stdin before the module.
# Either don't read directly from stdin, or figure out the cause.
- include_tasks: ../_mitogen_only.yml
- custom_python_new_style_missing_interpreter:
foo: true
with_sequence: start=0 end={{end|default(1)}}
@ -17,3 +21,4 @@
fail_msg: out={{out}}
tags:
- custom_python_new_style_module
- mitogen_only

@ -1,9 +1,9 @@
- name: integration/runner/custom_python_new_style_module.yml
hosts: test-targets
tasks:
# without Mitogen Ansible 2.10 hangs on this play
- meta: end_play
when: not is_mitogen
# FIXME Without Mitogen Ansible often reads stdin before the module.
# Either don't read directly from stdin, or figure out the cause.
- include_tasks: ../_mitogen_only.yml
- custom_python_new_style_module:
foo: true
@ -29,3 +29,4 @@
fail_msg: out={{out}}
tags:
- custom_python_new_style_module
- mitogen_only

@ -1,8 +1,14 @@
# Test functionality of ansible_mitogen.runner.PREHISTORIC_HACK_RE, which
# removes `reload(sys); sys.setdefaultencoding(...)` from an Ansible module
# as it is sent to a target. There are probably very few modules in the wild
# that still do this, if any - reload() is a Python 2.x builtin function.
# issue #555
- name: integration/runner/custom_python_prehistoric_module.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
- custom_python_prehistoric_module:
register: out
@ -11,3 +17,4 @@
fail_msg: out={{out}}
tags:
- custom_python_prehistoric_module
- mitogen_only

@ -6,7 +6,7 @@
hosts: test-targets[0]
gather_facts: true
tasks:
- include: _etc_environment_user.yml
- include_tasks: _etc_environment_user.yml
when: ansible_system == "Linux" and is_mitogen
- include_tasks: _etc_environment_global.yml

@ -1,6 +1,7 @@
- name: integration/runner/forking_active.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
# Verify mitogen_task_isolation=fork triggers forking.
@ -13,21 +14,19 @@
register: fork_proc1
vars:
mitogen_task_isolation: fork
when: is_mitogen
- name: get force-forked process ID again.
custom_python_detect_environment:
register: fork_proc2
vars:
mitogen_task_isolation: fork
when: is_mitogen
- assert:
that:
- fork_proc1.pid != sync_proc1.pid
- fork_proc1.pid != fork_proc2.pid
fail_msg: fork_proc1={{fork_proc1}} sync_proc1={{sync_proc1}} fork_proc2={{fork_proc2}}
when: is_mitogen
tags:
- forking_active
- mitogen_only

@ -2,6 +2,7 @@
- name: integration/runner/forking_correct_parent.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
# Verify mitogen_task_isolation=fork forks from "virginal fork parent", not
# shared interpreter, but only if forking is enabled (e.g. that's never true
@ -14,35 +15,34 @@
self._connection.init_child_result['fork_context'] is not None
)
register: forkmode
when: is_mitogen
- name: get regular process ID.
custom_python_detect_environment:
register: regular_proc
when: is_mitogen
- name: get force-forked process ID again.
custom_python_detect_environment:
register: fork_proc
vars:
mitogen_task_isolation: fork
when: is_mitogen
- assert:
that:
- fork_proc.pid != regular_proc.pid
fail_msg: fork_proc={{fork_proc}} regular_proc={{regular_proc}}
when: is_mitogen
- assert:
that: fork_proc.ppid != regular_proc.pid
fail_msg: fork_proc={{fork_proc}} regular_proc={{regular_proc}}
when: is_mitogen and forkmode.uses_fork
when:
- forkmode.uses_fork
- assert:
that: fork_proc.ppid == regular_proc.pid
fail_msg: fork_proc={{fork_proc}} regular_proc={{regular_proc}}
when: is_mitogen and not forkmode.uses_fork
when:
- not forkmode.uses_fork
tags:
- forking_correct_parent
- mitogen_only

@ -3,22 +3,21 @@
- name: integration/runner/forking_inactive.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
- name: get process ID.
custom_python_detect_environment:
register: sync_proc1
when: is_mitogen
- name: get process ID again.
custom_python_detect_environment:
register: sync_proc2
when: is_mitogen
- assert:
that:
- sync_proc1.pid == sync_proc2.pid
fail_msg: sync_proc1={{sync_proc1}} sync_proc2={{sync_proc2}}
when: is_mitogen
tags:
- forking_inactive
- mitogen_only

@ -6,8 +6,7 @@
vars:
ansible_private_key_file: ~/fakekey
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- mitogen_get_stack:
register: out
@ -20,3 +19,4 @@
fail_msg: out={{out}}
tags:
- config
- mitogen_only

@ -3,6 +3,8 @@
- name: integration/ssh/timeouts.yml
hosts: test-targets
tasks:
- include_tasks: ../_mitogen_only.yml
- name: Cause Ansible connection timeout
connection: local
environment:
@ -19,7 +21,6 @@
chdir: ../..
register: out
ignore_errors: true
when: is_mitogen
- name: Verify connection timeout occurred
assert:
@ -31,6 +32,6 @@
- |
'"msg": "Connection timed out."' in out.stdout
fail_msg: out={{out}}
when: is_mitogen
tags:
- mitogen_only
- timeouts

@ -13,6 +13,8 @@
-o "ControlPath /tmp/mitogen-ansible-test-{{18446744073709551615|random}}"
tasks:
- include_tasks: ../_mitogen_only.yml
- name: ansible_ssh_user, ansible_ssh_pass
shell: >
ANSIBLE_ANY_ERRORS_FATAL=false
@ -28,7 +30,6 @@
args:
chdir: ../..
register: out
when: is_mitogen
- name: ansible_ssh_user, wrong ansible_ssh_pass
shell: >
@ -46,13 +47,11 @@
chdir: ../..
register: out
ignore_errors: true
when: is_mitogen
- assert:
that:
- out.rc == 4 # ansible.executor.task_queue_manager.TaskQueueManager.RUN_UNREACHABLE_HOSTS
fail_msg: out={{out}}
when: is_mitogen
- name: ansible_user, ansible_ssh_pass
@ -70,7 +69,6 @@
args:
chdir: ../..
register: out
when: is_mitogen
- name: ansible_user, wrong ansible_ssh_pass
shell: >
@ -88,13 +86,11 @@
chdir: ../..
register: out
ignore_errors: true
when: is_mitogen
- assert:
that:
- out.rc == 4 # ansible.executor.task_queue_manager.TaskQueueManager.RUN_UNREACHABLE_HOSTS
fail_msg: out={{out}}
when: is_mitogen
- name: ansible_user, ansible_password
@ -112,7 +108,6 @@
args:
chdir: ../..
register: out
when: is_mitogen
- name: ansible_user, wrong ansible_password
shell: >
@ -130,13 +125,11 @@
chdir: ../..
register: out
ignore_errors: true
when: is_mitogen
- assert:
that:
- out.rc == 4 # ansible.executor.task_queue_manager.TaskQueueManager.RUN_UNREACHABLE_HOSTS
fail_msg: out={{out}}
when: is_mitogen
- name: setup ansible_ssh_private_key_file
@ -159,7 +152,6 @@
args:
chdir: ../..
register: out
when: is_mitogen
- name: ansible_user, wrong ansible_ssh_private_key_file
shell: >
@ -177,10 +169,10 @@
chdir: ../..
register: out
ignore_errors: true
when: is_mitogen
- assert:
that:
- out.rc == 4 # ansible.executor.task_queue_manager.TaskQueueManager.RUN_UNREACHABLE_HOSTS
fail_msg: out={{out}}
when: is_mitogen
tags:
- mitogen_only

@ -9,7 +9,7 @@
- command: sudo -n whoami
args:
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
ignore_errors: true
register: sudo_available

@ -3,8 +3,7 @@
hosts: test-targets
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
@ -23,3 +22,4 @@
fail_msg: out={{out}}
tags:
- kubectl
- mitogen_only

@ -3,8 +3,7 @@
hosts: test-targets
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- custom_python_detect_environment:
vars:
@ -19,3 +18,4 @@
fail_msg: out={{out}}
tags:
- lxc
- mitogen_only

@ -3,8 +3,7 @@
hosts: test-targets
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- custom_python_detect_environment:
vars:
@ -19,3 +18,4 @@
fail_msg: out={{out}}
tags:
- lxd
- mitogen_only

@ -3,8 +3,7 @@
hosts: test-targets
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- custom_python_detect_environment:
vars:
@ -22,3 +21,4 @@
fail_msg: out={{out}}
tags:
- mitogen_doas
- mitogen_only

@ -3,8 +3,7 @@
hosts: test-targets
gather_facts: false
tasks:
- meta: end_play
when: not is_mitogen
- include_tasks: ../_mitogen_only.yml
- custom_python_detect_environment:
vars:
@ -23,4 +22,5 @@
left: (out.env.ORIGINAL_ARGV|from_json)[1:9]
right: ['-u', 'root', '-H', '-r', 'somerole', '-t', 'sometype', '--']
tags:
- mitogen_only
- mitogen_sudo

@ -8,10 +8,8 @@
any_errors_fatal: false
connection: local
tasks:
- meta: end_play
when: not is_mitogen
- include: _end_play_if_not_sudo_linux.yml
- include_tasks: ../_mitogen_only.yml
- include_tasks: _end_play_if_not_sudo_linux.yml
- name: Run stub-lxc-info.py
command: |
@ -27,7 +25,7 @@
localhost
args:
chdir: ../..
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
register: result
- assert:
@ -35,3 +33,4 @@
fail_msg: result={{result}}
tags:
- stns_lxc
- mitogen_only

@ -8,10 +8,8 @@
any_errors_fatal: false
connection: local
tasks:
- meta: end_play
when: not is_mitogen
- include: _end_play_if_not_sudo_linux.yml
- include_tasks: ../_mitogen_only.yml
- include_tasks: _end_play_if_not_sudo_linux.yml
- name: Run ansible stub-lxc.py
command: |
@ -27,11 +25,12 @@
localhost
args:
chdir: ../..
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
register: result
- assert:
that: result.rc == 0
fail_msg: result={{result}}
tags:
- mitogen_only
- sens_lxd

@ -1,4 +1,4 @@
- include: kubectl.yml
- include_playbook: kubectl.yml
tags:
- kubectl

@ -5,7 +5,7 @@
- name: integration/transport_config/become.yml
hosts: tc-become-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -13,11 +13,13 @@
- out.result[0].method == "ssh"
- out.result[0].kwargs.username == "ansible-cfg-remote-user"
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-become-unset
vars: {mitogen_via: becomeuser@tc-become-set}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -31,6 +33,8 @@
- out.result[2].method == "ssh"
- out.result[2].kwargs.hostname == "tc-become-unset"
fail_msg: out={{out}}
tags:
- mitogen_only
# Become set.
@ -39,7 +43,7 @@
become: true
become_user: becomeuser
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -49,13 +53,15 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.username == "becomeuser"
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-become-set
vars: {mitogen_via: tc-become-unset}
become: true
become_user: becomeuser
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -70,3 +76,5 @@
- out.result[2].method == "sudo"
- out.result[2].kwargs.username == "becomeuser"
fail_msg: out={{out}}
tags:
- mitogen_only

@ -6,7 +6,7 @@
hosts: tc-become-method-unset
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -14,11 +14,13 @@
- out.result[0].method == "ssh"
- out.result[1].method == "sudo"
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-become-method-unset
vars: {mitogen_via: becomeuser@tc-become-method-su}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -29,6 +31,8 @@
- out.result[2].method == "ssh"
- out.result[2].kwargs.hostname == "tc-become-method-unset"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_method=su
@ -36,7 +40,7 @@
become: true
become_user: becomeuser
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -45,13 +49,15 @@
- out.result[1].method == "su"
- out.result[1].kwargs.username == "becomeuser"
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-become-method-su
vars: {mitogen_via: tc-become-method-unset}
become: true
become_user: becomeuser
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -65,6 +71,8 @@
- out.result[2].method == "su"
- out.result[2].kwargs.username == "becomeuser"
fail_msg: out={{out}}
tags:
- mitogen_only
@ -72,7 +80,7 @@
- hosts: tc-become-method-unset
vars: {mitogen_via: "doas:doasuser@tc-become-method-su"}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -86,3 +94,5 @@
- out.result[2].method == "ssh"
- out.result[2].kwargs.hostname == "tc-become-method-unset"
fail_msg: out={{out}}
tags:
- mitogen_only

@ -6,7 +6,7 @@
hosts: tc-become-pass-unset
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -15,13 +15,15 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.password == None
fail_msg: out={{out}}
tags:
- mitogen_only
# Not set, unbecoming mitogen_via=
- hosts: tc-become-pass-unset
become: true
vars: {mitogen_via: tc-become-pass-password}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -31,13 +33,15 @@
- out.result[2].method == "sudo"
- out.result[2].kwargs.password == None
fail_msg: out={{out}}
tags:
- mitogen_only
# Not set, becoming mitogen_via=
- hosts: tc-become-pass-unset
become: true
vars: {mitogen_via: viapass@tc-become-pass-password}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -49,13 +53,15 @@
- out.result[3].method == "sudo"
- out.result[3].kwargs.password == None
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_password= set.
- hosts: tc-become-pass-password
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -64,6 +70,8 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.password == "apassword"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_password=, via=
@ -71,7 +79,7 @@
vars: {mitogen_via: root@tc-become-pass-pass}
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -83,13 +91,15 @@
- out.result[3].method == "sudo"
- out.result[3].kwargs.password == "apassword"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_pass=
- hosts: tc-become-pass-pass
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -98,6 +108,8 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.password == "apass"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_pass=, via=
@ -105,7 +117,7 @@
vars: {mitogen_via: root@tc-become-pass-password}
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -117,12 +129,14 @@
- out.result[3].method == "sudo"
- out.result[3].kwargs.password == "apass"
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-become-pass-both
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -133,13 +147,15 @@
# to ansible_become_pass over ansible_become_password.
- out.result[1].kwargs.password == "bpass"
fail_msg: out={{out}}
tags:
- mitogen_only
# both, mitogen_via
- hosts: tc-become-pass-unset
vars: {mitogen_via: root@tc-become-pass-both}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -149,3 +165,5 @@
- out.result[1].kwargs.password == "bpass"
- out.result[2].method == "ssh"
fail_msg: out={{out}}
tags:
- mitogen_only

@ -6,7 +6,7 @@
hosts: tc-become-user-unset
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -15,13 +15,15 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.username == "root"
fail_msg: out={{out}}
tags:
- mitogen_only
# Not set, unbecoming mitogen_via=
- hosts: tc-become-user-unset
become: true
vars: {mitogen_via: tc-become-user-set}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -31,13 +33,15 @@
- out.result[2].method == "sudo"
- out.result[2].kwargs.username == "root"
fail_msg: out={{out}}
tags:
- mitogen_only
# Not set, becoming mitogen_via=
- hosts: tc-become-user-unset
become: true
vars: {mitogen_via: viauser@tc-become-user-set}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -49,13 +53,15 @@
- out.result[3].method == "sudo"
- out.result[3].kwargs.username == "root"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_user= set.
- hosts: tc-become-user-set
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -64,6 +70,8 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.username == "ansi-become-user"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_user=, unbecoming via=
@ -71,7 +79,7 @@
vars: {mitogen_via: tc-become-user-unset}
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -85,6 +93,8 @@
- out.result[2].method == "sudo"
- out.result[2].kwargs.username == "ansi-become-user"
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_become_user=, becoming via=
@ -92,7 +102,7 @@
vars: {mitogen_via: "doas:doasuser@tc-become-user-unset"}
become: true
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -109,4 +119,6 @@
- out.result[3].method == "sudo"
- out.result[3].kwargs.username == "ansi-become-user"
fail_msg: out={{out}}
tags:
- mitogen_only

@ -6,16 +6,18 @@
- name: integration/transport_config/password.yml
hosts: tc-password-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
right: "" # actually null, but assert_equal limitation
tags:
- mitogen_only
- hosts: tc-password-unset
vars: {mitogen_via: tc-password-explicit-ssh}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
@ -23,22 +25,26 @@
- assert_equal:
left: out.result[1].kwargs.password
right: ""
tags:
- mitogen_only
# ansible_ssh_user=
- hosts: tc-password-explicit-ssh
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
right: "ansi-ssh-pass"
tags:
- mitogen_only
- hosts: tc-password-explicit-ssh
vars: {mitogen_via: tc-password-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
@ -46,22 +52,26 @@
- assert_equal:
left: out.result[1].kwargs.password
right: "ansi-ssh-pass"
tags:
- mitogen_only
# ansible_user=
- hosts: tc-password-explicit-pass
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
right: "ansi-pass"
tags:
- mitogen_only
- hosts: tc-password-explicit-pass
vars: {mitogen_via: tc-password-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
@ -69,22 +79,26 @@
- assert_equal:
left: out.result[1].kwargs.password
right: "ansi-pass"
tags:
- mitogen_only
# both; ansible_ssh_user= takes precedence according to play_context.py.
- hosts: tc-password-explicit-both
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
right: "c.b.a"
tags:
- mitogen_only
- hosts: tc-password-explicit-both
vars: {mitogen_via: tc-password-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.password
@ -92,3 +106,5 @@
- assert_equal:
left: out.result[1].kwargs.password
right: "c.b.a"
tags:
- mitogen_only

@ -5,7 +5,7 @@
- name: integration/transport_config/port.yml
hosts: tc-port-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -13,12 +13,14 @@
- out.result[0].method == "ssh"
- out.result[0].kwargs.port == None
fail_msg: out={{out}}
tags:
- mitogen_only
# Not set, mitogen_via=
- hosts: tc-port-explicit-ssh
vars: {mitogen_via: tc-port-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -28,11 +30,13 @@
- out.result[1].method == "ssh"
- out.result[1].kwargs.port == 4321
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_ssh_port=
- hosts: tc-port-explicit-ssh
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -40,11 +44,13 @@
- out.result[0].method == "ssh"
- out.result[0].kwargs.port == 4321
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-port-unset
vars: {mitogen_via: tc-port-explicit-ssh}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -54,11 +60,13 @@
- out.result[1].method == "ssh"
- out.result[1].kwargs.port == None
fail_msg: out={{out}}
tags:
- mitogen_only
# ansible_port=
- hosts: tc-port-explicit-port
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -66,11 +74,13 @@
- out.result[0].method == "ssh"
- out.result[0].kwargs.port == 1234
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-port-unset
vars: {mitogen_via: tc-port-explicit-port}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -80,12 +90,14 @@
- out.result[1].method == "ssh"
- out.result[1].kwargs.port == None
fail_msg: out={{out}}
tags:
- mitogen_only
# both, ssh takes precedence
- hosts: tc-port-both
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -93,11 +105,13 @@
- out.result[0].method == "ssh"
- out.result[0].kwargs.port == 1532
fail_msg: out={{out}}
tags:
- mitogen_only
- hosts: tc-port-unset
vars: {mitogen_via: tc-port-both}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert:
that:
@ -107,3 +121,5 @@
- out.result[1].method == "ssh"
- out.result[1].kwargs.port == None
fail_msg: out={{out}}
tags:
- mitogen_only

@ -7,16 +7,18 @@
- name: integration/transport_config/python_path.yml
hosts: tc-python-path-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
right: ["{{out.discovered_interpreter}}"]
tags:
- mitogen_only
- hosts: tc-python-path-hostvar
vars: {mitogen_via: tc-python-path-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@ -24,21 +26,25 @@
- assert_equal:
left: out.result[1].kwargs.python_path
right: ["/hostvar/path/to/python"]
tags:
- mitogen_only
# Non-localhost with explicit ansible_python_interpreter
- hosts: tc-python-path-hostvar
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
right: [/hostvar/path/to/python]
tags:
- mitogen_only
- hosts: tc-python-path-unset
vars: {mitogen_via: tc-python-path-hostvar}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@ -46,21 +52,25 @@
- assert_equal:
left: out.result[1].kwargs.python_path
right: ["{{out.discovered_interpreter}}"]
tags:
- mitogen_only
# Implicit localhost gets ansible_python_interpreter=virtualenv interpreter
- hosts: localhost
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
right: ["{{ansible_playbook_python}}"]
tags:
- mitogen_only
- hosts: tc-python-path-unset
vars: {mitogen_via: localhost}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@ -68,22 +78,26 @@
- assert_equal:
left: out.result[1].kwargs.python_path
right: ["{{out.discovered_interpreter}}"]
tags:
- mitogen_only
# explicit local connections get the same treatment as everything else.
- hosts: tc-python-path-local-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
right: ["{{out.discovered_interpreter}}"]
tags:
- mitogen_only
- hosts: localhost
vars: {mitogen_via: tc-python-path-local-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@ -91,21 +105,25 @@
- assert_equal:
left: out.result[1].kwargs.python_path
right: ["{{ansible_playbook_python}}"]
tags:
- mitogen_only
# explicit local connection with explicit interpreter
- hosts: tc-python-path-local-explicit
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
right: ["/a/b/c"]
tags:
- mitogen_only
- hosts: localhost
vars: {mitogen_via: tc-python-path-local-explicit}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@ -113,3 +131,5 @@
- assert_equal:
left: out.result[1].kwargs.python_path
right: ["{{ansible_playbook_python}}"]
tags:
- mitogen_only

@ -7,16 +7,18 @@
- name: integration/transport_config/remote_addr.yml
hosts: tc-remote-addr-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
right: "tc-remote-addr-unset"
tags:
- mitogen_only
- hosts: tc-remote-addr-unset
vars: {mitogen_via: tc-remote-addr-explicit-ssh}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
@ -24,22 +26,26 @@
- assert_equal:
left: out.result[1].kwargs.hostname
right: "tc-remote-addr-unset"
tags:
- mitogen_only
# ansible_ssh_host=
- hosts: tc-remote-addr-explicit-ssh
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
right: "ansi.ssh.host"
tags:
- mitogen_only
- hosts: tc-remote-addr-explicit-ssh
vars: {mitogen_via: tc-remote-addr-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
@ -47,22 +53,26 @@
- assert_equal:
left: out.result[1].kwargs.hostname
right: "ansi.ssh.host"
tags:
- mitogen_only
# ansible_host=
- hosts: tc-remote-addr-explicit-host
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
right: "ansi.host"
tags:
- mitogen_only
- hosts: tc-remote-addr-explicit-host
vars: {mitogen_via: tc-remote-addr-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
@ -70,22 +80,26 @@
- assert_equal:
left: out.result[1].kwargs.hostname
right: "ansi.host"
tags:
- mitogen_only
# both; ansible_ssh_host= takes precedence according to play_context.py.
- hosts: tc-remote-addr-explicit-both
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
right: "a.b.c"
tags:
- mitogen_only
- hosts: tc-remote-addr-explicit-both
vars: {mitogen_via: tc-remote-addr-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.hostname
@ -93,3 +107,5 @@
- assert_equal:
left: out.result[1].kwargs.hostname
right: "a.b.c"
tags:
- mitogen_only

@ -7,17 +7,19 @@
- name: integration/transport_config/remote_user.yml
hosts: tc-remote-user-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
# We set DEFAULT_REMOTE_USER in our ansible.cfg
right: "ansible-cfg-remote-user"
tags:
- mitogen_only
- hosts: tc-remote-user-unset
vars: {mitogen_via: tc-remote-user-explicit-ssh}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
@ -25,22 +27,26 @@
- assert_equal:
left: out.result[1].kwargs.username
right: "ansible-cfg-remote-user"
tags:
- mitogen_only
# ansible_ssh_user=
- hosts: tc-remote-user-explicit-ssh
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
right: "ansi-ssh-user"
tags:
- mitogen_only
- hosts: tc-remote-user-explicit-ssh
vars: {mitogen_via: tc-remote-user-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
@ -48,13 +54,15 @@
- assert_equal:
left: out.result[1].kwargs.username
right: "ansi-ssh-user"
tags:
- mitogen_only
# ansible_user=
- hosts: tc-remote-user-explicit-user
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
@ -63,7 +71,7 @@
- hosts: tc-remote-user-explicit-user
vars: {mitogen_via: tc-remote-user-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
@ -71,22 +79,26 @@
- assert_equal:
left: out.result[1].kwargs.username
right: "ansi-user"
tags:
- mitogen_only
# both; ansible_ssh_user= takes precedence according to play_context.py.
- hosts: tc-remote-user-explicit-both
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
right: "c.b.a"
tags:
- mitogen_only
- hosts: tc-remote-user-explicit-both
vars: {mitogen_via: tc-remote-user-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.username
@ -94,3 +106,5 @@
- assert_equal:
left: out.result[1].kwargs.username
right: "c.b.a"
tags:
- mitogen_only

@ -6,16 +6,18 @@
- name: integration/transport_config/transport.yml
hosts: tc-transport-unset
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
right: "ssh"
tags:
- mitogen_only
- hosts: tc-transport-local
vars: {mitogen_via: tc-transport-unset}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
@ -23,22 +25,26 @@
- assert_equal:
left: out.result[1].method
right: "local"
tags:
- mitogen_only
# ansible_connection=local
- hosts: tc-transport-local
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
right: "local"
tags:
- mitogen_only
- hosts: tc-transport-unset
vars: {mitogen_via: tc-transport-local}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
@ -46,3 +52,5 @@
- assert_equal:
left: out.result[1].method
right: "ssh"
tags:
- mitogen_only

@ -7,16 +7,18 @@
- name: integration/transport_config/transport__smart.yml
hosts: tc-transport-smart
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
right: "ssh"
tags:
- mitogen_only
- hosts: tc-transport-local
vars: {mitogen_via: tc-transport-smart}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
@ -24,22 +26,26 @@
- assert_equal:
left: out.result[1].method
right: "local"
tags:
- mitogen_only
# ansible_connection=local
- hosts: tc-transport-local
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
right: "local"
tags:
- mitogen_only
- hosts: tc-transport-smart
vars: {mitogen_via: tc-transport-local}
tasks:
- include: ../_mitogen_only.yml
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].method
@ -47,3 +53,5 @@
- assert_equal:
left: out.result[1].method
right: "ssh"
tags:
- mitogen_only

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I am an Ansible new-style Python module. I return details about the Python
# interpreter I run within.
@ -25,6 +25,11 @@ except NameError:
def main():
module = AnsibleModule(argument_spec={})
module.exit_json(
fs={
'/tmp': {
'resolved': os.path.realpath('/tmp'),
},
},
python={
'version': {
'full': '%i.%i.%i' % sys.version_info[:3],

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I expect the quote from modules2/module_utils/joker.py.
from ansible.module_utils.basic import AnsibleModule

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.externalpkg import extmod

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I am an Ansible Python JSONARGS module. I should receive an encoding string.
json_arguments = """<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"""

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I am an Ansible new-style Python module. I leak state from each invocation
# into a class variable and a global variable.

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I am an Ansible new-style Python module. I modify the process environment and
# don't clean up after myself.

@ -1,6 +1,20 @@
# I am an Ansible new-style Python module, but I lack an interpreter.
# 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
# As of Ansible 2.10, Ansible changed new-style detection: # https://github.com/ansible/ansible/pull/61196/files#diff-5675e463b6ce1fbe274e5e7453f83cd71e61091ea211513c93e7c0b4d527d637L828-R980
# NOTE: this import works for Mitogen, and the import below matches new-style Ansible 2.10
@ -8,11 +22,46 @@ import sys
# from ansible.module_utils.
# import 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 <input.json>\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("{")

@ -1,16 +1,65 @@
#!/usr/bin/env python
#!/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 <input.json>\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("{")

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# #591: call os.getcwd() before AnsibleModule ever gets a chance to fix up the
# process environment.

@ -1,4 +1,9 @@
#!/usr/bin/env python
#!/usr/bin/python
# Test functionality of ansible_mitogen.runner.PREHISTORIC_HACK_RE, which
# removes `reload(sys); sys.setdefaultencoding(...)` from an Ansible module
# as it is sent to a target. There are probably very few modules in the wild
# that still do this, reload() is a Python 2.x builtin function.
# issue #555: I'm a module that cutpastes an old hack.
from ansible.module_utils.basic import AnsibleModule

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I am an Ansible new-style Python module. I run the script provided in the
# parameter.

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# issue #590: I am an Ansible new-style Python module that tries to use
# ansible.module_utils.distro.

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python
# I am an Ansible Python WANT_JSON module. I should receive a JSON-encoded file.
import json

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save