Compare commits

...

120 Commits

Author SHA1 Message Date
Alex Willmer 7be79d05e9
Merge pull request #1013 from moreati/issue1011-blacklist-msg
mitogen: Clarify blacklisted import error message
10 hours ago
Alex Willmer ccaaf4b7fe mitogen: Clarify blacklisted ModuleNotFoundError message
Previous phrasing was misleading - it implied a given module was explicitly on
the blacklist, even if it was due to a restrictive whitelist and the blacklist
was empty.

Arguably the blacklist/whitelist semantics are themselves misleading. A
redesign is tempting.
11 hours ago
Alex Willmer 073fc48afc tests: Remove BlacklistTest stubs (covered by ImporterBlacklistTest) 11 hours ago
Alex Willmer e0de4d3b8e
Merge pull request #1386 from moreati/issue1237
Tidy ups
1 day ago
Alex Willmer 64a581b2ac tests: Add Ubuntu 16.04 to image_prep inventory
I missed this when committing what built 2025.02 iamges
1 day ago
Alex Willmer 823d1d8b47 docs: Document Ansible 13 (ansible-core 2.20) support 1 day ago
Alex Willmer 9b46882478 ansible_mitogen: Remove a use of ansible.module_utils.six 1 day ago
Alex Willmer b105877f4d mitogen: Re-declare Python 2.4 compatibility
With CentOS 5 now covered by the Mitogen unit tests I'm content to
reverse/clarify 104865e866
1 day ago
Alex Willmer fb9efb24ca
Merge pull request #1383 from moreati/prepare-v0.3.35
Prepare v0.3.35
2 days ago
Alex Willmer 9ce6a43329 Begin 0.3.36dev 2 days ago
Alex Willmer 4af6a75278 Prepare v0.3.35 2 days ago
Alex Willmer be3d496110
Merge pull request #1382 from moreati/issue1225-interpreter-python-fallback
ansible_mitogen: Use INTERPRETER_PYTHON_FALLBACK as python candidates
2 days ago
Alex Willmer 699a8ebfb5 ansible_mitogen: Use INTERPRETER_PYTHON_FALLBACK as python candidates
This shouldn't change the interpreter ultimately chosen by Ansible. It should
only improve the hit rate of performing interpreter discovery, particular in
cases where only pythonX.Y is present on the target.

Interpreter discovery may take longer or shorter, depending on the Ansible
version and the interpreters present on the target.
5 days ago
Alex Willmer abb77e77e1
Merge pull request #1374 from moreati/prepare-v0.3.34
Prepare v0.3.34
7 days ago
Alex Willmer 19938ec05a Begin 0.3.35.dev 7 days ago
Alex Willmer 8e6a93dd0f Prepare v0.3.34 7 days ago
Alex Willmer 682faf85fc
Merge pull request #1126 from moreati/issue1124
Ansible: Avoid sending `__main__` and its dependencies from controller to targets
7 days ago
Alex Willmer 83b6cdb616 ansible_mitogen: Speedup startup by not sending `__main__`
On my laptop his reduces the time to execute `ansible -mping ...` by approx
300 ms with `strategy=mitogen_linear`.

Until this commit Mitogen was unnecessarily sending large chunks of Ansible
from the controller to targets, due to `__main__` being identified as a
related module of `ansible.module_utils.basic`, and resolving to something
within `ansible.cli...`.

On Ansible target hosts executing any Ansible Module `__main__` is imported by
`ansible.module_utils.basic` as part of Ansible's module delivery mechanism.
When `mitogen.master.ModuleResponder` (on the controller) processes the
request for `ansible.module_utils.basic` from the target, it scans
`ansible.module_utils.basic` for related imports and finds `__main__`. However
`__main__` on the controller is not the same module as `__main__` on the
target. On the controller it is a module in `ansible.cli...` that implements
one of the ansible commands (e.g. `ansible`, `ansible-playbook`).
7 days ago
Alex Willmer f191f050bf mitogen: Log why a module is sent or not sent by ModuleResponder
This should not change the logic
7 days ago
Alex Willmer f556ec12b1
Merge pull request #1373 from moreati/issue1118-new-os-releases
CI: Expand test coverage of OS releases
1 week ago
Alex Willmer a208daa461 CI: Add OS release coverage: Ubuntu 22.04, Ubuntu 24.04 1 week ago
Alex Willmer 14e8334705 CI: Add OS release coverage: Debian 12 1 week ago
Alex Willmer 1fe55f1c67 CI: Add OS release coverage: CentOS 5
Only the Mitogen unit tests will run against CentOS 5, providing atleast some
Python 2.4test coverage. There is no version of Ansible that supports Python
2.4 that is also supported by Mitogen 0.3.

The SSH key exchange argument is to persuade newer SSH clients to talk with
such an old SSH server.

See https://www.openssh.org/legacy.html
1 week ago
Alex Willmer e0103eb66c CI: Add OS release coverage: AlmaLinux 9 1 week ago
Alex Willmer e044893a88 tests: Variabalize virtualenv creation in isssue 152 regression test
Prep for AlamaLinux 9 introduction
1 week ago
Alex Willmer 1cbd1777bc tests: Check Mitogen+Ansible discovered interpreter fresh Ansible result
Previously this test used a manually compiled list of results, which is
fragile and an ongoing maintenance burden. New method should 'just work' and
be more transparent.

This technique might be more widely applicable in the test suite.
1 week ago
Alex Willmer 0bafbd501c tests: Remove unused distros_* Tox factors 1 week ago
Alex Willmer f0a83168bf
Merge pull request #1370 from moreati/ci-macos15
CI: bump macOS 13 runner -> macOS 15
1 week ago
Alex Willmer 15b2619fb2 CI: Bump deprecated macOS 13 runner to macOS 15 1 week ago
Alex Willmer 5c9abeda94
Merge pull request #1372 from moreati/issue1118-ci-use-2025.02-images
CI: Use 2025.02 test images
1 week ago
Alex Willmer 006d497c25 CI: Show details of failed ci_lib.run_batches() commands 1 week ago
Alex Willmer 9609437262 CI: Use 2025.02 test images, keeping same OS releases
centos8-test:2025.02 no longer has a /usr/bin/python installed, so use
centos8-py3 target which sets `ansible_python_interpreter=/usr/bin/python3` in
the templated inventory.

Ansible <= 9 (ansible-core <= 2.6) now discover the interpreter as
/usr/bin/python3 on debian11-test:2025.02, as opposed to
/usr/bin/python3.9 on debian11-test:2021. I'm don't know the exact
cause. From manual tests the change in observed behaviour appears to be common to
vanilla Ansible (strategy=linear) and Mitogen flavour
(strategy=mitogen_linear).

```console
(ans9) ➜  mitogen git:(4efb7158) ✗ ANSIBLE_STRATEGY=mitogen_linear ANSIBLE_STRATEGY_PLUGINS=ansible_mitogen/plugins/strategy ans9/bin/ansible -e ansible_python_interpreter=auto -mping d11.lan
d11.lan | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
(ans9) ➜  mitogen git:(4efb7158) ✗ ans9/bin/ansible -e ansible_python_interpreter=auto -mping d11.lan
d11.lan | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
```

Update some tests which assume `/usr/bin/python` exists or that `env python`
will resolve successfully.
1 week ago
Alex Willmer 7eabcc61c1 tests: Only test doas on targets with doas binary installed 1 week ago
Alex Willmer 017de4c8e1
Merge pull request #1254 from moreati/issue1118-update-containers
CI: Build newer test images
1 week ago
Alex Willmer 7996a03a37 ci: Bootstrap Debian like containers with python-apt or python3-apt
The Ansible apt module requires it
1 week ago
Alex Willmer 5b6b076c4e ci: Avoid ansible_virtualization_type to check for docker targets
It's not consistant across Ansible versions, particular the oldest ones. This
may have contributed to older test images containing usernames from the host
OS that they were built on (e.g. dmw, alex).
1 week ago
Alex Willmer 4ecafc564d ci: Use command: true as noop handler
meta: noop failed on older Ansibles (e.g. 2.3)
1 week ago
Alex Willmer 56dce28906 ci: Dont show arguments in task name during image prep
A bit to noisy for my taste
1 week ago
Alex Willmer ff973775ce ci: Push new container images to GitHub Container Registry 1 week ago
Alex Willmer 5ffdbb5999 ci: Add Alma 9, Debian 12, Ubuntu 22.04, & Ubuntu 24.04 to image prep 1 week ago
Alex Willmer 01e24f9ddf ci: Use highest supported Ansible version during image prep
It was necessary to split setup.yml because there is no common subset of
supported include/import keywords across Ansible 2.3 - 2.11. The yaml stdout
callback is unavailabe in Ansible 2.3.
1 week ago
Alex Willmer 22e7046cf6 ci: Run image-prep as fast as possible
Mitogen maintainer(s) got better laptops in the last decaade or so.
1 week ago
Alex Willmer cc8a39864d ci: Only install default Python 3.x during image prep
Newer images will shortly be generated, so these higher Python versions aren't
needed anymore.
1 week ago
Alex Willmer 40fbfe58fc ci: Install doas package during image prep, delete vendored doas
Debian 11 is the earliest Debian release with such a package. Ubuntu first
included it in 22.04 CentOS doesn't have it.
1 week ago
Alex Willmer b353980699 ci: Tighten Ansible error checking during image prep 1 week ago
Alex Willmer 3fe9b9bd87 ci: Install setfacl for vanilla Ansible unprivileged become 1 week ago
Alex Willmer cfbb7f884e ci: Add playbook to configure container host for image prep 1 week ago
Alex Willmer a1b5d4941e ci: Use upstream base images for image prep
This eliminates use of third-party *-vault images and performs repository
config during image prep.

The Apache httpd proxy is necessary because https://vault.centos.org now only
accepts TLS 1.x connections, and CentOS 5 can only do upto SSL 3.0. It is
developed to run on Debian 11.
1 week ago
Alex Willmer e32c90a63e ci: Factor out package installation role 1 week ago
Alex Willmer a143787c02 ci: Handle custom package repositories in bootstrap role 1 week ago
Alex Willmer bcc726d3b7 ci: Handle dnf packages in bootstrap role 1 week ago
Alex Willmer 780f8af1a4 ci: Factor out image prep bootstrap as a role
Promoting the script to a full template will fix some whitespace errors later.
1 week ago
Alex Willmer d1c4217db0 ci: Wait for fresh image prep containers to start 1 week ago
Alex Willmer 09b972e96e ci: Fix ansible-lint complaints in image prep playbooks 1 week ago
Alex Willmer 509c572682
Merge pull request #1368 from moreati/prepare-v0.3.33
Prepare v0.3.33
2 weeks ago
Alex Willmer c9eb6e54e2 Begin 0.3.34dev 2 weeks ago
Alex Willmer 4a442f503e Prepare v0.3.33 2 weeks ago
Alex Willmer e52132c89b
Merge pull request #1367 from moreati/ansible13
Test/fix Ansible 13 (ansible-core 2.20) support
2 weeks ago
Alex Willmer f966b3e5c6 CI: Remove lingering stdout_callback=yaml in macOS jobs
Support removed in Ansible 13 (ansible-core 2.20).
refs #1285, #1291
2 weeks ago
Alex Willmer 7c9c38325d ansible_mitogen: Ansible 13 (ansible-core 2.20) support 2 weeks ago
Alex Willmer 5da56f577c CI: Use non-rc Python 3.14 releases
Left over from Python 3.14 support work.
2 weeks ago
Alex Willmer 734047e1cc CI: Remove Ansible 11 (ansible-core 2.18) strategy=linear jobs
I believe I kep them as a reference during Ansible 12 fixups. No longer needed
and they slow down CI runs.
2 weeks ago
Alex Willmer 8b29846990
Merge pull request #1365 from moreati/prepare-v0.3.32
Prepare v0.3.32
2 weeks ago
Alex Willmer 61a7fa1fee Begin 0.3.33dev 2 weeks ago
Alex Willmer 28ea4780db Prepare v0.3.32 2 weeks ago
Alex Willmer 36f7cee2d1
Merge pull request #1363 from moreati/issue1362-issue-templates
Convert bug issue templates to an issue form
2 weeks ago
Alex Willmer 9a2e600317 chore: Convert bug template to a form 2 weeks ago
Alex Willmer 2cc507a6de chore: Remove Mitogen 0.2 issue template
No longer used
2 weeks ago
Alex Willmer db63dd1def
Merge pull request #1359 from moreati/issue1260
CI: Remove integration of retired lgtm.com
3 weeks ago
Alex Willmer cab024a6fc CI: Remove integration of retired lgtm.com
Company was aquired by Github in 2019. Service was switched off in Dec 2022,
replaced by GitHub code scanning. Fixes #1260

See
- https://github.blog/news-insights/product-news/the-next-step-for-lgtm-com-github-code-scanning/
3 weeks ago
Alex Willmer aea028f175
Merge pull request #1357 from moreati/issue1218
ansible_mitogen: Remove maximum Ansible version check
3 weeks ago
Alex Willmer df890459c5 ansible_mitogen: Remove maximum Ansible version check
fixes #1218
3 weeks ago
Alex Willmer 847f34c17d
Merge pull request #1243 from moreati/boot-cmd--argv
mitogen: Pass first stage, preamble length, and context name in argv
3 weeks ago
Alex Willmer 83c5ab1900 mitogen: Send first stage parameters as argv (796 bytes -> 822)
Benefit: The base64 lump is now static for a given Mitogen version, and the
variable parts are more visible. This will make debugging, auditting, and
allow-listing a bit easier.
Potential benefit: generate the base64 once, at build time or startup. Rather
than once per connection.
Cost: Bootstrap command is 26 bytes longer.

```
➜  mitogen git:(boot-cmd--argv) ✗ ./preamble_size.py
SSH command size: 822
Preamble (mitogen.core + econtext) size: 18230 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152237 148.7KiB  68453 66.8KiB 45.0%  18130 17.7KiB 11.9%
mitogen.parent        98746  96.4KiB  51215 50.0KiB 51.9%  12922 12.6KiB 13.1%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10847  10.6KiB   6913  6.8KiB 63.7%   2102  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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15753  15.4KiB   8135  7.9KiB 51.6%   2672  2.6KiB 17.0%
mitogen.master        52891  51.7KiB  27586 26.9KiB 52.2%   7129  7.0KiB 13.5%
```
4 weeks ago
Alex Willmer 3b7a75dfaf mitogen: Send first stage as argv (786 bytes -> 796 bytes)
This saves one layer of quoting/quote escaping in the bootstrap command and a
string interpolation per connection. The cost is an increasing the bootstrap
command by 10 bytes. I like the tradeoff. I could be convinced to revert it.

```console
➜  mitogen git:(boot-cmd--argv) ✗ ./preamble_size.py
SSH command size: 796
Preamble (mitogen.core + econtext) size: 18230 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152237 148.7KiB  68453 66.8KiB 45.0%  18130 17.7KiB 11.9%
mitogen.parent        99181  96.9KiB  51384 50.2KiB 51.8%  12956 12.7KiB 13.1%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10847  10.6KiB   6913  6.8KiB 63.7%   2102  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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15753  15.4KiB   8135  7.9KiB 51.6%   2672  2.6KiB 17.0%
mitogen.master        52891  51.7KiB  27586 26.9KiB 52.2%   7129  7.0KiB 13.5%
```
4 weeks ago
Alex Willmer 191abd492a mitogen: Compress first stage without header or checksum (790 bytes -> 786)
```console
➜  mitogen git:(boot-cmd--argv) ✗ ./preamble_size.py
SSH command size: 786
Preamble (mitogen.core + econtext) size: 18230 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152237 148.7KiB  68453 66.8KiB 45.0%  18130 17.7KiB 11.9%
mitogen.parent        99166  96.8KiB  51375 50.2KiB 51.8%  12957 12.7KiB 13.1%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10847  10.6KiB   6913  6.8KiB 63.7%   2102  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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15753  15.4KiB   8135  7.9KiB 51.6%   2672  2.6KiB 17.0%
mitogen.master        52891  51.7KiB  27586 26.9KiB 52.2%   7129  7.0KiB 13.5%
```

Confirmed Python 2.4 supports this use of zlib.compressobj, despite lack of
mention in https://docs.python.org/2.4/lib/module-zlib.html

```pycon
Python 2.4.6 (#2, Apr 29 2018, 11:16:24)
[GCC 7.3.0] on linux4
Type "help", "copyright", "credits" or "license" for more information.
>>> import zlib
>>> c=zlib.compressobj(zlib.Z_BEST_COMPRESSION,zlib.DEFLATED,-zlib.MAX_WBITS)
>>> c.compress('qwertyuiop') + c.flush()
'+,O-*\xa9,\xcd\xcc/\x00\x00'
```
4 weeks ago
Alex Willmer 408946adbe mitogen: Golf 8 bytes from bootstrap first stage (798 -> 790)
Before
```
SSH command size: 798
Preamble (mitogen.core + econtext) size: 18230 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152237 148.7KiB  68453 66.8KiB 45.0%  18130 17.7KiB 11.9%
mitogen.parent        99020  96.7KiB  51247 50.0KiB 51.8%  12910 12.6KiB 13.0%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10847  10.6KiB   6913  6.8KiB 63.7%   2102  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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15753  15.4KiB   8135  7.9KiB 51.6%   2672  2.6KiB 17.0%
mitogen.master        52891  51.7KiB  27586 26.9KiB 52.2%   7129  7.0KiB 13.5%
```

After
```
SSH command size: 790
Preamble (mitogen.core + econtext) size: 18230 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152237 148.7KiB  68453 66.8KiB 45.0%  18130 17.7KiB 11.9%
mitogen.parent        99020  96.7KiB  51247 50.0KiB 51.8%  12903 12.6KiB 13.0%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10847  10.6KiB   6913  6.8KiB 63.7%   2102  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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15753  15.4KiB   8135  7.9KiB 51.6%   2672  2.6KiB 17.0%
mitogen.master        52891  51.7KiB  27586 26.9KiB 52.2%   7129  7.0KiB 13.5%
```
4 weeks ago
Alex Willmer fdb5c62532
Merge pull request #1353 from moreati/prepare-v0.3.31
Prepare v0.3.31
4 weeks ago
Alex Willmer e4e82f53a1 Begin 0.3.32dev 4 weeks ago
Alex Willmer 77b7a31949 Prepare v0.3.31 4 weeks ago
Alex Willmer 69a5cdce1b
Merge pull request #1352 from moreati/issue1350
ansible_mitogen: Fix ModuleNotFoundError: No module named 'ansible_mitogen'
4 weeks ago
Alex Willmer 85069b28cd ansible_mitogen: Fix ModuleNotFoundError: No module named 'ansible_mitogen'
Loading the ansible_mitogen Ansible plugins apparently doesn't follow the same
rules as importing a Python module. So sys.path manipulations in __init__.py
weren't fired when Ansible tried to load the plugins from a /custom/path that
wasn't already on sys.path.

This wasn't picked up by the test because CI always installs Mitogen as a
Python package (in a virtual env).

This reverses 6145508312.
4 weeks ago
Alex Willmer 2305446ab8
Merge pull request #1346 from moreati/prepare-v0.3.30
Prepare v0.3.30
1 month ago
Alex Willmer c72acfd966 Begin v0.3.31.dev 1 month ago
Alex Willmer 1e90ff25ee Prepare v0.3.30 1 month ago
Alex Willmer 48243724a0
Merge pull request #1341 from mhartmay/logforwarder-fix
master: Fix LogForwarder in case an own LogRecordFactory is used
1 month ago
Marc Hartmayer 24745183ed master: Fix LogForwarder in case an own LogRecordFactory is used
Since Python 3.2 the log record factory can be changed by using
`logging.setLogRecordFactory` [1]. Therefore use `logging.makeLogRecord` as
recommended in the documentation:

"LogRecord instances are created automatically by the Logger every time
something is logged, and can be created manually via makeLogRecord() (for
example, from a pickled event received over the wire)." [2]

This fixes the test case
`log_handler_test.LogRecordFactoryTest.test_logrecordfactory`.

[1] https://docs.python.org/3/library/logging.html#logging.setLogRecordFactory
[2] https://docs.python.org/3/library/logging.html#logrecord-objects

Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
1 month ago
Marc Hartmayer dad28e8b4a tests: Add a test case that verifies behavior when the log record factory is modified
The test currently fails with the following error:

  $ PYTHONPATH=$(pwd)/tests:$PYTHONPATH python3 -m unittest -v log_handler_test
  ...
  test_logrecordfactory (log_handler_test.LogRecordFactoryTest.test_logrecordfactory) ... --- Logging error ---
  Traceback (most recent call last):
    File "/usr/lib/python3.12/logging/__init__.py", line 464, in format
      return self._format(record)
             ^^^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.12/logging/__init__.py", line 460, in _format
      return self._fmt % values
             ~~~~~~~~~~^~~~~~~~
  KeyError: 'custom_attribute'

  During handling of the above exception, another exception occurred:

  Traceback (most recent call last):
    File "/usr/lib/python3.12/logging/__init__.py", line 1160, in emit
      msg = self.format(record)
            ^^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.12/logging/__init__.py", line 999, in format
      return fmt.format(record)
             ^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.12/logging/__init__.py", line 999, in format
      return fmt.format(record)
             ^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.12/logging/__init__.py", line 706, in format
      s = self.formatMessage(record)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.12/logging/__init__.py", line 675, in formatMessage
      return self._style.format(record)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/usr/lib/python3.12/logging/__init__.py", line 466, in format
      raise ValueError('Formatting field not found in record: %s' % e)
  ValueError: Formatting field not found in record: 'custom_attribute'

Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
1 month ago
Alex Willmer 3c648f7df8
Merge pull request #1345 from moreati/oi-mate-wheres-your-loicense
Fix and formalise license metadata
1 month ago
Alex Willmer 01baec8347 Declare license as SPDX identifier in metadata
Fixes warning seen during packaging operations

```
➜  mitogen git:(master) ✗ uv build --sdist
Building source distribution...
...
!!

        ********************************************************************************
        Please consider removing the following classifiers in favor of a SPDX license expression:

        License :: OSI Approved :: BSD License

        See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
        ********************************************************************************

!!
  self._finalize_license_expression()
running egg_info
...
```
1 month ago
Alex Willmer 85f0c33dc5 Correct mitogen.imports.* licenses 1 month ago
Alex Willmer 8f66aa5fcd
Merge pull request #1248 from moreati/sys.path
ansible_mitogen: De-duplicate sys.path manipulations by Ansible plugins
1 month ago
Alex Willmer 6145508312 ansible_mitogen: De-duplicate sys.path manipulations by Ansible plugins 1 month ago
Alex Willmer 9701424be0
Merge pull request #1266 from moreati/imports-cleanup
Imports cleanup
1 month ago
Alex Willmer 7d5f63ccbf Cleanup unused and missing imports 1 month ago
Alex Willmer 3338a651a6
Merge pull request #1339 from moreati/prepare-v0.3.29
Prepare v0.3.29
3 months ago
Alex Willmer 6071fb58c9 Begin 0.3.30dev 3 months ago
Alex Willmer e670bf0ebd Prepare v0.3.29 3 months ago
Alex Willmer f6451bf795
Merge pull request #1287 from moreati/issue1242-py3.14
Python 3.14 support
3 months ago
Alex Willmer 090952a987 Python 3.14 support 3 months ago
Alex Willmer d27275ad46 ci: Set global max failed logins on macOS 3 months ago
Alex Willmer 1b00ca2581 tests: Bump dependency versions 3 months ago
Alex Willmer f4f646a00a
Merge pull request #1337 from moreati/prepare-v0.3.28
Prepare v0.3.28
3 months ago
Alex Willmer b03c1f3d87 Begin 0.3.29dev 3 months ago
Alex Willmer 9f9b37d1ad Prepare v0.3.28 3 months ago
Alex Willmer f6902dd05d
Merge pull request #1336 from Nihlus/freeipa-fixes
Add FreeIPA client modules to the always-fork list
3 months ago
Alex Willmer 2736f38c4b docs: Changelog for FreeIPA client modules -> ALWAYS_FORK_MODULES 3 months ago
Jarl Gullberg 59d5d74abd
Add FreeIPA client modules to the always-fork list. 3 months ago
Alex Willmer 36569792bc
Merge pull request #1307 from moreati/issue1306-investigate
mitogen: Fix non-blocking IO errors in first stage of bootstrap
3 months ago
Alex Willmer 85d6046f2f mitogen: Fix non-blocking IO errors in first stage of bootstrap
When /etc/sudoers has log_output (or similar) enabled the process spawned by
`ctx.sudo()` via `mitogen.parent.Connection.start_child()` receives a stdin
that is in non-blocking mode. The immediate symptom is that `os.openfd(0,
...).read(n)` sometimes returns `None`, causing the first stage to raise an
unhandled TypeError.

The fix (for now) is to use `select.select()` in a while loop to read stdin.
This increases the command size slightly, but I think it's a reasonable
tradeoff until/unless the cause is more fully understood.

All CI tests are now run with sudoers log_output enabled, in order to catch
regressions. `first_stage_test.CommandLineTest` has been amended, because it
relied on implementation details of the bootstrap process that are no longer
true.

Before
```
SSH command size: 755
Preamble (mitogen.core + econtext) size: 18227 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152218 148.7KiB  68437 66.8KiB 45.0%  18124 17.7KiB 11.9%
mitogen.parent        98853  96.5KiB  51103 49.9KiB 51.7%  12881 12.6KiB 13.0%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10827  10.6KiB   6893  6.7KiB 63.7%   2099  2.0KiB 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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15767  15.4KiB   8149  8.0KiB 51.7%   2676  2.6KiB 17.0%
mitogen.master        55317  54.0KiB  28846 28.2KiB 52.1%   7528  7.4KiB 13.6%
```

After
```
SSH command size: 798
Preamble (mitogen.core + econtext) size: 18227 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152218 148.7KiB  68437 66.8KiB 45.0%  18124 17.7KiB 11.9%
mitogen.parent        98944  96.6KiB  51180 50.0KiB 51.7%  12910 12.6KiB 13.0%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10827  10.6KiB   6893  6.7KiB 63.7%   2099  2.0KiB 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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15767  15.4KiB   8149  8.0KiB 51.7%   2676  2.6KiB 17.0%
mitogen.master        55317  54.0KiB  28846 28.2KiB 52.1%   7528  7.4KiB 13.6%
```
3 months ago
Alex Willmer c508bfb58b tests: Check stdio is blocking in sudo contexts
refs #712
3 months ago
Alex Willmer 76f6eb741d tests: Count bytes written in stdio_test.StdIOTest
This is mainly for peace of mind. With all this non-blocking IO investigation
I'm getting a bit paranoid wrt file objects.

refs #712
3 months ago
Alex Willmer 3dfaf83ce7 preamble_size: Fix variability of command & preamble(?) size
Previously the command size could very depanding on the current username, hostname, and process pid.

Before
```
SSH command size: 759
Preamble (mitogen.core + econtext) size: 18227 (17.80KiB)
...
```

After
SSH command size: 755
Preamble (mitogen.core + econtext) size: 18227 (17.80KiB)
...
```
3 months ago
Alex Willmer 936b08dd08 preamble_size: Include mitogen.core and clarify bootstrap size
After:
SSH command size: 759
Preamble (mitogen.core + econtext) size: 18227 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.core         152218 148.7KiB  68437 66.8KiB 45.0%  18124 17.7KiB 11.9%
mitogen.parent        98853  96.5KiB  51103 49.9KiB 51.7%  12881 12.6KiB 13.0%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10827  10.6KiB   6893  6.7KiB 63.7%   2099  2.0KiB 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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15767  15.4KiB   8149  8.0KiB 51.7%   2676  2.6KiB 17.0%
mitogen.master        55317  54.0KiB  28846 28.2KiB 52.1%   7528  7.4KiB 13.6%
3 months ago
Alex Willmer 30d8a38a3b preamble_size: Consolidate table formatting, align columns better
Before
./preamble_size.py
SSH command size: 759
Bootstrap (mitogen.core) size: 18227 (17.80KiB)

                              Original          Minimized           Compressed
mitogen.parent            98853 96.5KiB  51103 49.9KiB 51.7%  12881 12.6KiB 13.0%
mitogen.fork               8445  8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh               10827 10.6KiB   6893  6.7KiB 63.7%   2099  2.0KiB 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           41581 40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh           15767 15.4KiB   8149  8.0KiB 51.7%   2676  2.6KiB 17.0%
mitogen.master            55317 54.0KiB  28846 28.2KiB 52.1%   7528  7.4KiB 13.6%

After:
SSH command size: 759
Bootstrap (mitogen.core) size: 18227 (17.80KiB)

                        Original           Minimized           Compressed
mitogen.parent        98853  96.5KiB  51103 49.9KiB 51.7%  12881 12.6KiB 13.0%
mitogen.fork           8445   8.2KiB   4139  4.0KiB 49.0%   1652  1.6KiB 19.6%
mitogen.ssh           10827  10.6KiB   6893  6.7KiB 63.7%   2099  2.0KiB 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       41581  40.6KiB  22398 21.9KiB 53.9%   5847  5.7KiB 14.1%
mitogen.fakessh       15767  15.4KiB   8149  8.0KiB 51.7%   2676  2.6KiB 17.0%
mitogen.master        55317  54.0KiB  28846 28.2KiB 52.1%   7528  7.4KiB 13.6%
3 months ago
Alex Willmer e4e2c6caaf CI: Move sudo test users defaults into /etc/sudoers.d
Prep for reusing it in non-Ansible tests
3 months ago
Alex Willmer 5abdde1117 CI: Report sudo version on Ansible targets 3 months ago
Alex Willmer dc7fae973b CI: Fix ci_lib and test_lib have_<cmd>() when <cmd> exits abnormally
We were not raising CalledProcessError when exit status != 0.
3 months ago
Alex Willmer 885c6de65e
Merge pull request #1331 from moreati/prep-v0.3.27
Prep v0.3.27
3 months ago
Alex Willmer 07d1078010 Begin v0.3.28dev 3 months ago

@ -34,13 +34,15 @@ ANSIBLE_TESTS_HOSTS_DIR = os.path.join(GIT_ROOT, 'tests/ansible/hosts')
ANSIBLE_TESTS_TEMPLATES_DIR = os.path.join(GIT_ROOT, 'tests/ansible/templates')
DISTRO_SPECS = os.environ.get(
'MITOGEN_TEST_DISTRO_SPECS',
'centos6 centos8 debian9 debian11 ubuntu1604 ubuntu2004',
'alma9-py3 centos5 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2404-py3',
)
IMAGE_PREP_DIR = os.path.join(GIT_ROOT, 'tests/image_prep')
IMAGE_TEMPLATE = os.environ.get(
'MITOGEN_TEST_IMAGE_TEMPLATE',
'ghcr.io/mitogen-hq/%(distro)s-test:2021',
'ghcr.io/mitogen-hq/%(distro)s-test:2025.02',
)
SUDOERS_DEFAULTS_SRC = './tests/image_prep/files/sudoers_defaults'
SUDOERS_DEFAULTS_DEST = '/etc/sudoers.d/mitogen_test_defaults'
TESTS_SSH_PRIVATE_KEY_FILE = os.path.join(GIT_ROOT, 'tests/data/docker/mitogen__has_sudo_pubkey.key')
@ -58,6 +60,7 @@ def _have_cmd(args):
try:
subprocess.run(
args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True,
)
except OSError as exc:
if exc.errno == errno.ENOENT:
@ -153,7 +156,15 @@ def run_batches(batches):
subprocess.Popen(combine(batch), shell=True)
for batch in batches
]
assert [proc.wait() for proc in procs] == [0] * len(procs)
for proc in procs:
proc.wait()
if proc.returncode:
print(
'proc: pid=%i rc=%i args=%r'
% (proc.pid, proc.returncode, proc.args),
file=sys.stderr, flush=True,
)
assert [proc.returncode for proc in procs] == [0] * len(procs)
def get_output(s, *args, **kwargs):
@ -228,7 +239,7 @@ def container_specs(
[{'distro': 'debian11',
'family': 'debian',
'hostname': 'localhost',
'image': 'ghcr.io/mitogen-hq/debian11-test:2021',
'image': 'ghcr.io/mitogen-hq/debian11-test:2025.02',
'index': 1,
'name': 'target-debian11-1',
'port': 2201,
@ -236,7 +247,7 @@ def container_specs(
{'distro': 'centos6',
'family': 'centos',
'hostname': 'localhost',
'image': 'ghcr.io/mitogen-hq/centos6-test:2021',
'image': 'ghcr.io/mitogen-hq/centos6-test:2025.02',
'index': 2,
'name': 'target-centos6-2',
'port': 2202,

@ -2,6 +2,7 @@
# Run the Mitogen tests.
import os
import subprocess
import ci_lib
@ -13,6 +14,14 @@ os.environ.update({
if not ci_lib.have_docker():
os.environ['SKIP_DOCKER_TESTS'] = '1'
subprocess.check_call(
"umask 0022; sudo cp '%s' '%s'"
% (ci_lib.SUDOERS_DEFAULTS_SRC, ci_lib.SUDOERS_DEFAULTS_DEST),
shell=True,
)
subprocess.check_call(['sudo', 'visudo', '-cf', ci_lib.SUDOERS_DEFAULTS_DEST])
subprocess.check_call(['sudo', '-l'])
interesting = ci_lib.get_interesting_procs()
ci_lib.run('./run_tests -v')
ci_lib.check_stray_processes(interesting)

@ -1,33 +0,0 @@
---
name: Mitogen 0.2.x bug report
about: Report a bug in Mitogen 0.2.x (for Ansible 2.5, 2.6, 2.7, 2.8, or 2.9)
title: ''
labels: affects-0.2, bug
assignees: ''
---
Please drag-drop large logs as text file attachments.
Feel free to write an issue in your preferred format, however if in doubt, use
the following checklist as a guide for what to include.
* Which version of Ansible are you running?
* Is your version of Ansible patched in any way?
* Are you running with any custom modules, or `module_utils` loaded?
* Have you tried the latest master version from Git?
* Do you have some idea of what the underlying problem may be?
https://mitogen.networkgenomics.com/ansible_detailed.html#common-problems has
instructions to help figure out the likely cause and how to gather relevant
logs.
* Mention your host and target OS and versions
* Mention your host and target Python versions
* If reporting a performance issue, mention the number of targets and a rough
description of your workload (lots of copies, lots of tiny file edits, etc.)
* If reporting a crash or hang in Ansible, please rerun with -vvv and include
200 lines of output around the point of the error, along with a full copy of
any traceback or error text in the log. Beware "-vvv" may include secret
data! Edit as necessary before posting.
* If reporting any kind of problem with Ansible, please include the Ansible
version along with output of "ansible-config dump --only-changed".

@ -1,33 +0,0 @@
---
name: Mitogen 0.3.x bug report
about: Report a bug in Mitogen 0.3.x (for Ansible 2.10.x)
title: ''
labels: affects-0.3, bug
assignees: ''
---
Please drag-drop large logs as text file attachments.
Feel free to write an issue in your preferred format, however if in doubt, use
the following checklist as a guide for what to include.
* Which version of Ansible are you running?
* Is your version of Ansible patched in any way?
* Are you running with any custom modules, or `module_utils` loaded?
* Have you tried the latest master version from Git?
* Do you have some idea of what the underlying problem may be?
https://mitogen.networkgenomics.com/ansible_detailed.html#common-problems has
instructions to help figure out the likely cause and how to gather relevant
logs.
* Mention your host and target OS and versions
* Mention your host and target Python versions
* If reporting a performance issue, mention the number of targets and a rough
description of your workload (lots of copies, lots of tiny file edits, etc.)
* If reporting a crash or hang in Ansible, please rerun with -vvv and include
200 lines of output around the point of the error, along with a full copy of
any traceback or error text in the log. Beware "-vvv" may include secret
data! Edit as necessary before posting.
* If reporting any kind of problem with Ansible, please include the Ansible
version along with output of "ansible-config dump --only-changed".

@ -0,0 +1,62 @@
name: Bug report
description: Report a bug in Mitogen 0.3.x (for Ansible 2.10 and above)
labels:
- affects-0.3
type: bug
body:
- type: textarea
attributes:
label: Description
description: >
When does the problem occur?
What happens after?
How is this different?
Did it previously behave as expected?
placeholder: |
When I do X, Y happens, but I was expecting Z because ...
Before version 1.2.3 it worked as expected.
validations:
required: true
- type: input
attributes:
label: Mitogen version
placeholder: 0.3.31, 0.3.3-9+deb12u1
validations:
required: true
- type: input
attributes:
label: Ansible version (if applicable)
placeholder: 2.18.11
- type: textarea
attributes:
label: OS and environment
description: >
What operating system version(s), Python version(s), etc. are you using?
placeholder: |
Controller (master): Debian 13, Python 3.14
Targets (slaves): Ubuntu 20.04/Python 2.7, RHEL 10, ...
- type: textarea
attributes:
label: Steps to reproduce
description: >
Instructions, code, or playbook(s) recreate the beahviour
value: |
Steps:
1. Set config `foo = 42` in somefile.cfg
2. Run the following Python or Playbook with `cmd --option bar ...`
```
Code or playbook here
```
- type: textarea
attributes:
label: Anything else
description: >
Include any other details you think might be relevant or helpful.
Examples might include logs, unusual settings, environment variables, ...

@ -100,22 +100,22 @@ jobs:
python_version: '3.13'
- tox_env: py313-m_ans-ans8
python_version: '3.13'
- tox_env: py313-m_ans-ans9
python_version: '3.13'
- tox_env: py313-m_ans-ans10
python_version: '3.13'
- tox_env: py313-m_ans-ans11
python_version: '3.13'
- tox_env: py313-m_ans-ans12
python_version: '3.13'
- tox_env: py313-m_ans-ans11-s_lin
python_version: '3.13'
- tox_env: py313-m_ans-ans12-s_lin
python_version: '3.13'
- tox_env: py313-m_mtg
python_version: '3.13'
- tox_env: py314-m_ans-ans9
python_version: '3.14'
- tox_env: py314-m_ans-ans10
python_version: '3.14'
- tox_env: py314-m_ans-ans11
python_version: '3.14'
- tox_env: py314-m_ans-ans12
python_version: '3.14'
- tox_env: py314-m_ans-ans13
python_version: '3.14'
- tox_env: py314-m_ans-ans13-s_lin
python_version: '3.14'
- tox_env: py314-m_mtg
python_version: '3.14'
steps:
- uses: actions/checkout@v4
@ -153,22 +153,21 @@ jobs:
macos:
name: macos ${{ matrix.tox_env }}
# https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md
runs-on: macos-13
# https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
runs-on: macos-15
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- tox_env: py313-m_lcl-ans11
sshpass_version: "1.10"
- tox_env: py313-m_lcl-ans11-s_lin
sshpass_version: "1.10"
- tox_env: py313-m_lcl-ans12
- tox_env: py313-m_lcl-ans12-s_lin
- tox_env: py313-m_mtg
- tox_env: py314-m_lcl-ans13
python_version: '3.14'
- tox_env: py314-m_lcl-ans13-s_lin
python_version: '3.14'
- tox_env: py314-m_mtg
python_version: '3.14'
steps:
- uses: actions/checkout@v4

1
.gitignore vendored

@ -13,6 +13,7 @@ build/
dist/
extra/
tests/ansible/.*.pid
tests/image_prep/logs
docs/_build/
htmlcov/
*.egg-info

@ -1,10 +0,0 @@
path_classifiers:
library:
- "mitogen/compat"
- "ansible_mitogen/compat"
queries:
# Mitogen 2.4 compatibility trips this query everywhere, so just disable it
- exclude: py/unreachable-statement
- exclude: py/should-use-with
# mitogen.core.b() trips this query everywhere, so just disable it
- exclude: py/import-and-import-from

@ -7,5 +7,3 @@
<a href="https://mitogen.networkgenomics.com/">Please see the documentation</a>.
![](https://i.imgur.com/eBM6LhJ.gif)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/mitogen-hq/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mitogen-hq/mitogen/alerts/)

@ -43,11 +43,11 @@ import ansible.errors
import ansible.plugins.connection
import mitogen.core
import mitogen.parent
import mitogen.service
import ansible_mitogen.mixins
import ansible_mitogen.parsing
import ansible_mitogen.process
import ansible_mitogen.services
import ansible_mitogen.target
import ansible_mitogen.transport_config
import ansible_mitogen.utils.unsafe

@ -49,18 +49,6 @@ __all__ = [
ANSIBLE_VERSION_MIN = (2, 10)
ANSIBLE_VERSION_MAX = (2, 19)
NEW_VERSION_MSG = (
"Your Ansible version (%s) is too recent. The most recent version\n"
"supported by Mitogen for Ansible is %s.x. Please check the Mitogen\n"
"release notes to see if a new version is available, otherwise\n"
"subscribe to the corresponding GitHub issue to be notified when\n"
"support becomes available.\n"
"\n"
" https://mitogen.rtfd.io/en/latest/changelog.html\n"
" https://github.com/mitogen-hq/mitogen/issues/\n"
)
OLD_VERSION_MSG = (
"Your version of Ansible (%s) is too old. The oldest version supported by "
"Mitogen for Ansible is %s."
@ -78,11 +66,6 @@ def assert_supported_release():
OLD_VERSION_MSG % (v, ANSIBLE_VERSION_MIN)
)
if v[:2] > ANSIBLE_VERSION_MAX:
raise ansible.errors.AnsibleError(
NEW_VERSION_MSG % (v, ANSIBLE_VERSION_MAX)
)
# this is the first file our strategy plugins import, so we need to check this here
# in prior Ansible versions, connection_loader.get_with_context didn't exist, so if a user

@ -34,7 +34,6 @@ import os
import ansible.utils.display
import mitogen.core
import mitogen.utils

@ -470,18 +470,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
# chicken-and-egg issue, mitogen needs a python to run low_level_execute_command
# which is required by Ansible's discover_interpreter function
if self._mitogen_discovering_interpreter:
possible_pythons = [
'/usr/bin/python',
'python3',
'python3.7',
'python3.6',
'python3.5',
'python2.7',
'python2.6',
'/usr/libexec/platform-python',
'/usr/bin/python3',
'python'
]
possible_pythons = self._mitogen_interpreter_candidates
else:
# not used, just adding a filler value
possible_pythons = ['python']
@ -492,12 +481,15 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
rc, stdout, stderr = self._connection.exec_command(
cmd, in_data, sudoable, mitogen_chdir=chdir,
)
# TODO: what exception is thrown?
except:
except BaseException as exc:
# we've reached the last python attempted and failed
if possible_python == possible_pythons[-1]:
raise
else:
LOG.debug(
'%r._low_level_execute_command: candidate=%r ignored: %s, %r',
self, possible_python, type(exc), exc,
)
continue
stdout_text = to_text(stdout, errors=encoding_errors)

@ -50,6 +50,7 @@ import ansible.executor.module_common
import mitogen.core
import mitogen.select
import mitogen.service
import ansible_mitogen.loaders
import ansible_mitogen.parsing
@ -347,6 +348,25 @@ class NewStylePlanner(ScriptPlanner):
'freeipa.ansible_freeipa.ipaautomountlocation',
'freeipa.ansible_freeipa.ipaautomountmap',
'freeipa.ansible_freeipa.ipacert',
'freeipa.ansible_freeipa.ipaclient_api',
'freeipa.ansible_freeipa.ipaclient_fix_ca',
'freeipa.ansible_freeipa.ipaclient_fstore',
'freeipa.ansible_freeipa.ipaclient_get_otp',
'freeipa.ansible_freeipa.ipaclient_ipa_conf',
'freeipa.ansible_freeipa.ipaclient_join',
'freeipa.ansible_freeipa.ipaclient_set_hostname',
'freeipa.ansible_freeipa.ipaclient_setup_automount',
'freeipa.ansible_freeipa.ipaclient_setup_certmonger',
'freeipa.ansible_freeipa.ipaclient_setup_firefox',
'freeipa.ansible_freeipa.ipaclient_setup_krb5',
'freeipa.ansible_freeipa.ipaclient_setup_nis',
'freeipa.ansible_freeipa.ipaclient_setup_nss',
'freeipa.ansible_freeipa.ipaclient_setup_ntp',
'freeipa.ansible_freeipa.ipaclient_setup_ssh',
'freeipa.ansible_freeipa.ipaclient_setup_sshd',
'freeipa.ansible_freeipa.ipaclient_temp_krb5',
'freeipa.ansible_freeipa.ipaclient_test',
'freeipa.ansible_freeipa.ipaclient_test_keytab',
'freeipa.ansible_freeipa.ipaconfig',
'freeipa.ansible_freeipa.ipadelegation',
'freeipa.ansible_freeipa.ipadnsconfig',

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen.connection
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -30,7 +30,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
import ansible.errors
@ -38,9 +38,7 @@ import ansible.errors
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection
import ansible_mitogen.loaders

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen.connection
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection
import ansible_mitogen.process

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen.connection
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen.connection
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,7 +29,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
from ansible.plugins.connection.ssh import (
@ -50,9 +50,7 @@ DOCUMENTATION = """
try:
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection
import ansible_mitogen.loaders

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen.connection
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,15 +29,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
try:
import ansible_mitogen.connection
import ansible_mitogen
except ImportError:
base_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
del base_dir
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.connection

@ -29,7 +29,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
#
@ -47,12 +47,10 @@ import sys
# debuggers and isinstance() work predictably.
#
BASE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '../../..')
)
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
try:
import ansible_mitogen
except ImportError:
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.strategy
import ansible.plugins.strategy.linear

@ -29,7 +29,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
#
@ -47,12 +47,10 @@ import sys
# debuggers and isinstance() work predictably.
#
BASE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '../../..')
)
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
try:
import ansible_mitogen
except ImportError:
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.loaders
import ansible_mitogen.strategy

@ -29,7 +29,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
#
@ -47,12 +47,10 @@ import sys
# debuggers and isinstance() work predictably.
#
BASE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '../../..')
)
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
try:
import ansible_mitogen
except ImportError:
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.loaders
import ansible_mitogen.strategy

@ -29,7 +29,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os.path
import os
import sys
#
@ -47,12 +47,10 @@ import sys
# debuggers and isinstance() work predictably.
#
BASE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '../../..')
)
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
try:
import ansible_mitogen
except ImportError:
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..')))
import ansible_mitogen.loaders
import ansible_mitogen.strategy

@ -41,6 +41,7 @@ except ImportError:
import mitogen.core
import ansible_mitogen.affinity
import ansible_mitogen.loaders
import ansible_mitogen.logging
import ansible_mitogen.mixins
import ansible_mitogen.process

@ -40,7 +40,6 @@ import errno
import grp
import json
import logging
import operator
import os
import pty
import pwd
@ -66,8 +65,6 @@ if not sys.modules.get(str('__main__')):
import ansible.module_utils.json_utils
from ansible.module_utils.six.moves import reduce
import ansible_mitogen.runner
@ -718,7 +715,9 @@ def apply_mode_spec(spec, mode):
mask = CHMOD_MASKS[ch]
bits = CHMOD_BITS[ch]
cur_perm_bits = mode & mask
new_perm_bits = reduce(operator.or_, (bits[p] for p in perms), 0)
new_perm_bits = 0
for perm in perms:
new_perm_bits |= bits[perm]
mode &= ~mask
if op == '=':
mode |= new_perm_bits

@ -73,13 +73,21 @@ import ansible.utils.unsafe_proxy
from ansible.module_utils.six import with_metaclass
from ansible.module_utils.parsing.convert_bool import boolean
import ansible_mitogen.utils
import mitogen.core
LOG = logging.getLogger(__name__)
if ansible_mitogen.utils.ansible_version[:2] >= (2, 19):
_FALLBACK_INTERPRETER = ansible.executor.interpreter_discovery._FALLBACK_INTERPRETER
elif ansible_mitogen.utils.ansible_version[:2] >= (2, 17):
_FALLBACK_INTERPRETER = u'/usr/bin/python3'
else:
_FALLBACK_INTERPRETER = u'/usr/bin/python'
def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_python):
def run_interpreter_discovery_if_necessary(s, candidates, task_vars, action, rediscover_python):
"""
Triggers ansible python interpreter discovery if requested.
Caches this value the same way Ansible does it.
@ -107,8 +115,11 @@ def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_pyth
# blow away the discovered_interpreter_config cache and rediscover
del task_vars['ansible_facts'][discovered_interpreter_config]
if discovered_interpreter_config not in task_vars['ansible_facts']:
try:
s = task_vars[u'ansible_facts'][discovered_interpreter_config]
except KeyError:
action._mitogen_discovering_interpreter = True
action._mitogen_interpreter_candidates = candidates
# fake pipelining so discover_interpreter can be happy
action._connection.has_pipelining = True
s = ansible.executor.interpreter_discovery.discover_interpreter(
@ -121,18 +132,17 @@ def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_pyth
# cache discovered interpreter
task_vars['ansible_facts'][discovered_interpreter_config] = s
action._connection.has_pipelining = False
else:
s = task_vars['ansible_facts'][discovered_interpreter_config]
# propagate discovered interpreter as fact
action._discovered_interpreter_key = discovered_interpreter_config
action._discovered_interpreter = s
action._mitogen_discovering_interpreter = False
action._mitogen_interpreter_candidates = None
return s
def parse_python_path(s, task_vars, action, rediscover_python):
def parse_python_path(s, candidates, task_vars, action, rediscover_python):
"""
Given the string set for ansible_python_interpeter, parse it using shell
syntax and return an appropriate argument vector. If the value detected is
@ -143,10 +153,9 @@ def parse_python_path(s, task_vars, action, rediscover_python):
# if python_path doesn't exist, default to `auto` and attempt to discover it
s = 'auto'
s = run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_python)
# if unable to determine python_path, fallback to '/usr/bin/python'
s = run_interpreter_discovery_if_necessary(s, candidates, task_vars, action, rediscover_python)
if not s:
s = '/usr/bin/python'
s = _FALLBACK_INTERPRETER
return ansible.utils.shlex.shlex_split(s)
@ -510,6 +519,9 @@ class PlayContextSpec(Spec):
interpreter_python = C.config.get_config_value(
'INTERPRETER_PYTHON', variables=variables,
)
interpreter_python_fallback = C.config.get_config_value(
'INTERPRETER_PYTHON_FALLBACK', variables=variables,
)
if '{{' in interpreter_python or '{%' in interpreter_python:
templar = self._connection.templar
@ -517,6 +529,7 @@ class PlayContextSpec(Spec):
return parse_python_path(
interpreter_python,
candidates=interpreter_python_fallback,
task_vars=self._task_vars,
action=self._action,
rediscover_python=rediscover_python)
@ -732,11 +745,12 @@ class MitogenViaSpec(Spec):
def python_path(self, rediscover_python=False):
s = self._host_vars.get('ansible_python_interpreter')
# #511, #536: executor/module_common.py::_get_shebang() hard-wires
# "/usr/bin/python" as the default interpreter path if no other
# interpreter is specified.
interpreter_python_fallback = self._host_vars.get(
'ansible_interpreter_python_fallback', [],
)
return parse_python_path(
s,
candidates=interpreter_python_fallback,
task_vars=self._task_vars,
action=self._action,
rediscover_python=rediscover_python)

@ -138,13 +138,15 @@ Noteworthy Differences
| 8 | 3.9 - 3.13 |
+-----------------+-----------------+
| 9 | |
+-----------------+ 3.10 - 3.13 |
+-----------------+ 3.10 - 3.14 |
| 10 | |
+-----------------+-----------------+
| 11 | |
+-----------------+ 3.11 - 3.13+ |
+-----------------+ 3.11 - 3.14 |
| 12 | |
+-----------------+-----------------+
| 13 | 3.12 - 3.14 |
+-----------------+-----------------+
Verify your installation is running one of these versions by checking
``ansible --version`` output.

@ -18,6 +18,93 @@ To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub <https://github.com/mitogen-hq/mitogen/>`_.
In progress (unreleased)
------------------------
* :gh:issue:`1237` :mod:`mitogen`: Re-declare Python 2.4 compatibility
* :gh:issue:`1385` :mod:`ansible_mitogen`: Remove a use of
``ansible.module_utils.six``
* :gh:issue:`1354` docs: Document Ansible 13 (ansible-core 2.20) support
* :gh:issue:`1354` :mod:`mitogen`: Clarify error message when a module
request would be refused by allow or deny listing
v0.3.35 (2025-12-01)
--------------------
* :gh:issue:`1132` :mod:`ansible_mitogen` During intrepreter discovery use
Ansible ``INTERPRETER_PYTHON_FALLBACK`` config as list of candidates
v0.3.34 (2025-11-27)
--------------------
* :gh:issue:`1118` CI: Use 2025.02 test images, keeping same OS releases
* :gh:issue:`1358` CI: Bump deprecated macOS 13 runner to macOS 15
* :gh:issue:`1118` CI: Add OS release coverage: AlmaLinux 9
* :gh:issue:`1118` CI: Add OS release coverage: CentOS 5
* :gh:issue:`1118` CI: Add OS release coverage: Debian 12
* :gh:issue:`1118` CI: Add OS release coverage: Ubuntu 22.04, Ubuntu 24.04
* :gh:issue:`1124` :mod:`mitogen`: Log why a module is sent or not sent by
:class:`mitogen.master.ModuleResponder`
* :gh:issue:`1124` :mod:`ansible_mitogen`: Speedup startup by not sending
``__main__`` as a related module
v0.3.33 (2025-11-22)
--------------------
* :gh:issue:`1354` :mod:`ansible_mitogen`: ansible_mitogen: Ansible 13
(ansible-core 2.20) support
v0.3.32 (2025-11-21)
--------------------
* :gh:issue:`1243` :mod:`mitogen`: Pass first stage, context name, & preamble
size as seperate **argv** arguments
* :gh:issue:`1218` :mod:`ansible_mitogen`: Remove maximum Ansible version check
* :gh:issue:`1260` CI: Remove integration of retired lgtm.com
v0.3.31 (2025-11-05)
--------------------
* :gh:issue:`1350` :mod:`ansible_mitogen`: Fix regression when loading plugins
from ``/custom/path/to/mitogen``
v0.3.30 (2025-10-30)
--------------------
* :gh:issue:`1266` Import cleanups
* :gh:issue:`1266` :mod:`ansible_mitogen`: De-duplicate sys.path manipulations
* :gh:issue:`1344` Correct SPDX license declarations
* :gh:issue:`1344` Declare BSD-3-Clause SPDX license in package metadata
* :gh:issue:`1344` :mod:`mitogen`: Use :py:func:`logging.makeLogRecord`
v0.3.29 (2025-09-18)
--------------------
* :gh:issue:`1287` Python 3.14 support
* :gh:issue:`1287` tests: Bump dependencies
v0.3.28 (2025-09-17)
--------------------
* :gh:issue:`1306` :mod:`ansible_mitogen`: Fix non-blocking IO errors in
first stage of bootstrap
* :gh:issue:`1306` CI: Report sudo version on Ansible targets
* :gh:issue:`1306` CI: Move sudo test users defaults into ``/etc/sudoers.d``
* :gh:issue:`1306` preamble_size: Fix variability of measured command size
* :gh:issue:`1306` tests: Count bytes written in ``stdio_test.StdIOTest``
* :gh:issue:`1306` tests: Check stdio is blocking in sudo contexts
* :gh:issue:`1327` :mod:`ansible_mitogen`: Add FreeIPA client modules to the
always-fork list
v0.3.27 (2025-08-20)
--------------------

@ -27,14 +27,13 @@ Python Command Line
###################
The Python command line sent to the host is a :mod:`zlib`-compressed [#f2]_ and
base64-encoded copy of the :py:meth:`mitogen.master.Stream._first_stage`
function, which has been carefully optimized to reduce its size. Prior to
compression and encoding, ``CONTEXT_NAME`` is replaced with the desired context
name in the function's source code.
base64-encoded copy of :py:meth:`mitogen.parent.Connection._first_stage`,
which is carefully written to maximize it compatibility and minimize its size.
A simplified illustration of the bootstrap command is
.. code::
python -c 'exec "xxx".decode("base64").decode("zlib")'
python -c 'exec(sys.argv[1].decode("base64").decode("zlib"))' <base64> ...
The command-line arranges for the Python interpreter to decode the base64'd
component, decompress it and execute it as Python code. Base64 is used since
@ -71,8 +70,8 @@ of the large base64-encoded first stage parameter, and to replace **argv[0]**
with something descriptive.
After configuring its ``stdin`` to point to the read end of the pipe, the
parent half of the fork re-executes Python, with **argv[0]** taken from the
``CONTEXT_NAME`` variable earlier substituted into its source code. As no
fork parent re-executes Python with **argv[0]** composed of the Python
interpreter path and a remote name supplied by the Mitogen parent. As no
arguments are provided to this new execution of Python, and since ``stdin`` is
connected to a pipe (whose write end is connected to the first stage), the
Python interpreter begins reading source code to execute from the pipe

@ -332,12 +332,16 @@ a large fleet of machines, or to alert the parent of unexpected state changes.
Compatibility
#############
Mitogen is compatible with **Python 2.4** released November 2004, making it
``mitogen.*`` is compatible with Python 2.4 - 2.7 and 3.6 onward; making it
suitable for managing a fleet of potentially ancient corporate hardware, such
as Red Hat Enterprise Linux 5, released in 2007.
Every combination of Python 3.x/2.x parent and child should be possible,
however at present only Python 2.4, 2.6, 2.7 and 3.6 are tested automatically.
Every combination of Python 3.x/2.x parent and child should be possible.
Automated testing cannot cover every combination, automated testing tries to
cover the extemities (e.g. Python 3.14 parent -> Python 2.4 child).
``ansible_mitogen.*`` is compatible with Python 2.7 and 3.6 onward; making it
suitable for Ansible 2.10 onward.
Zero Dependencies

@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup.
#: Library version as a tuple.
__version__ = (0, 3, 27)
__version__ = (0, 3, 36, 'dev')
#: This is :data:`False` in slave contexts. Previously it was used to prevent
@ -106,7 +106,8 @@ def main(log_level='INFO', profiling=_default_profiling):
def wrapper(func):
if func.__module__ != '__main__':
return func
import mitogen.parent
import mitogen.core
import mitogen.master
import mitogen.utils
if profiling:
mitogen.core.enable_profiling()

@ -541,6 +541,7 @@ def is_blacklisted_import(importer, fullname):
any packages have been whitelisted and `fullname` is not part of one.
NB:
- The default whitelist is `['']` which matches any module name.
- If a package is on both lists, then it is treated as blacklisted.
- If any package is whitelisted, then all non-whitelisted packages are
treated as blacklisted.
@ -1536,9 +1537,8 @@ class Importer(object):
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 '
'request will always be refused.'
'A %r request would be refused by the Mitogen master. The module is '
'on the deny list (blacklist) or not on the allow list (whitelist).'
)
pkg_resources_msg = (
'pkg_resources is prohibited from importing __main__, as it causes '

@ -97,7 +97,6 @@ import inspect
import os
import pty
import shutil
import socket
import subprocess
import sys
import tempfile

@ -1,10 +1,12 @@
# SPDX-FileCopyrightText: 2025 Mitogen authors <https://github.com/mitogen-hq>
# SPDX-License-Identifier: MIT
# SPDX-License-Identifier: BSD-3-Clause
# !mitogen: minify_safe
import sys
if sys.version_info >= (3, 6):
if sys.version_info >= (3, 14):
from mitogen.imports._py314 import _code_imports
elif sys.version_info >= (3, 6):
from mitogen.imports._py36 import _code_imports
elif sys.version_info >= (2, 5):
from mitogen.imports._py2 import _code_imports_py25 as _code_imports

@ -1,5 +1,5 @@
# SPDX-FileCopyrightText: 2025 Mitogen authors <https://github.com/mitogen-hq>
# SPDX-License-Identifier: MIT
# SPDX-License-Identifier: BSD-3-Clause
# !mitogen: minify_safe
import array

@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2025 Mitogen authors <https://github.com/mitogen-hq>
# SPDX-License-Identifier: BSD-3-Clause
# !mitogen: minify_safe
import opcode
IMPORT_NAME = opcode.opmap['IMPORT_NAME']
LOAD_CONST = opcode.opmap['LOAD_CONST']
LOAD_SMALL_INT = opcode.opmap['LOAD_SMALL_INT']
def _code_imports(code, consts, names):
start = 4
while True:
op3_idx = code.find(IMPORT_NAME, start, -1)
if op3_idx < 0:
return
if op3_idx % 2:
start = op3_idx + 1
continue
if code[op3_idx-4] != LOAD_SMALL_INT or code[op3_idx-2] != LOAD_CONST:
start = op3_idx + 2
continue
start = op3_idx + 6
arg1, arg2, arg3 = code[op3_idx-3], code[op3_idx-1], code[op3_idx+1]
yield (arg1, names[arg3], consts[arg2] or ())

@ -1,5 +1,5 @@
# SPDX-FileCopyrightText: 2025 Mitogen authors <https://github.com/mitogen-hq>
# SPDX-License-Identifier: MIT
# SPDX-License-Identifier: BSD-3-Clause
# !mitogen: minify_safe
import opcode

@ -370,15 +370,19 @@ class LogForwarder(object):
if logger is None:
self._cache[logger_name] = logger = logging.getLogger(logger_name)
levelno = int(level_s)
# See logging.Handler.makeRecord()
record = logging.LogRecord(
name=logger.name,
level=int(level_s),
pathname='(unknown file)',
lineno=0,
msg=s,
args=(),
exc_info=None,
record = logging.makeLogRecord(
{
"name": logger.name,
"levelname": logging.getLevelName(levelno),
"levelno": levelno,
"pathname": "(unknown file)",
"lineno": 0,
"msg": s,
"args": (),
"exc_info": None,
}
)
record.mitogen_message = s
record.mitogen_context = self._router.context_by_id(msg.src_id)
@ -839,6 +843,12 @@ class ModuleFinder(object):
related modules likely needed by a child context requesting the original
module.
"""
# Fullnames of modules that should not be sent as a related module
_related_modules_denylist = frozenset({
'__main__',
})
def __init__(self):
#: Import machinery is expensive, keep :py:meth`:get_module_source`
#: results around.
@ -927,6 +937,34 @@ class ModuleFinder(object):
fullname, _, _ = str_rpartition(to_text(fullname), u'.')
yield fullname
def _reject_related_module(self, requested_fullname, related_fullname):
def _log_reject(reason):
LOG.debug(
'%r: Rejected related module %s of requested module %s: %s',
self, related_fullname, requested_fullname, reason,
)
return reason
try:
related_module = sys.modules[related_fullname]
except KeyError:
return _log_reject('sys.modules entry absent')
# Python 2.x "indirection entry"
if related_module is None:
return _log_reject('sys.modules entry is None')
if is_stdlib_name(related_fullname):
return _log_reject('stdlib module')
if 'six.moves' in related_fullname:
return _log_reject('six.moves avoidence')
if related_fullname in self._related_modules_denylist:
return _log_reject('on denylist')
return False
def find_related_imports(self, fullname):
"""
Return a list of non-stdlib modules that are directly imported by
@ -969,9 +1007,7 @@ class ModuleFinder(object):
set(
mitogen.core.to_text(name)
for name in maybe_names
if sys.modules.get(name) is not None
and not is_stdlib_name(name)
and u'six.moves' not in name # TODO: crap
if not self._reject_related_module(fullname, name)
)
))
@ -1134,7 +1170,7 @@ class ModuleResponder(object):
self._cache[fullname] = tup
return tup
def _send_load_module(self, stream, fullname):
def _send_load_module(self, stream, fullname, reason):
if fullname not in stream.protocol.sent_modules:
tup = self._build_tuple(fullname)
msg = mitogen.core.Message.pickled(
@ -1142,8 +1178,10 @@ class ModuleResponder(object):
dst_id=stream.protocol.remote_id,
handle=mitogen.core.LOAD_MODULE,
)
self._log.debug('sending %s (%.2f KiB) to %s',
fullname, len(msg.data) / 1024.0, stream.name)
self._log.debug(
'sending %s %s (%.2f KiB) to %s',
reason, fullname, len(msg.data) / 1024.0, stream.name,
)
self._router._async_route(msg)
stream.protocol.sent_modules.add(fullname)
if tup[2] is not None:
@ -1174,8 +1212,8 @@ class ModuleResponder(object):
# Parent hasn't been sent, so don't load submodule yet.
continue
self._send_load_module(stream, name)
self._send_load_module(stream, fullname)
self._send_load_module(stream, name, 'related')
self._send_load_module(stream, fullname, 'requested')
except Exception:
LOG.debug('While importing %r', fullname, exc_info=True)
self._send_module_load_failed(stream, fullname)

@ -1396,10 +1396,6 @@ class Connection(object):
# 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.
@ -1435,20 +1431,21 @@ class Connection(object):
os.close(r)
os.close(W)
os.close(w)
if os.uname()[0]=='Darwin'and os.uname()[2][:2]<'19'and sys.executable=='/usr/bin/python':sys.executable='/usr/bin/python2.7'
if os.uname()[0]=='Darwin'and os.uname()[2][:2]in'2021'and sys.version[:3]=='2.7':os.environ['PYTHON_LAUNCHED_FROM_WRAPPER']='1'
if os.uname()[0]+os.uname()[2][:2]+sys.executable=='Darwin19/usr/bin/python':sys.executable+='2.7'
if os.uname()[0]+os.uname()[2][:2]+sys.version[:3]=='Darwin202.7':os.environ['PYTHON_LAUNCHED_FROM_WRAPPER']='1'
if os.uname()[0]+os.uname()[2][:2]+sys.version[:3]=='Darwin212.7':os.environ['PYTHON_LAUNCHED_FROM_WRAPPER']='1'
os.environ['ARGV0']=sys.executable
os.execl(sys.executable,sys.executable+'(mitogen:CONTEXT_NAME)')
os.execl(sys.executable,sys.executable+'(mitogen:%s)'%sys.argv[2])
os.write(1,'MITO000\n'.encode())
fp=os.fdopen(0,'rb')
C=zlib.decompress(fp.read(PREAMBLE_COMPRESSED_LEN))
fp.close()
fp=os.fdopen(W,'wb',0)
fp.write(C)
fp.close()
fp=os.fdopen(w,'wb',0)
fp.write(C)
fp.close()
C=''.encode()
while int(sys.argv[3])-len(C)and select.select([0],[],[]):C+=os.read(0,int(sys.argv[3])-len(C))
C=zlib.decompress(C)
f=os.fdopen(W,'wb',0)
f.write(C)
f.close()
f=os.fdopen(w,'wb',0)
f.write(C)
f.close()
os.write(1,'MITO001\n'.encode())
os.close(2)
@ -1469,21 +1466,24 @@ class Connection(object):
source = inspect.getsource(self._first_stage)
source = textwrap.dedent('\n'.join(source.strip().split('\n')[2:]))
source = source.replace(' ', ' ')
source = source.replace('CONTEXT_NAME', self.options.remote_name)
preamble_compressed = self.get_preamble()
source = source.replace('PREAMBLE_COMPRESSED_LEN',
str(len(preamble_compressed)))
compressed = zlib.compress(source.encode(), 9)
compressor = zlib.compressobj(
zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS,
)
compressed = compressor.compress(source.encode()) + compressor.flush()
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.
# `import os,select` here (not stage 1) to save a few bytes overall.
return self.get_python_argv() + [
'-c',
'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(),),
'import sys;sys.path=[p for p in sys.path if p];'
'import binascii,os,select,zlib;'
'exec(zlib.decompress(binascii.a2b_base64(sys.argv[1]),-15))',
encoded.decode(),
self.options.remote_name,
str(len(self.get_preamble())),
]
def get_econtext_config(self):

@ -40,6 +40,7 @@ try:
except ImportError:
from pipes import quote as shlex_quote
import mitogen.core
import mitogen.parent
from mitogen.core import b

@ -44,6 +44,7 @@ import sys
import tempfile
import mitogen.core
import mitogen.parent
import mitogen.master

@ -8,6 +8,7 @@ import inspect
import sys
import zlib
import mitogen.core
import mitogen.fakessh
import mitogen.fork
import mitogen.master
@ -18,14 +19,28 @@ import mitogen.service
import mitogen.ssh
import mitogen.sudo
class Table(object):
HEADERS = (' ', 'Original', 'Minimized', 'Compressed')
HEAD_FMT = '{:20} {:^15} {:^19} {:^19}'
ROW_FMT = '%-20s %6i %5.1fKiB %5i %4.1fKiB %4.1f%% %5i %4.1fKiB %4.1f%%'
def header(self):
return self.HEAD_FMT.format(*self.HEADERS)
router = mitogen.master.Router()
context = mitogen.parent.Context(router, 0)
options = mitogen.ssh.Options(max_message_size=0, hostname='foo')
options = mitogen.ssh.Options(
hostname='foo',
max_message_size=0,
remote_name='alice@host:1234',
)
conn = mitogen.ssh.Connection(options, router)
conn.context = context
print('SSH command size: %s' % (len(' '.join(conn.get_boot_command())),))
print('Bootstrap (mitogen.core) size: %s (%.2fKiB)' % (
print('Preamble (mitogen.core + econtext) size: %s (%.2fKiB)' % (
len(conn.get_preamble()),
len(conn.get_preamble()) / 1024.0,
))
@ -36,17 +51,10 @@ if '--dump' in sys.argv:
exit()
print(
' '
' '
' Original '
' '
' Minimized '
' '
' Compressed '
)
table = Table()
print(table.header())
for mod in (
mitogen.core,
mitogen.parent,
mitogen.fork,
mitogen.ssh,
@ -63,13 +71,7 @@ for mod in (
compressed = zlib.compress(minimized.encode(), 9)
compressed_size = len(compressed)
print(
'%-25s'
' '
'%5i %4.1fKiB'
' '
'%5i %4.1fKiB %.1f%%'
' '
'%5i %4.1fKiB %.1f%%'
table.ROW_FMT
% (
mod.__name__,
original_size,

@ -79,19 +79,21 @@ setup(
long_description = long_description(),
long_description_content_type='text/markdown',
author = 'David Wilson',
license = 'New BSD',
license = 'BSD-3-Clause',
url = 'https://github.com/mitogen-hq/mitogen/',
packages = find_packages(exclude=['tests', 'examples']),
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*',
python_requires='>=2.4, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*',
zip_safe = False,
classifiers = [
'Environment :: Console',
'Framework :: Ansible',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Programming Language :: Python',
'Programming Language :: Python :: 2.4',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
@ -102,6 +104,7 @@ setup(
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: System :: Distributed Computing',
'Topic :: System :: Systems Administration',

@ -76,6 +76,7 @@ ssh_args =
-o ControlPersist=60s
-o ForwardAgent=yes
-o HostKeyAlgorithms=+ssh-rsa
-o KexAlgorithms=+diffie-hellman-group1-sha1
-o PubkeyAcceptedKeyTypes=+ssh-rsa
-o UserKnownHostsFile=/dev/null
pipelining = True

@ -6,6 +6,7 @@
ansible_version_major_minor: "{{ ansible_version.major }}.{{ ansible_version.minor }}"
ansible_version_major_minor_patch: "{{ ansible_version.major }}.{{ ansible_version.minor }}.{{ ansible_version.revision | regex_search('^[0-9]+') }}"
become_doas_available: false
become_unpriv_available: >-
{#
Vanilla Ansible >= 4 (ansible-core >= 2.11) can use `setfacl` for
@ -34,3 +35,11 @@ become_unpriv_available: >-
-}}
pkg_mgr_python_interpreter: python
virtualenv_create_argv:
- virtualenv
- -p
- "{{ virtualenv_python }}"
- "{{ virtualenv_path }}"
virtualenv_path: /path/intentionally/left/invalid
virtualenv_python: /path/intentionally/left/invalid

@ -0,0 +1,8 @@
pkg_mgr_python_interpreter: python3
# Alma Linux 9, RHEL 9, etc. lack a virtualenv package
virtualenv_create_argv:
- "{{ virtualenv_python }}"
- -m
- venv
- "{{ virtualenv_path }}"

@ -1,3 +1,4 @@
become_doas_available: true
package_manager_keys:
- src: debian-archive-bullseye-automatic.gpg # Debian 11
dest: /etc/apt/trusted.gpg.d/debian-archive-bullseye-automatic.gpg

@ -0,0 +1,2 @@
become_doas_available: true
pkg_mgr_python_interpreter: python3

@ -1,4 +1,6 @@
package_manager_repos:
- dest: /etc/apt/sources.list
content: |
deb http://archive.debian.org/debian stretch main contrib non-free
deb http://archive.debian.org/debian/ stretch main contrib non-free
deb http://archive.debian.org/debian/ stretch-proposed-updates main contrib non-free
deb http://archive.debian.org/debian-security stretch/updates main contrib non-free

@ -0,0 +1,2 @@
become_doas_available: true
pkg_mgr_python_interpreter: python3

@ -0,0 +1,2 @@
become_doas_available: true
pkg_mgr_python_interpreter: python3

@ -15,12 +15,16 @@
changed_when: false
check_mode: false
register: doas_default_user
when:
- become_doas_available
- assert:
that:
- doas_default_user.stdout == 'root'
fail_msg:
doas_default_user={{ doas_default_user }}
when:
- become_doas_available
- name: Test doas -> mitogen__user1
become: true
@ -30,6 +34,7 @@
check_mode: false
register: doas_mitogen__user1
when:
- become_doas_available
- become_unpriv_available
- assert:
@ -38,6 +43,7 @@
fail_msg:
doas_mitogen__user1={{ doas_mitogen__user1 }}
when:
- become_doas_available
- become_unpriv_available
tags:
- doas
@ -61,12 +67,16 @@
changed_when: false
check_mode: false
register: fq_doas_default_user
when:
- become_doas_available
- assert:
that:
- fq_doas_default_user.stdout == 'root'
fail_msg:
fq_doas_default_user={{ fq_doas_default_user }}
when:
- become_doas_available
- name: Test community.general.doas -> mitogen__user1
become: true
@ -76,6 +86,7 @@
check_mode: false
register: fq_doas_mitogen__user1
when:
- become_doas_available
- become_unpriv_available
- assert:
@ -84,6 +95,7 @@
fail_msg:
fq_doas_mitogen__user1={{ fq_doas_mitogen__user1 }}
when:
- become_doas_available
- become_unpriv_available
tags:
- doas

@ -47,6 +47,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -75,6 +76,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],

@ -84,6 +84,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -128,6 +129,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -183,6 +185,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -227,6 +230,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -255,6 +259,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -309,6 +314,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
@ -354,6 +360,7 @@
-o, ControlPersist=60s,
-o, ForwardAgent=yes,
-o, HostKeyAlgorithms=+ssh-rsa,
-o, KexAlgorithms=+diffie-hellman-group1-sha1,
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],

@ -1,58 +1,20 @@
# ripped and ported from https://github.com/ansible/ansible/pull/50163/files, when interpreter discovery was added to ansible
---
- name: integration/interpreter_discovery/ansible_2_8_tests.yml, baseline
hosts: test-targets
strategy: linear
tasks:
- meta: clear_facts
- name: Discover interpreter, linear, auto
vars:
ansible_python_interpreter: auto
ping:
register: linear_auto_result
- name: integration/interpreter_discovery/ansible_2_8_tests.yml
hosts: test-targets
gather_facts: true
vars:
DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_lt_2_12:
centos:
'6': /usr/bin/python
'7': /usr/bin/python
'8': /usr/libexec/platform-python
debian:
'9': /usr/bin/python
'10': /usr/bin/python3
'11': /usr/bin/python
'NA': /usr/bin/python # Debian 11, Ansible <= 7 (ansible-core <= 2.14)
'bullseye/sid': /usr/bin/python # Debian 11, Ansible 8 - 9 (ansible-core 2.15 - 2.16)
ubuntu:
'16': /usr/bin/python3
'18': /usr/bin/python3
'20': /usr/bin/python3
DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_2_12_to_2_16:
centos:
'6': /usr/bin/python
'7': /usr/bin/python
'8': /usr/libexec/platform-python
debian:
'9': /usr/bin/python
'10': /usr/bin/python3
'11': /usr/bin/python3.9
'NA': /usr/bin/python3.9 # Debian 11, Ansible <= 7 (ansible-core <= 2.14)
'bullseye/sid': /usr/bin/python3.9 # Debian 11, Ansible 8 - 9 (ansible-core 2.15 - 2.16)
ubuntu:
'16': /usr/bin/python3
'18': /usr/bin/python3
'20': /usr/bin/python3
DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_ge_2_17:
debian:
'10': /usr/bin/python3.7
'11': /usr/bin/python3.9
'bullseye/sid': /usr/bin/python3.9
ubuntu:
'20': /usr/bin/python3.8
discovered_interpreter_expected: >-
{%- if ansible_version_major_minor is version('2.12', '<', strict=True) -%}
{{ DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_lt_2_12[distro][distro_major] }}
{%- elif ansible_version_major_minor is version('2.17', '<', strict=True) -%}
{{ DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_2_12_to_2_16[distro][distro_major] }}
{%- else -%}
{{ DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_ge_2_17[distro][distro_major] }}
{%- endif -%}
tasks:
- name: can only run these tests on ansible >= 2.8.0
block:
@ -65,12 +27,6 @@
fail_msg: "'ansible_python_interpreter' appears to be set at a high precedence to {{ ansible_python_interpreter }},
which breaks this test."
- name: snag some facts to validate for later
set_fact:
distro: '{{ ansible_facts.distribution | lower }}'
distro_major: '{{ ansible_facts.distribution_major_version }}'
system: '{{ ansible_facts.system }}'
- name: test that python discovery is working and that fact persistence makes it only run once
block:
- name: clear facts to force interpreter discovery to run
@ -215,16 +171,10 @@
- name: Check discovered interpreter matches expected
assert:
that:
- auto_out.ansible_facts.discovered_interpreter_python == discovered_interpreter_expected
- auto_out.ansible_facts.discovered_interpreter_python == linear_auto_result.ansible_facts.discovered_interpreter_python
fail_msg: |
distro={{ distro }}
distro_major= {{ distro_major }}
system={{ system }}
auto_out={{ auto_out }}
discovered_interpreter_expected={{ discovered_interpreter_expected }}
ansible_version.full={{ ansible_version.full }}
when:
- system in ['Linux']
linear_auto_result={{ linear_auto_result }}
always:
- meta: clear_facts

@ -14,7 +14,7 @@
ANSIBLE_CALLBACK_RESULT_FORMAT=json
ANSIBLE_LOAD_CALLBACK_PLUGINS=false
ANSIBLE_STRATEGY=mitogen_linear
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o KexAlgorithms=+diffie-hellman-group1-sha1 -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_VERBOSITY="{{ ansible_verbosity }}"
ansible -m shell -c local -a whoami
{% for inv in ansible_inventory_sources %}

@ -35,7 +35,7 @@
ssh_no_password_result.msg is search('SSH password was requested, but none specified')
or ssh_no_password_result.msg is search('SSH password is incorrect')
or ssh_no_password_result.msg is search('Invalid/incorrect password')
or ssh_no_password_result.msg is search('Permission denied \(publickey,password(,keyboard-interactive)?\)')
or ssh_no_password_result.msg is search('Permission denied \(publickey(,gssapi-keyex)?(,gssapi-with-mic)?,password(,keyboard-interactive)?\)')
fail_msg: |
ssh_no_password_result={{ ssh_no_password_result }}
@ -72,6 +72,6 @@
- >-
ssh_wrong_password_result.msg is search('SSH password is incorrect')
or ssh_wrong_password_result.msg is search('Invalid/incorrect password')
or ssh_wrong_password_result.msg is search('Permission denied \(publickey,password(,keyboard-interactive)?\)')
or ssh_no_password_result.msg is search('Permission denied \(publickey(,gssapi-keyex)?(,gssapi-with-mic)?,password(,keyboard-interactive)?\)')
fail_msg: |
ssh_wrong_password_result={{ ssh_wrong_password_result }}

@ -22,7 +22,7 @@
ANSIBLE_CALLBACK_RESULT_FORMAT=json
ANSIBLE_LOAD_CALLBACK_PLUGINS=false
ANSIBLE_STRATEGY=mitogen_linear
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o KexAlgorithms=+diffie-hellman-group1-sha1 -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_VERBOSITY="{{ ansible_verbosity }}"
ansible -m shell -a whoami
{% for inv in ansible_inventory_sources %}
@ -42,7 +42,7 @@
ANSIBLE_CALLBACK_RESULT_FORMAT=json
ANSIBLE_LOAD_CALLBACK_PLUGINS=false
ANSIBLE_STRATEGY=mitogen_linear
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o KexAlgorithms=+diffie-hellman-group1-sha1 -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_VERBOSITY="{{ ansible_verbosity }}"
ansible -m shell -a whoami
{% for inv in ansible_inventory_sources %}

@ -8,9 +8,12 @@
- name: regression/issue_122__environment_difference.yml
hosts: test-targets
tasks:
- script: scripts/print_env.py
register: env
- debug: msg={{env}}
- name: Run print_env.py
script:
cmd: scripts/print_env.py
executable: "{{ ansible_python_interpreter | default(ansible_facts.discovered_interpreter_python) }}"
register: print_env_result
- debug:
var: print_env_result
tags:
- issue_122

@ -1,26 +1,29 @@
- name: regression/issue_152__virtualenv_python_fails.yml
gather_facts: true
hosts: test-targets
vars:
virtualenv_path: /tmp/issue_152_virtualenv
virtualenv_python: "{{ ansible_facts.python.executable }}"
tasks:
- custom_python_detect_environment:
register: lout
# Can't use pip module because it can't create virtualenvs, must call it
# directly.
- name: Create /tmp/issue_152_virtualenv
- name: Create temporary virtualenv
environment:
https_proxy: "{{ lookup('env', 'https_proxy')|default('') }}"
no_proxy: "{{ lookup('env', 'no_proxy')|default('') }}"
PATH: "{{ lookup('env', 'PATH') }}"
command:
cmd: virtualenv -p "{{ ansible_facts.python.executable }}" /tmp/issue_152_virtualenv
creates: /tmp/issue_152_virtualenv
argv: "{{ virtualenv_create_argv }}"
creates: "{{ virtualenv_path }}"
when:
- lout.python.version.full is version('2.7', '>=', strict=True)
- custom_python_detect_environment:
vars:
ansible_python_interpreter: /tmp/issue_152_virtualenv/bin/python
ansible_python_interpreter: "{{ virtualenv_path }}/bin/python"
register: out
when:
- lout.python.version.full is version('2.7', '>=', strict=True)
@ -28,7 +31,7 @@
- name: Check virtualenv was used
# On macOS runners a symlink /tmp -> /private/tmp has been seen
vars:
requested_executable: /tmp/issue_152_virtualenv/bin/python
requested_executable: "{{ virtualenv_path }}/bin/python"
expected_executables:
- "{{ requested_executable }}"
- "{{ requested_executable.replace('/tmp', out.fs['/tmp'].resolved) }}"
@ -40,9 +43,9 @@
when:
- lout.python.version.full is version('2.7', '>=', strict=True)
- name: Cleanup /tmp/issue_152_virtualenv
- name: Cleanup temporary virtualenv
file:
path: /tmp/issue_152_virtualenv
path: "{{ virtualenv_path }}"
state: absent
when:
- lout.python.version.full is version('2.7', '>=', strict=True)

@ -9,6 +9,8 @@
netconf_container_image: ghcr.io/mitogen-hq/sysrepo-netopeer2:latest
netconf_container_name: sysrepo
netconf_container_port: 8030
# https://github.com/ansible-collections/ansible.netcommon/issues/698#issuecomment-2910082548
ansible_network_import_modules: "{{ (ansible_version_major_minor is version('2.19', '==', strict=True)) | bool }}"
tasks:
- meta: end_play
@ -28,13 +30,6 @@
- ansible_version_major_minor is version('2.11', '>=', strict=True)
- ansible_version_major_minor is version('2.12', '<', strict=True)
- meta: end_play
when:
# TASK [Get running configuration and state data ]
# Error: : Task failed: ActionBase._parse_returned_data() missing 1 required positional argument: 'profile'
# https://github.com/ansible-collections/ansible.netcommon/issues/698#issuecomment-2910082548
- ansible_version_major_minor is version('2.19', '>=', strict=True)
- block:
- name: Start container
command:
@ -63,6 +58,9 @@
ansible.netcommon.netconf_get:
always:
- name: Close connections
meta: reset_connection
- name: Cleanup container
command:
cmd: podman stop "{{ netconf_container_name }}"

@ -1,5 +1,6 @@
paramiko==2.12.0; python_version <= '2.7'
paramiko==3.5.0; python_version >= '3.6'
paramiko==3.5.1; python_version >= '3.6' and python_version <= '3.8'
paramiko==4.0.0; python_version >= '3.9'
# Incompatible with pip >= 72, due to removal of `setup.py test`:
# ModuleNotFoundError: No module named 'setuptools.command.test'
@ -7,7 +8,8 @@ paramiko==3.5.0; python_version >= '3.6'
hdrhistogram==0.6.1
ncclient==0.6.13; python_version <= '2.7'
ncclient==0.6.16; python_version > '2.7'
ncclient==0.6.19; python_version >= '3.5'
PyYAML==3.11; python_version < '2.7'
PyYAML==5.3.1; python_version >= '2.7' # Latest release (Jan 2021)
PyYAML==5.4.1; python_version >= '2.7' and python_version <= '3.7'
PyYAML==6.0.2; python_version >= '3.8'

@ -13,3 +13,23 @@
- debug: {var: ansible_facts.osversion}
- debug: {var: ansible_facts.python}
- debug: {var: ansible_facts.system}
- name: Check target versions
hosts: localhost:test-targets
check_mode: false
tasks:
- name: Get command versions
command:
cmd: "{{ item.cmd }}"
changed_when: false
check_mode: false
loop:
- cmd: sudo -V
register: command_versions
- name: Show command versions
debug:
msg: |
cmd: {{ item.item.cmd }}
{{ item.stdout }}
loop: "{{ command_versions.results }}"

@ -10,7 +10,7 @@ BIG_MODULE_PATH="$(dirname -- "$0")/data/big_module.py"
IMPORTS="from collections import deque; from mitogen.imports import $BENCH_FUNC"
COMPILE="co=compile(open('$BIG_MODULE_PATH').read(), '$BIG_MODULE_PATH', 'exec')"
PYTHONS=(
python2.7 python3.9 python3.10 python3.11 python3.12 python3.13
python2.7 python3.{9..14}
)
for p in "${PYTHONS[@]}"; do
echo -e -n "$BENCH_FUNC $p "

@ -1,7 +1,6 @@
import pickle
import mitogen.core
from mitogen.core import b
import testlib

@ -12,8 +12,12 @@ class MyError(Exception):
def get_sentinel_value():
# Some proof we're even talking to the mitogen-test Docker image
with open('/etc/sentinel', 'rb') as f:
return f.read().decode()
f = open('/etc/sentinel', 'rb')
try:
value = f.read().decode()
finally:
f.close()
return value
def add(x, y):

@ -3,9 +3,24 @@ import os
import sys
def _shout_stdout_py3(size):
nwritten = sys.stdout.write('A' * size)
return nwritten
def _shout_stdout_py2(size):
shout = 'A' * size
nwritten = 0
while nwritten < size:
nwritten += os.write(sys.stdout.fileno(), shout[-nwritten:])
return nwritten
def shout_stdout(size):
sys.stdout.write('A' * size)
return 'success'
if sys.version_info > (3, 0):
return _shout_stdout_py3(size)
else:
return _shout_stdout_py2(size)
def file_is_blocking(fobj):

@ -1,5 +1,6 @@
import subprocess
import mitogen.core
import mitogen.parent
from mitogen.core import b
@ -21,14 +22,18 @@ class CommandLineTest(testlib.RouterMixin, testlib.TestCase):
conn.context = mitogen.core.Context(None, 123)
args = conn.get_boot_command()
# Executing the boot command will print "EC0" and expect to read from
# stdin, which will fail because it's pointing at /dev/null, causing
# the forked child to crash with an EOFError and disconnect its write
# pipe. The forked and freshly execed parent will get a 0-byte read
# from the pipe, which is a valid script, and therefore exit indicating
# success.
# The boot command should write an ECO marker to stdout, read the
# preamble from stdin, then execute it.
fp = open("/dev/null", "r")
# This test attaches /dev/zero to stdin to create a specific failure
# 1. Fork child reads <compressed preamble size> bytes of NUL (`b'\0'`)
# 2. Fork child crashes (trying to decompress the junk data)
# 3. Fork child's file descriptors (write pipes) are closed by the OS
# 4. Fork parent does `dup(<read pipe>, <stdin>)` and `exec(<python>)`
# 5. Python reads `b''` (i.e. EOF) from stdin (a closed pipe)
# 6. Python runs `''` (a valid script) and exits with success
fp = open("/dev/zero", "r")
try:
proc = subprocess.Popen(args,
stdin=fp,
@ -39,6 +44,9 @@ class CommandLineTest(testlib.RouterMixin, testlib.TestCase):
self.assertEqual(0, proc.returncode)
self.assertEqual(stdout,
mitogen.parent.BootstrapProtocol.EC0_MARKER+b('\n'))
self.assertIn(b("Error -5 while decompressing data"), stderr)
self.assertIn(
b("Error -3 while decompressing data"), # Unknown compression method
stderr,
)
finally:
fp.close()

@ -3,18 +3,30 @@
strategy: mitogen_free
gather_facts: false
tasks:
- name: Fetch container images
docker_image:
name: "{{ docker_base }}"
delegate_to: localhost
- name: Start containers
docker_container:
name: "{{ inventory_hostname }}"
image: "{{ docker_base }}"
command: /bin/bash
hostname: "mitogen-{{ inventory_hostname }}"
etc_hosts:
centos-vault-proxy: host-gateway
detach: true
interactive: true
tty: true
delegate_to: localhost
- name: Wait for containers
# Can't use wait_for_connection yet, not all base images have a python
command: >-
docker inspect
--format "{% raw %}{{.State.Running}}{% endraw %}"
"{{ inventory_hostname }}"
register: container_inspect_result
retries: 5
delay: 10
until:
- container_inspect_result is succeeded
- container_inspect_result.stdout == "true"
changed_when: false
delegate_to: localhost

@ -1,7 +1,7 @@
- name: Prepare images
hosts: all
strategy: mitogen_free
gather_facts: true
gather_facts: false
tasks:
- name: Commit containers
command: >
@ -10,9 +10,11 @@
--change 'CMD ["/usr/sbin/sshd", "-D"]'
{{ inventory_hostname }}
{{ container_image_name }}
changed_when: true
delegate_to: localhost
- name: Stop containers
command: >
docker rm -f {{ inventory_hostname }}
changed_when: true
delegate_to: localhost

@ -0,0 +1,5 @@
- name: Setup container host
hosts: localhost
become: true
roles:
- role: container_host

@ -2,113 +2,44 @@
hosts: all
strategy: linear
gather_facts: false
tasks:
- name: Install bootstrap packages
raw: |
set -o errexit
set -o nounset
if type -p yum; then
yum -y install {{ bootstrap_packages | join(' ') }}
else
apt-get -y update
apt-get -y --no-install-recommends install {{ bootstrap_packages | join(' ') }}
fi
when: bootstrap_packages | length
roles:
- role: bootstrap
- name: Setup containers
hosts: all
strategy: mitogen_free
# Resource limitation, my laptop freezes doing every container concurrently
serial: 4
# Can't gather facts before here.
gather_facts: true
vars:
distro: "{{ansible_distribution}}"
pre_tasks:
- meta: end_play
when:
- ansible_facts.virtualization_type != "docker"
roles:
- role: package_manager
- role: packages
- role: sshd
- role: sshd_container
tasks:
- name: Ensure requisite apt packages are installed
apt:
name: "{{ common_packages + packages }}"
state: present
install_recommends: false
update_cache: true
when: ansible_pkg_mgr == 'apt'
- name: Ensure requisite yum packages are installed
yum:
name: "{{ common_packages + packages }}"
state: present
update_cache: true
when: ansible_pkg_mgr == 'yum'
- name: Ensure requisite dnf packages are installed
dnf:
name: "{{ common_packages + packages }}"
state: present
update_cache: true
when: ansible_pkg_mgr == 'dnf'
- name: Clean up package cache
vars:
clean_command:
apt: apt-get clean
yum: yum clean all
dnf: dnf clean all
command: "{{ clean_command[ansible_pkg_mgr] }}"
args:
warn: "{{ False if ansible_version_major_minor is version('2.10', '<=', strict=True) else omit }}"
- name: Clean up apt package lists
shell: rm -rf {{item}}/*
with_items:
- /var/cache/apt
- /var/lib/apt/lists
when: ansible_pkg_mgr == 'apt'
- name: Configure /usr/bin/python
command: alternatives --set python /usr/bin/python3.8
args:
creates: /usr/bin/python
when: inventory_hostname in ["centos8"]
- name: Enable UTF-8 locale on Debian
copy:
dest: /etc/locale.gen
content: |
en_US.UTF-8 UTF-8
fr_FR.UTF-8 UTF-8
mode: u=rw,go=r
when: ansible_pkg_mgr == 'apt'
- name: Generate UTF-8 locale on Debian
shell: locale-gen
command:
cmd: locale-gen
changed_when: true
when: ansible_pkg_mgr == 'apt'
- name: Write Unicode into /etc/environment
copy:
dest: /etc/environment
content: "UNICODE_SNOWMAN=\u2603\n"
- name: Install prebuilt 'doas' binary
unarchive:
dest: /
src: ../data/docker/doas-debian.tar.gz
- name: Make prebuilt 'doas' binary executable
file:
path: /usr/local/bin/doas
mode: 'u=rwxs,go=rx'
owner: root
group: root
mode: u=rw,go=r
- name: Install doas.conf
copy:
@ -116,6 +47,7 @@
content: |
permit :mitogen__group
permit :root
mode: u=rw,go=
- name: Set root user password and shell
user:
@ -127,6 +59,7 @@
file:
path: /var/run/sshd
state: directory
mode: u=rwx,go=rx
- name: Generate SSH host key
command: ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
@ -142,6 +75,7 @@
dest: /etc/sentinel
content: |
i-am-mitogen-test-docker-image
mode: u=rw,go=r
- name: Ensure /etc/sudoers.d exists
file:

@ -157,15 +157,14 @@
owner: mitogen__has_sudo_pubkey
group: mitogen__group
- name: Configure sudoers defaults
blockinfile:
path: /etc/sudoers
marker: "# {mark} Mitogen test defaults"
block: |
Defaults>mitogen__pw_required targetpw
Defaults>mitogen__require_tty requiretty
Defaults>mitogen__require_tty_pw_required requiretty,targetpw
- name: Configure sudoers
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: ug=r,o=
validate: '/usr/sbin/visudo -cf %s'
with_items:
- {src: sudoers_defaults, dest: /etc/sudoers.d/mitogen_test_defaults}
- name: Configure sudoers users
blockinfile:
@ -182,6 +181,6 @@
{% endfor %}
validate: '/usr/sbin/visudo -cf %s'
when:
- ansible_virtualization_type != "docker"
- ansible_connection == "local"
roles:
- role: user_policies

@ -1,12 +1,17 @@
[defaults]
any_errors_fatal = true
# Ansible >= 6 (ansible-core >= 2.13)
callback_result_format = yaml
deprecation_warnings = false
duplicate_dict_key = error
inventory = hosts.ini
strategy_plugins = ../../ansible_mitogen/plugins/strategy
retry_files_enabled = false
display_args_to_stdout = True
no_target_syslog = True
host_key_checking = False
stdout_callback = yaml
[inventory]
unparsed_is_fatal = true
any_unparsed_is_failed = true
host_pattern_mismatch = error
unparsed_is_failed = true

@ -0,0 +1,33 @@
DefaultRuntimeDir ${XDG_RUNTIME_DIR}
PidFile ${XDG_RUNTIME_DIR}/apache2.pid
LoadModule alias_module /usr/lib/apache2/modules/mod_alias.so
LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so
LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.so
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
KeepAlive On
Listen 8090
<Directory />
Require all denied
AllowOverride None
</Directory>
<VirtualHost *:8090>
ServerName centos-vault-proxy
SSLProxyEngine On
CustomLog logs/access.log vhost_combined
ProxyPass "/" "https://vault.centos.org/"
ProxyPassReverse "https://vault.centos.org/" "/"
RedirectMatch "^/(.*)" "http://centos-vault-proxy:8090/$1"
</VirtualHost>
# /usr/sbin/apache2 -d . -f apache_proxy.conf -D FOREGROUND
# vim: syntax=apache

@ -0,0 +1,7 @@
# Testing non-blocking stdio during bootstrap
# https://github.com/mitogen-hq/mitogen/issues/1306
Defaults log_output
Defaults>mitogen__pw_required targetpw
Defaults>mitogen__require_tty requiretty
Defaults>mitogen__require_tty_pw_required requiretty,targetpw

@ -1,16 +1,18 @@
ansible_version_major_minor: "{{ ansible_version.major }}.{{ ansible_version.minor }}"
common_packages:
- acl
- openssh-server
- rsync
- strace
- sudo
container_image_name: "{{ container_registry }}/{{ inventory_hostname }}-test"
container_registry: public.ecr.aws/n5z0e8q9
container_registry: ghcr.io/mitogen-hq
sudo_group:
MacOSX: admin
Debian: sudo
Ubuntu: sudo
CentOS: wheel
AlmaLinux: wheel

@ -0,0 +1,6 @@
bootstrap_packages: [python3]
docker_base: almalinux:9
packages:
- perl-JSON
- procps-ng

@ -1,6 +1,36 @@
bootstrap_packages: [python-simplejson]
docker_base: astj/centos5-vault
docker_base: centos:5
packages:
- perl
package_manager_repos:
- dest: /etc/yum.repos.d/CentOS-Base.repo
content: |
[base]
name=CentOS-$releasever - Base
baseurl=http://centos-vault-proxy:8090/5.11/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5
[updates]
name=CentOS-$releasever - Updates
baseurl=http://centos-vault-proxy:8090/5.11/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5
[extras]
name=CentOS-$releasever - Extras
baseurl=http://centos-vault-proxy:8090/5.11/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5
- dest: /etc/yum.repos.d/libselinux.repo
content: |
[libselinux]
name=CentOS-$releasever - libselinux
baseurl=http://centos-vault-proxy:8090/5.11/centosplus/$basearch/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5
includepkgs=libselinux*

@ -1,6 +1,27 @@
bootstrap_packages: [python]
docker_base: moreati/centos6-vault
docker_base: centos:6
packages:
- perl-JSON
package_manager_repos:
- dest: /etc/yum.repos.d/CentOS-Base.repo
content: |
[base]
name=CentOS-$releasever - Base
baseurl=http://vault.centos.org/6.10/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
[updates]
name=CentOS-$releasever - Updates
baseurl=http://vault.centos.org/6.10/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
[extras]
name=CentOS-$releasever - Extras
baseurl=http://vault.centos.org/6.10/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

@ -6,3 +6,24 @@ packages:
- perl-JSON
- python-virtualenv
- python3
package_manager_repos:
- dest: /etc/yum.repos.d/CentOS-Base.repo
content: |
[base]
name=CentOS-$releasever - Base
baseurl=http://vault.centos.org/$contentdir/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[updates]
name=CentOS-$releasever - Updates
baseurl=http://vault.centos.org/$contentdir/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[extras]
name=CentOS-$releasever - Extras
baseurl=http://vault.centos.org/$contentdir/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

@ -6,5 +6,29 @@ packages:
- perl-JSON
- python2-virtualenv
- python3-virtualenv
- python36
- python38
package_manager_repos:
- dest: /etc/yum.repos.d/CentOS-Linux-AppStream.repo
content: |
[appstream]
name=CentOS Linux $releasever - AppStream
baseurl=http://vault.centos.org/$contentdir/$releasever/AppStream/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
- dest: /etc/yum.repos.d/CentOS-Linux-BaseOS.repo
content: |
[baseos]
name=CentOS Linux $releasever - BaseOS
baseurl=http://vault.centos.org/$contentdir/$releasever/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
- dest: /etc/yum.repos.d/CentOS-Linux-Extras.repo
content: |
[extras]
name=CentOS Linux $releasever - Extras
baseurl=http://vault.centos.org/$contentdir/$releasever/extras/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial

@ -1,4 +1,4 @@
bootstrap_packages: [python]
bootstrap_packages: [python, python-apt]
docker_base: debian:10
@ -9,3 +9,11 @@ packages:
- python3
- python3-virtualenv
- virtualenv
package_manager_repos:
- dest: /etc/apt/sources.list
content: |
deb http://archive.debian.org/debian/ buster main non-free contrib
deb http://archive.debian.org/debian/ buster-updates main non-free contrib
deb http://archive.debian.org/debian/ buster-proposed-updates main non-free contrib
deb http://security.debian.org/ buster/updates main non-free contrib

@ -1,11 +1,18 @@
bootstrap_packages: [python3, python3-apt]
docker_base: debian:bullseye
docker_base: debian:11
packages:
- doas
- libjson-perl
- locales
- python-is-python3
- python2
- python3-virtualenv
- virtualenv
package_manager_keys:
- src: debian-archive-bullseye-automatic.gpg # Debian 11
dest: /etc/apt/trusted.gpg.d/
- src: debian-archive-bookworm-automatic.gpg # Debian 12
dest: /etc/apt/trusted.gpg.d/

@ -0,0 +1,8 @@
bootstrap_packages: [python3, python3-apt]
docker_base: debian:12
packages:
- libjson-perl
- locales
- opendoas
- virtualenv

@ -1,4 +1,4 @@
bootstrap_packages: [python]
bootstrap_packages: [python, python-apt]
docker_base: debian:9
@ -9,3 +9,10 @@ packages:
- python3
- python3-virtualenv
- virtualenv
package_manager_repos:
- dest: /etc/apt/sources.list
content: |
deb http://archive.debian.org/debian/ stretch main contrib non-free
deb http://archive.debian.org/debian/ stretch-proposed-updates main contrib non-free
deb http://archive.debian.org/debian-security stretch/updates main contrib non-free

@ -1,4 +1,4 @@
bootstrap_packages: [python]
bootstrap_packages: [python, python-apt]
docker_base: ubuntu:16.04

@ -1,4 +1,4 @@
bootstrap_packages: [python]
bootstrap_packages: [python, python-apt]
docker_base: ubuntu:18.04

@ -1,4 +1,4 @@
bootstrap_packages: [python3]
bootstrap_packages: [python3, python3-apt]
docker_base: ubuntu:20.04

@ -0,0 +1,10 @@
bootstrap_packages: [python3, python3-apt]
docker_base: ubuntu:22.04
packages:
- doas
- libjson-perl
- locales
- python2
- python3-virtualenv
- virtualenv

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

Loading…
Cancel
Save