From c9eb6e54e29bfcf356f54d6cb9a827cbcfac14b9 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Sat, 22 Nov 2025 08:49:14 +0000 Subject: [PATCH 01/35] Begin 0.3.34dev --- mitogen/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mitogen/__init__.py b/mitogen/__init__.py index 2d0af387..8e1d2ec3 100644 --- a/mitogen/__init__.py +++ b/mitogen/__init__.py @@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup. #: Library version as a tuple. -__version__ = (0, 3, 33) +__version__ = (0, 3, 34, 'dev') #: This is :data:`False` in slave contexts. Previously it was used to prevent From 09b972e96e5f16f48ae5a0b4b79ee31630287f10 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 16:48:41 +0000 Subject: [PATCH 02/35] ci: Fix ansible-lint complaints in image prep playbooks --- tests/image_prep/_container_finalize.yml | 4 +++- tests/image_prep/_container_setup.yml | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/image_prep/_container_finalize.yml b/tests/image_prep/_container_finalize.yml index 5329fefa..7e090870 100644 --- a/tests/image_prep/_container_finalize.yml +++ b/tests/image_prep/_container_finalize.yml @@ -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 diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index b95d67a9..55e21efa 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -87,16 +87,20 @@ 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" + mode: u=rw,go=r - name: Install prebuilt 'doas' binary unarchive: @@ -116,6 +120,7 @@ content: | permit :mitogen__group permit :root + mode: u=rw,go= - name: Set root user password and shell user: @@ -127,6 +132,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 +148,7 @@ dest: /etc/sentinel content: | i-am-mitogen-test-docker-image + mode: u=rw,go=r - name: Ensure /etc/sudoers.d exists file: From d1c4217db0ffd97ff543a56466f42c147f4662f3 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 16:49:47 +0000 Subject: [PATCH 03/35] ci: Wait for fresh image prep containers to start --- tests/image_prep/_container_create.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/image_prep/_container_create.yml b/tests/image_prep/_container_create.yml index b07c46eb..a3e8385f 100644 --- a/tests/image_prep/_container_create.yml +++ b/tests/image_prep/_container_create.yml @@ -18,3 +18,18 @@ 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 From 780f8af1a4c1b8c5b0de30412cb8dcdaa809af27 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 17:08:50 +0000 Subject: [PATCH 04/35] ci: Factor out image prep bootstrap as a role Promoting the script to a full template will fix some whitespace errors later. --- tests/image_prep/_container_setup.yml | 14 ++------------ tests/image_prep/roles/bootstrap/defaults/main.yml | 1 + tests/image_prep/roles/bootstrap/tasks/main.yml | 3 +++ .../roles/bootstrap/templates/bootstrap.sh.j2 | 13 +++++++++++++ 4 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 tests/image_prep/roles/bootstrap/defaults/main.yml create mode 100644 tests/image_prep/roles/bootstrap/tasks/main.yml create mode 100644 tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index 55e21efa..d894cd25 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -2,18 +2,8 @@ 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 diff --git a/tests/image_prep/roles/bootstrap/defaults/main.yml b/tests/image_prep/roles/bootstrap/defaults/main.yml new file mode 100644 index 00000000..91e2fe57 --- /dev/null +++ b/tests/image_prep/roles/bootstrap/defaults/main.yml @@ -0,0 +1 @@ +bootstrap_packages: [] diff --git a/tests/image_prep/roles/bootstrap/tasks/main.yml b/tests/image_prep/roles/bootstrap/tasks/main.yml new file mode 100644 index 00000000..5ecaa49d --- /dev/null +++ b/tests/image_prep/roles/bootstrap/tasks/main.yml @@ -0,0 +1,3 @@ +- name: Bootstrap + raw: "{{ lookup('template', 'bootstrap.sh.j2') }}" + changed_when: true diff --git a/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 b/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 new file mode 100644 index 00000000..daffa178 --- /dev/null +++ b/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 @@ -0,0 +1,13 @@ +set -o errexit +set -o nounset + +{% if bootstrap_packages %} +if command -v apt-get; then + apt-get -y update + apt-get -y --no-install-recommends install {{ bootstrap_packages | join(' ') }} +elif command -v yum; then + yum -y install {{ bootstrap_packages | join(' ') }} +else + exit 42 +fi +{% endif %} From bcc726d3b745bad20b206a90b5139cceb6bc3eba Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 17:25:09 +0000 Subject: [PATCH 05/35] ci: Handle dnf packages in bootstrap role --- tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 b/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 index daffa178..c5a35a2b 100644 --- a/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 +++ b/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 @@ -5,6 +5,8 @@ set -o nounset if command -v apt-get; then apt-get -y update apt-get -y --no-install-recommends install {{ bootstrap_packages | join(' ') }} +elif command -v dnf; then + dnf -y install {{ bootstrap_packages | join(' ') }} elif command -v yum; then yum -y install {{ bootstrap_packages | join(' ') }} else From a143787c029c023402ac943fe3d579dacc8c5714 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 17:26:27 +0000 Subject: [PATCH 06/35] ci: Handle custom package repositories in bootstrap role --- tests/image_prep/roles/bootstrap/defaults/main.yml | 1 + tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/tests/image_prep/roles/bootstrap/defaults/main.yml b/tests/image_prep/roles/bootstrap/defaults/main.yml index 91e2fe57..198a6135 100644 --- a/tests/image_prep/roles/bootstrap/defaults/main.yml +++ b/tests/image_prep/roles/bootstrap/defaults/main.yml @@ -1 +1,2 @@ bootstrap_packages: [] +package_manager_repos: [] diff --git a/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 b/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 index c5a35a2b..0d25a95b 100644 --- a/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 +++ b/tests/image_prep/roles/bootstrap/templates/bootstrap.sh.j2 @@ -1,6 +1,12 @@ set -o errexit set -o nounset +{% for item in package_manager_repos %} +cat << "EOF" > "{{ item.dest }}" +{{ item.content }} +EOF +{% endfor %} + {% if bootstrap_packages %} if command -v apt-get; then apt-get -y update From e32c90a63e05f268e388b414914123cf89276465 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 17:39:15 +0000 Subject: [PATCH 07/35] ci: Factor out package installation role --- tests/image_prep/_container_setup.yml | 40 +------------------ .../roles/packages/defaults/main.yml | 14 +++++++ .../image_prep/roles/packages/tasks/main.yml | 35 ++++++++++++++++ 3 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 tests/image_prep/roles/packages/defaults/main.yml create mode 100644 tests/image_prep/roles/packages/tasks/main.yml diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index d894cd25..35bea259 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -22,49 +22,11 @@ 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: diff --git a/tests/image_prep/roles/packages/defaults/main.yml b/tests/image_prep/roles/packages/defaults/main.yml new file mode 100644 index 00000000..c6ee8cc4 --- /dev/null +++ b/tests/image_prep/roles/packages/defaults/main.yml @@ -0,0 +1,14 @@ +common_packages: [] +packages: [] + +packages_clean_command: + apt: apt-get clean + dnf: dnf clean all + yum: yum clean all + +packages_cleanup_directories: + apt: + - /var/cache/apt + - /var/lib/apt/lists + dnf: [] + yum: [] diff --git a/tests/image_prep/roles/packages/tasks/main.yml b/tests/image_prep/roles/packages/tasks/main.yml new file mode 100644 index 00000000..30076690 --- /dev/null +++ b/tests/image_prep/roles/packages/tasks/main.yml @@ -0,0 +1,35 @@ +- 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 + command: + cmd: "{{ packages_clean_command[ansible_pkg_mgr] }}" + changed_when: true + +- name: Clean up package directories + shell: + rm -rf {{ item }}/* + with_items: "{{ packages_cleanup_directories }}" + changed_when: true From a1b5d4941ea7c75a95f4ad81f650c6b44d1332a8 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 17:52:57 +0000 Subject: [PATCH 08/35] 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. --- tests/ansible/hosts/group_vars/debian9.yml | 4 ++- tests/image_prep/_container_create.yml | 2 ++ tests/image_prep/apache_proxy.conf | 33 ++++++++++++++++++++++ tests/image_prep/host_vars/centos5.yml | 32 ++++++++++++++++++++- tests/image_prep/host_vars/centos6.yml | 23 ++++++++++++++- tests/image_prep/host_vars/centos7.yml | 21 ++++++++++++++ tests/image_prep/host_vars/centos8.yml | 26 +++++++++++++++++ tests/image_prep/host_vars/debian10.yml | 8 ++++++ tests/image_prep/host_vars/debian11.yml | 8 +++++- tests/image_prep/host_vars/debian9.yml | 7 +++++ 10 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 tests/image_prep/apache_proxy.conf diff --git a/tests/ansible/hosts/group_vars/debian9.yml b/tests/ansible/hosts/group_vars/debian9.yml index e08b1ed2..5be6ee80 100644 --- a/tests/ansible/hosts/group_vars/debian9.yml +++ b/tests/ansible/hosts/group_vars/debian9.yml @@ -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 diff --git a/tests/image_prep/_container_create.yml b/tests/image_prep/_container_create.yml index a3e8385f..2fec8bd9 100644 --- a/tests/image_prep/_container_create.yml +++ b/tests/image_prep/_container_create.yml @@ -14,6 +14,8 @@ image: "{{ docker_base }}" command: /bin/bash hostname: "mitogen-{{ inventory_hostname }}" + etc_hosts: + centos-vault-proxy: host-gateway detach: true interactive: true tty: true diff --git a/tests/image_prep/apache_proxy.conf b/tests/image_prep/apache_proxy.conf new file mode 100644 index 00000000..79022df7 --- /dev/null +++ b/tests/image_prep/apache_proxy.conf @@ -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 + + + Require all denied + AllowOverride None + + + + 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" + + +# /usr/sbin/apache2 -d . -f apache_proxy.conf -D FOREGROUND + +# vim: syntax=apache diff --git a/tests/image_prep/host_vars/centos5.yml b/tests/image_prep/host_vars/centos5.yml index 1828c29e..19397096 100644 --- a/tests/image_prep/host_vars/centos5.yml +++ b/tests/image_prep/host_vars/centos5.yml @@ -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* diff --git a/tests/image_prep/host_vars/centos6.yml b/tests/image_prep/host_vars/centos6.yml index aae7965f..2eb20f48 100644 --- a/tests/image_prep/host_vars/centos6.yml +++ b/tests/image_prep/host_vars/centos6.yml @@ -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 diff --git a/tests/image_prep/host_vars/centos7.yml b/tests/image_prep/host_vars/centos7.yml index fec83471..513e4bb9 100644 --- a/tests/image_prep/host_vars/centos7.yml +++ b/tests/image_prep/host_vars/centos7.yml @@ -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 diff --git a/tests/image_prep/host_vars/centos8.yml b/tests/image_prep/host_vars/centos8.yml index 17eccd01..c2deb6ff 100644 --- a/tests/image_prep/host_vars/centos8.yml +++ b/tests/image_prep/host_vars/centos8.yml @@ -8,3 +8,29 @@ packages: - 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 diff --git a/tests/image_prep/host_vars/debian10.yml b/tests/image_prep/host_vars/debian10.yml index 1b03d6a2..f3c592b4 100644 --- a/tests/image_prep/host_vars/debian10.yml +++ b/tests/image_prep/host_vars/debian10.yml @@ -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 diff --git a/tests/image_prep/host_vars/debian11.yml b/tests/image_prep/host_vars/debian11.yml index 5ab2d761..6d4a991a 100644 --- a/tests/image_prep/host_vars/debian11.yml +++ b/tests/image_prep/host_vars/debian11.yml @@ -1,6 +1,6 @@ bootstrap_packages: [python3, python3-apt] -docker_base: debian:bullseye +docker_base: debian:11 packages: - libjson-perl @@ -9,3 +9,9 @@ packages: - 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/ diff --git a/tests/image_prep/host_vars/debian9.yml b/tests/image_prep/host_vars/debian9.yml index cbd22e0f..987d9cd4 100644 --- a/tests/image_prep/host_vars/debian9.yml +++ b/tests/image_prep/host_vars/debian9.yml @@ -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 From cfbb7f884e28f55400023f60ab879d850b64d719 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 17:55:38 +0000 Subject: [PATCH 09/35] ci: Add playbook to configure container host for image prep --- .gitignore | 1 + tests/image_prep/_container_host.yml | 5 ++++ .../roles/container_host/handlers/main.yml | 6 +++++ .../roles/container_host/tasks/main.yml | 27 +++++++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 tests/image_prep/_container_host.yml create mode 100644 tests/image_prep/roles/container_host/handlers/main.yml create mode 100644 tests/image_prep/roles/container_host/tasks/main.yml diff --git a/.gitignore b/.gitignore index 43e46a19..b3710ea2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ build/ dist/ extra/ tests/ansible/.*.pid +tests/image_prep/logs docs/_build/ htmlcov/ *.egg-info diff --git a/tests/image_prep/_container_host.yml b/tests/image_prep/_container_host.yml new file mode 100644 index 00000000..d3c2aca8 --- /dev/null +++ b/tests/image_prep/_container_host.yml @@ -0,0 +1,5 @@ +- name: Setup container host + hosts: localhost + become: true + roles: + - role: container_host diff --git a/tests/image_prep/roles/container_host/handlers/main.yml b/tests/image_prep/roles/container_host/handlers/main.yml new file mode 100644 index 00000000..60c76cee --- /dev/null +++ b/tests/image_prep/roles/container_host/handlers/main.yml @@ -0,0 +1,6 @@ +- name: Update GRUB + command: update-grub + changed_when: true + +- name: Reboot + reboot: diff --git a/tests/image_prep/roles/container_host/tasks/main.yml b/tests/image_prep/roles/container_host/tasks/main.yml new file mode 100644 index 00000000..c7736790 --- /dev/null +++ b/tests/image_prep/roles/container_host/tasks/main.yml @@ -0,0 +1,27 @@ +# > If running `docker run --rm -it centos:centos6.7 bash` immediately exits +# > with status code 139, check to see if your system has disabled vsyscall: +# > ... +# > If you do not see a vsyscall mapping, and you need to run a CentOS 6 +# > container, try adding vsyscall=emulated to the kernel options. +# > -- https://hub.docker.com/_/centos + +- name: Check vsyscall enabled + command: + cmd: grep -c vsyscall /proc/self/maps + register: grep_self_maps_result + changed_when: false + check_mode: false + failed_when: + # 0 -> match, 1 -> no match, 2 -> error + - grep_self_maps_result.rc not in [0, 1] + +- name: Enable vsyscall + lineinfile: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX_DEFAULT.+' + line: GRUB_CMDLINE_LINUX_DEFAULT="quiet vsyscall=emulate" + when: + - grep_self_maps_result.rc != 0 + notify: + - Update GRUB + - Reboot From 3fe9b9bd876b8bdbbb7e85591e52c2aab46f63d6 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 18:12:12 +0000 Subject: [PATCH 10/35] ci: Install setfacl for vanilla Ansible unprivileged become --- tests/image_prep/group_vars/all.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/image_prep/group_vars/all.yml b/tests/image_prep/group_vars/all.yml index 6545e432..9316a3d9 100644 --- a/tests/image_prep/group_vars/all.yml +++ b/tests/image_prep/group_vars/all.yml @@ -1,6 +1,7 @@ ansible_version_major_minor: "{{ ansible_version.major }}.{{ ansible_version.minor }}" common_packages: + - acl - openssh-server - rsync - strace From b353980699c97f5120ff5ec4a471927620d7f61e Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Mon, 24 Nov 2025 11:00:02 +0000 Subject: [PATCH 11/35] ci: Tighten Ansible error checking during image prep --- tests/image_prep/ansible.cfg | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/image_prep/ansible.cfg b/tests/image_prep/ansible.cfg index f698ed89..e46bedcb 100644 --- a/tests/image_prep/ansible.cfg +++ b/tests/image_prep/ansible.cfg @@ -1,8 +1,10 @@ [defaults] +any_errors_fatal = true # Ansible >= 6 (ansible-core >= 2.13) callback_result_format = yaml deprecation_warnings = false +duplicate_dict_key = error strategy_plugins = ../../ansible_mitogen/plugins/strategy retry_files_enabled = false display_args_to_stdout = True @@ -10,4 +12,6 @@ no_target_syslog = True host_key_checking = False [inventory] -unparsed_is_fatal = true +any_unparsed_is_failed = true +host_pattern_mismatch = error +unparsed_is_failed = true From 40fbfe58fcb8b0a6c27ea2ce0371c8a379f0a677 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 18:30:12 +0000 Subject: [PATCH 12/35] 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. --- tests/data/docker/doas-debian.tar.gz | Bin 58712 -> 0 bytes tests/image_prep/_container_setup.yml | 12 ------------ tests/image_prep/host_vars/debian11.yml | 1 + 3 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 tests/data/docker/doas-debian.tar.gz diff --git a/tests/data/docker/doas-debian.tar.gz b/tests/data/docker/doas-debian.tar.gz deleted file mode 100644 index 2deb72ff8c0481d0851a7bb7ebc4e11e34ad8cec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58712 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 z 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

_Ar3Boz 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 diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index 35bea259..1120897a 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -54,18 +54,6 @@ content: "UNICODE_SNOWMAN=\u2603\n" mode: u=rw,go=r - - 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 - - name: Install doas.conf copy: dest: /etc/doas.conf diff --git a/tests/image_prep/host_vars/debian11.yml b/tests/image_prep/host_vars/debian11.yml index 6d4a991a..55d34ae4 100644 --- a/tests/image_prep/host_vars/debian11.yml +++ b/tests/image_prep/host_vars/debian11.yml @@ -3,6 +3,7 @@ bootstrap_packages: [python3, python3-apt] docker_base: debian:11 packages: + - doas - libjson-perl - locales - python-is-python3 From cc8a39864d065102e9242648ed7c7de9056c135b Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 18:37:00 +0000 Subject: [PATCH 13/35] 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. --- tests/image_prep/_container_setup.yml | 6 ------ tests/image_prep/host_vars/centos8.yml | 2 -- 2 files changed, 8 deletions(-) diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index 1120897a..256cf255 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -27,12 +27,6 @@ - role: sshd_container tasks: - - 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 diff --git a/tests/image_prep/host_vars/centos8.yml b/tests/image_prep/host_vars/centos8.yml index c2deb6ff..baccafea 100644 --- a/tests/image_prep/host_vars/centos8.yml +++ b/tests/image_prep/host_vars/centos8.yml @@ -6,8 +6,6 @@ packages: - perl-JSON - python2-virtualenv - python3-virtualenv - - python36 - - python38 package_manager_repos: - dest: /etc/yum.repos.d/CentOS-Linux-AppStream.repo From 22e7046cf6185d0fb3e98a26ed5b01061066dfa1 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 18:39:15 +0000 Subject: [PATCH 14/35] ci: Run image-prep as fast as possible Mitogen maintainer(s) got better laptops in the last decaade or so. --- tests/image_prep/_container_setup.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index 256cf255..3ec75760 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -8,8 +8,6 @@ - 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: From 01e24f9ddf3828e44b176cf3ae0e1754d2dac7ef Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Mon, 24 Nov 2025 11:02:14 +0000 Subject: [PATCH 15/35] 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. --- tests/image_prep/_container_create.yml | 5 --- tests/image_prep/_container_setup.yml | 2 +- tests/image_prep/ansible.cfg | 1 + tests/image_prep/hosts.ini | 20 +++++++++++ tests/image_prep/setup.yml | 19 ++++++++--- tests/image_prep/setup_ansible2.3.yml | 15 +++++++++ tests/image_prep/tox.ini | 46 ++++++++++++++++++++++---- tox.ini | 6 ++-- 8 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 tests/image_prep/setup_ansible2.3.yml diff --git a/tests/image_prep/_container_create.yml b/tests/image_prep/_container_create.yml index 2fec8bd9..ab52b73b 100644 --- a/tests/image_prep/_container_create.yml +++ b/tests/image_prep/_container_create.yml @@ -3,11 +3,6 @@ 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 }}" diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index 3ec75760..93d9a7ce 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -16,7 +16,7 @@ pre_tasks: - meta: end_play when: - - ansible_facts.virtualization_type != "docker" + - ansible_virtualization_type != "docker" roles: - role: package_manager diff --git a/tests/image_prep/ansible.cfg b/tests/image_prep/ansible.cfg index e46bedcb..0d5771ee 100644 --- a/tests/image_prep/ansible.cfg +++ b/tests/image_prep/ansible.cfg @@ -5,6 +5,7 @@ any_errors_fatal = true 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 diff --git a/tests/image_prep/hosts.ini b/tests/image_prep/hosts.ini index 68f8be62..a6d6b6b0 100644 --- a/tests/image_prep/hosts.ini +++ b/tests/image_prep/hosts.ini @@ -21,3 +21,23 @@ debian11 ubuntu1604 ubuntu1804 ubuntu2004 + +[ansible_2_3] +# Python 2.4 on targets +centos5 + +[ansible_5] +# Python 2.6 on targets +centos6 + +[ansible_9] +# Python 2.7 and/or 3.6 on targets +centos7 +centos8 +debian9 +debian10 +ubuntu1804 + +[ansible_11] +# Python >= 3.8 on targets +debian11 diff --git a/tests/image_prep/setup.yml b/tests/image_prep/setup.yml index b820e1bc..afe189a5 100755 --- a/tests/image_prep/setup.yml +++ b/tests/image_prep/setup.yml @@ -1,6 +1,17 @@ #!/usr/bin/env ansible-playbook -- include_playbook: _container_create.yml -- include_playbook: _container_setup.yml -- include_playbook: _user_accounts.yml -- include_playbook: _container_finalize.yml +- name: Get base images + hosts: all + # strategy: mitogen_free + gather_facts: false + tasks: + - name: Fetch container base images + docker_image: + name: "{{ docker_base }}" + source: pull # Added in Ansible 2.8, required circa 2.12 + delegate_to: localhost + +- import_playbook: _container_create.yml +- import_playbook: _container_setup.yml +- import_playbook: _user_accounts.yml +- import_playbook: _container_finalize.yml diff --git a/tests/image_prep/setup_ansible2.3.yml b/tests/image_prep/setup_ansible2.3.yml new file mode 100644 index 00000000..cc7025c8 --- /dev/null +++ b/tests/image_prep/setup_ansible2.3.yml @@ -0,0 +1,15 @@ +#!/usr/bin/env ansible-playbook + +- name: Get base images + hosts: all + gather_facts: false + tasks: + - name: Fetch container base images + docker_image: + name: "{{ docker_base }}" + delegate_to: localhost + +- include: _container_create.yml +- include: _container_setup.yml +- include: _user_accounts.yml +- include: _container_finalize.yml diff --git a/tests/image_prep/tox.ini b/tests/image_prep/tox.ini index d28d1512..2e32cdce 100644 --- a/tests/image_prep/tox.ini +++ b/tests/image_prep/tox.ini @@ -1,7 +1,9 @@ [tox] envlist = ansible2.3, - ansible2.10, + ansible5 + ansible9 + ansible11 skipsdist = true [testenv] @@ -13,17 +15,47 @@ basepython = python2 deps = ansible>=2.3,<2.4 docker-py>=1.7.0 - mitogen>=0.2.10rc1,<0.3 + mitogen~=0.2.0 install_command = python -m pip --no-python-version-warning install {opts} {packages} commands = - ./setup.yml -i hosts.ini -l 'localhost,centos5' {posargs} + ansible-playbook -l 'localhost,ansible_2_3' setup_ansible2.3.yml -[testenv:ansible2.10] +[testenv:ansible5] basepython = python3 deps = - ansible>=2.10,<2.11 + ansible~=5.0 docker>=1.8.0 - mitogen>=0.3.0rc1,<0.4 + mitogen~=0.3.0 + passlib +setenv = + ANSIBLE_PYTHON_INTERPRETER=auto_silent + ANSIBLE_STDOUT_CALLBACK=yaml +commands = + ansible-playbook -l 'localhost,ansible_5' setup.yml + +[testenv:ansible9] +basepython = python3 +deps = + ansible~=9.0 + docker>=1.8.0 + mitogen~=0.3.0 + passlib +setenv = + ANSIBLE_PYTHON_INTERPRETER=auto_silent + ANSIBLE_STDOUT_CALLBACK=yaml +commands = + ansible-playbook -l 'localhost,ansible_9' setup.yml + +[testenv:ansible11] +basepython = python3 +deps = + ansible~=11.0 + docker>=1.8.0 + mitogen~=0.3.0 + passlib +setenv = + ANSIBLE_PYTHON_INTERPRETER=auto_silent + ANSIBLE_STDOUT_CALLBACK=yaml commands = - ./setup.yml -i hosts.ini -l '!centos5' {posargs} + ansible-playbook -l 'localhost,ansible_11' setup.yml diff --git a/tox.ini b/tox.ini index 08b2eb98..c2a51dda 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,8 @@ # Py A cntrllr A target coverage Django Jinja2 pip psutil pytest tox virtualenv # ==== ========== ========== ========== ========== ========== ========== ========== ========== ========== ========== -# 2.4 2.3? <= 3.7.1 <= 1.3.7 <= 1.1 <= 2.1.3 <= 1.4 <= 1.8 -# 2.5 <= 3.7.1 <= 1.4.22 <= 1.3.1 <= 2.1.3 <= 2.8.7 <= 1.6.1 <= 1.9.1 +# 2.4 <= 2.3³ <= 3.7.1 <= 1.3.7 <= 1.1 <= 2.1.3 <= 1.4 <= 1.8 +# 2.5 <= 2.3³ <= 3.7.1 <= 1.4.22 <= 1.3.1 <= 2.1.3 <= 2.8.7 <= 1.6.1 <= 1.9.1 # 2.6 <= 2.6.20 <= 2.12 <= 4.5.4 <= 1.6.11 <= 2.10.3 <= 9.0.3 <= 5.9.0 <= 3.2.5 <= 2.9.1 <= 15.2.0 # 2.7 <= 2.11 <= 2.16 <= 5.5 <= 1.11.29 <= 2.11.3 <= 20 <= 4.6.11 <= 3.28 <= 20.15² # 3.5 <= 2.11 <= 2.15 <= 5.5 <= 2.2.28 <= 2.11.3 <= 20 <= 5.9.5 <= 6.1.0 <= 3.28 <= 20.15² @@ -35,6 +35,8 @@ # Virtualenv <= 20.21.1 supports creating virtualenvs with any *target* Python. # Virtualenv >= 20.22 supports creating virtualenvs with target Python >= 3.7. # https://virtualenv.pypa.io/en/latest/#compatibility +# +# 3. https://docs.ansible.com/ansible/2.7/dev_guide/developing_python_3.html#minimum-version-of-python-3-x-and-python-2-x # Ansible Dependency # ================== ====================== From 5ffdbb5999793cb0b909f74b1a3ff3792e9210b1 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 19:03:38 +0000 Subject: [PATCH 16/35] ci: Add Alma 9, Debian 12, Ubuntu 22.04, & Ubuntu 24.04 to image prep --- tests/image_prep/group_vars/all.yml | 1 + tests/image_prep/host_vars/alma9.yml | 5 +++++ tests/image_prep/host_vars/debian12.yml | 8 ++++++++ tests/image_prep/host_vars/ubuntu2204.yml | 10 ++++++++++ tests/image_prep/host_vars/ubuntu2404.yml | 9 +++++++++ tests/image_prep/hosts.ini | 12 ++++++++++++ 6 files changed, 45 insertions(+) create mode 100644 tests/image_prep/host_vars/alma9.yml create mode 100644 tests/image_prep/host_vars/debian12.yml create mode 100644 tests/image_prep/host_vars/ubuntu2204.yml create mode 100644 tests/image_prep/host_vars/ubuntu2404.yml diff --git a/tests/image_prep/group_vars/all.yml b/tests/image_prep/group_vars/all.yml index 9316a3d9..b3211ad6 100644 --- a/tests/image_prep/group_vars/all.yml +++ b/tests/image_prep/group_vars/all.yml @@ -15,3 +15,4 @@ sudo_group: Debian: sudo Ubuntu: sudo CentOS: wheel + AlmaLinux: wheel diff --git a/tests/image_prep/host_vars/alma9.yml b/tests/image_prep/host_vars/alma9.yml new file mode 100644 index 00000000..63279e0c --- /dev/null +++ b/tests/image_prep/host_vars/alma9.yml @@ -0,0 +1,5 @@ +bootstrap_packages: [python3] +docker_base: almalinux:9 + +packages: + - perl-JSON diff --git a/tests/image_prep/host_vars/debian12.yml b/tests/image_prep/host_vars/debian12.yml new file mode 100644 index 00000000..7bcdf94f --- /dev/null +++ b/tests/image_prep/host_vars/debian12.yml @@ -0,0 +1,8 @@ +bootstrap_packages: [python, python3-apt] +docker_base: debian:12 + +packages: + - libjson-perl + - locales + - opendoas + - virtualenv diff --git a/tests/image_prep/host_vars/ubuntu2204.yml b/tests/image_prep/host_vars/ubuntu2204.yml new file mode 100644 index 00000000..3d20e507 --- /dev/null +++ b/tests/image_prep/host_vars/ubuntu2204.yml @@ -0,0 +1,10 @@ +bootstrap_packages: [python3] +docker_base: ubuntu:22.04 + +packages: + - doas + - libjson-perl + - locales + - python2 + - python3-virtualenv + - virtualenv diff --git a/tests/image_prep/host_vars/ubuntu2404.yml b/tests/image_prep/host_vars/ubuntu2404.yml new file mode 100644 index 00000000..cc9a1efb --- /dev/null +++ b/tests/image_prep/host_vars/ubuntu2404.yml @@ -0,0 +1,9 @@ +bootstrap_packages: [python3] +docker_base: ubuntu:24.04 + +packages: + - libjson-perl + - locales + - opendoas + - python3-virtualenv + - virtualenv diff --git a/tests/image_prep/hosts.ini b/tests/image_prep/hosts.ini index a6d6b6b0..254643a7 100644 --- a/tests/image_prep/hosts.ini +++ b/tests/image_prep/hosts.ini @@ -1,4 +1,5 @@ [all:children] +alma centos debian ubuntu @@ -6,6 +7,9 @@ ubuntu [all:vars] ansible_connection = docker +[alma] +alma9 + [centos] centos5 centos6 @@ -16,11 +20,14 @@ centos8 debian9 debian10 debian11 +debian12 [ubuntu] ubuntu1604 ubuntu1804 ubuntu2004 +ubuntu2204 +ubuntu2404 [ansible_2_3] # Python 2.4 on targets @@ -40,4 +47,9 @@ ubuntu1804 [ansible_11] # Python >= 3.8 on targets +alma9 debian11 +debian12 +ubuntu2004 +ubuntu2204 +ubuntu2404 From ff973775ce45eea1f782254625b399a9158d49bf Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 19:08:09 +0000 Subject: [PATCH 17/35] ci: Push new container images to GitHub Container Registry --- tests/image_prep/group_vars/all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/image_prep/group_vars/all.yml b/tests/image_prep/group_vars/all.yml index b3211ad6..fc9ffddf 100644 --- a/tests/image_prep/group_vars/all.yml +++ b/tests/image_prep/group_vars/all.yml @@ -8,7 +8,7 @@ common_packages: - 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 From 56dce2890654c7fa057a74272f2bd88647e43daf Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 27 Feb 2025 00:01:15 +0000 Subject: [PATCH 18/35] ci: Dont show arguments in task name during image prep A bit to noisy for my taste --- tests/image_prep/ansible.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/image_prep/ansible.cfg b/tests/image_prep/ansible.cfg index 0d5771ee..cc63c3a3 100644 --- a/tests/image_prep/ansible.cfg +++ b/tests/image_prep/ansible.cfg @@ -8,7 +8,6 @@ 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 From 4ecafc564da20815ba33d1a8109a35f9c233cb02 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 27 Feb 2025 00:02:23 +0000 Subject: [PATCH 19/35] ci: Use command: true as noop handler meta: noop failed on older Ansibles (e.g. 2.3) --- tests/image_prep/roles/sshd_container/handlers/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/image_prep/roles/sshd_container/handlers/main.yml b/tests/image_prep/roles/sshd_container/handlers/main.yml index cc7b9166..79902ed6 100644 --- a/tests/image_prep/roles/sshd_container/handlers/main.yml +++ b/tests/image_prep/roles/sshd_container/handlers/main.yml @@ -1,2 +1,4 @@ - name: Restart sshd - meta: noop + command: "true" + changed_when: false + check_mode: false From 5b6b076c4e05b9fb2d7e585fbb1a022ed4f60125 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Mon, 24 Nov 2025 11:04:00 +0000 Subject: [PATCH 20/35] 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). --- tests/image_prep/_container_setup.yml | 5 ----- tests/image_prep/_user_accounts.yml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml index 93d9a7ce..276004f1 100644 --- a/tests/image_prep/_container_setup.yml +++ b/tests/image_prep/_container_setup.yml @@ -13,11 +13,6 @@ vars: distro: "{{ansible_distribution}}" - pre_tasks: - - meta: end_play - when: - - ansible_virtualization_type != "docker" - roles: - role: package_manager - role: packages diff --git a/tests/image_prep/_user_accounts.yml b/tests/image_prep/_user_accounts.yml index a1701e55..0f167e7a 100644 --- a/tests/image_prep/_user_accounts.yml +++ b/tests/image_prep/_user_accounts.yml @@ -181,6 +181,6 @@ {% endfor %} validate: '/usr/sbin/visudo -cf %s' when: - - ansible_virtualization_type != "docker" + - ansible_connection == "local" roles: - role: user_policies From 7996a03a378aba78bc473ef0bf5d2ce139401149 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 27 Feb 2025 00:06:49 +0000 Subject: [PATCH 21/35] ci: Bootstrap Debian like containers with python-apt or python3-apt The Ansible apt module requires it --- tests/image_prep/host_vars/debian10.yml | 2 +- tests/image_prep/host_vars/debian12.yml | 2 +- tests/image_prep/host_vars/debian9.yml | 2 +- tests/image_prep/host_vars/ubuntu1604.yml | 2 +- tests/image_prep/host_vars/ubuntu1804.yml | 2 +- tests/image_prep/host_vars/ubuntu2004.yml | 2 +- tests/image_prep/host_vars/ubuntu2204.yml | 2 +- tests/image_prep/host_vars/ubuntu2404.yml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/image_prep/host_vars/debian10.yml b/tests/image_prep/host_vars/debian10.yml index f3c592b4..3e3c000f 100644 --- a/tests/image_prep/host_vars/debian10.yml +++ b/tests/image_prep/host_vars/debian10.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python] +bootstrap_packages: [python, python-apt] docker_base: debian:10 diff --git a/tests/image_prep/host_vars/debian12.yml b/tests/image_prep/host_vars/debian12.yml index 7bcdf94f..8c4afc66 100644 --- a/tests/image_prep/host_vars/debian12.yml +++ b/tests/image_prep/host_vars/debian12.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python, python3-apt] +bootstrap_packages: [python3, python3-apt] docker_base: debian:12 packages: diff --git a/tests/image_prep/host_vars/debian9.yml b/tests/image_prep/host_vars/debian9.yml index 987d9cd4..6676d7ce 100644 --- a/tests/image_prep/host_vars/debian9.yml +++ b/tests/image_prep/host_vars/debian9.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python] +bootstrap_packages: [python, python-apt] docker_base: debian:9 diff --git a/tests/image_prep/host_vars/ubuntu1604.yml b/tests/image_prep/host_vars/ubuntu1604.yml index 461e522d..6afe326f 100644 --- a/tests/image_prep/host_vars/ubuntu1604.yml +++ b/tests/image_prep/host_vars/ubuntu1604.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python] +bootstrap_packages: [python, python-apt] docker_base: ubuntu:16.04 diff --git a/tests/image_prep/host_vars/ubuntu1804.yml b/tests/image_prep/host_vars/ubuntu1804.yml index 4c913e2d..e174cbb8 100644 --- a/tests/image_prep/host_vars/ubuntu1804.yml +++ b/tests/image_prep/host_vars/ubuntu1804.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python] +bootstrap_packages: [python, python-apt] docker_base: ubuntu:18.04 diff --git a/tests/image_prep/host_vars/ubuntu2004.yml b/tests/image_prep/host_vars/ubuntu2004.yml index 4ee5b331..f8179f1d 100644 --- a/tests/image_prep/host_vars/ubuntu2004.yml +++ b/tests/image_prep/host_vars/ubuntu2004.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python3] +bootstrap_packages: [python3, python3-apt] docker_base: ubuntu:20.04 diff --git a/tests/image_prep/host_vars/ubuntu2204.yml b/tests/image_prep/host_vars/ubuntu2204.yml index 3d20e507..e8a21c65 100644 --- a/tests/image_prep/host_vars/ubuntu2204.yml +++ b/tests/image_prep/host_vars/ubuntu2204.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python3] +bootstrap_packages: [python3, python3-apt] docker_base: ubuntu:22.04 packages: diff --git a/tests/image_prep/host_vars/ubuntu2404.yml b/tests/image_prep/host_vars/ubuntu2404.yml index cc9a1efb..76eefc5f 100644 --- a/tests/image_prep/host_vars/ubuntu2404.yml +++ b/tests/image_prep/host_vars/ubuntu2404.yml @@ -1,4 +1,4 @@ -bootstrap_packages: [python3] +bootstrap_packages: [python3, python3-apt] docker_base: ubuntu:24.04 packages: From 7eabcc61c1d277659c78f58a1f90627b642818ce Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 25 Nov 2025 09:20:11 +0000 Subject: [PATCH 22/35] tests: Only test doas on targets with doas binary installed --- tests/ansible/hosts/group_vars/all.yml | 1 + tests/ansible/hosts/group_vars/debian11.yml | 1 + tests/ansible/integration/become/doas.yml | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/tests/ansible/hosts/group_vars/all.yml b/tests/ansible/hosts/group_vars/all.yml index 7ec10d6a..42a087ce 100644 --- a/tests/ansible/hosts/group_vars/all.yml +++ b/tests/ansible/hosts/group_vars/all.yml @@ -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 diff --git a/tests/ansible/hosts/group_vars/debian11.yml b/tests/ansible/hosts/group_vars/debian11.yml index 30c98341..cecc3c50 100644 --- a/tests/ansible/hosts/group_vars/debian11.yml +++ b/tests/ansible/hosts/group_vars/debian11.yml @@ -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 diff --git a/tests/ansible/integration/become/doas.yml b/tests/ansible/integration/become/doas.yml index 31858168..296b3084 100644 --- a/tests/ansible/integration/become/doas.yml +++ b/tests/ansible/integration/become/doas.yml @@ -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 From 9609437262554e46ce807c19b6bfd91345ee69ef Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 25 Nov 2025 08:26:12 +0000 Subject: [PATCH 23/35] CI: Use 2025.02 test images, keeping same OS releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .ci/ci_lib.py | 8 ++++---- docs/changelog.rst | 2 ++ .../interpreter_discovery/ansible_2_8_tests.yml | 4 ++-- .../regression/issue_122__environment_difference.yml | 11 +++++++---- tests/testlib.py | 4 ++-- tox.ini | 9 +++++---- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index c50c76da..06867c33 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -34,12 +34,12 @@ 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', + 'centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', ) 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' @@ -231,7 +231,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, @@ -239,7 +239,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, diff --git a/docs/changelog.rst b/docs/changelog.rst index ad11c08f..b8f59f59 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,8 @@ To avail of fixes in an unreleased version, please download a ZIP file In progress (unreleased) ------------------------ +* :gh:issue:`1118` CI: Use 2025.02 test images, keeping same OS releases + v0.3.33 (2025-11-22) -------------------- diff --git a/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml b/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml index 451b4dc3..403f7848 100644 --- a/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml +++ b/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml @@ -13,7 +13,7 @@ debian: '9': /usr/bin/python '10': /usr/bin/python3 - '11': /usr/bin/python + '11': /usr/bin/python3 '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: @@ -29,7 +29,7 @@ debian: '9': /usr/bin/python '10': /usr/bin/python3 - '11': /usr/bin/python3.9 + '11': /usr/bin/python3 '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: diff --git a/tests/ansible/regression/issue_122__environment_difference.yml b/tests/ansible/regression/issue_122__environment_difference.yml index 273a49ae..510dbf86 100644 --- a/tests/ansible/regression/issue_122__environment_difference.yml +++ b/tests/ansible/regression/issue_122__environment_difference.yml @@ -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 diff --git a/tests/testlib.py b/tests/testlib.py index 3b5dc8b6..803159a3 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -53,11 +53,11 @@ LOG = logging.getLogger(__name__) DISTRO_SPECS = os.environ.get( 'MITOGEN_TEST_DISTRO_SPECS', - 'centos6 centos8 debian9 debian11 ubuntu1604 ubuntu2004', + 'centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', ) 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', ) TESTS_DIR = os.path.join(os.path.dirname(__file__)) diff --git a/tox.ini b/tox.ini index c2a51dda..b2974332 100644 --- a/tox.ini +++ b/tox.ini @@ -123,10 +123,11 @@ setenv = ans{2.10,3,4,5}: ANSIBLE_STDOUT_CALLBACK=yaml # Print warning on the first occurence at each module:linenno in Mitogen. Available Python 2.7, 3.2+. PYTHONWARNINGS=default:::ansible_mitogen,default:::mitogen + ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004 # Ansible 6 - 8 (ansible-core 2.13 - 2.15) require Python 2.7 or >= 3.5 on targets - ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=centos7 centos8 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004 + ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=centos7 centos8-py3 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004 # Ansible 9 (ansible-core 2.16) requires Python 2.7 or >= 3.6 on targets - ans9: MITOGEN_TEST_DISTRO_SPECS=centos7 centos8 debian9 debian10 debian11 ubuntu1804 ubuntu2004 + ans9: MITOGEN_TEST_DISTRO_SPECS=centos7 centos8-py3 debian9 debian10 debian11 ubuntu1804 ubuntu2004 # Ansible 10 (ansible-core 2.17) requires Python >= 3.7 on targets ans10: MITOGEN_TEST_DISTRO_SPECS=debian10-py3 debian11-py3 ubuntu2004-py3 # Ansible 11 (ansible-core 2.18) requires Python >= 3.8 on targets @@ -134,11 +135,11 @@ setenv = ans12: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 ubuntu2004-py3 # Ansible 13 (ansible-core 2.20) requires Python >= 3.9 on targets ans13: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 - distros_centos: MITOGEN_TEST_DISTRO_SPECS=centos6 centos7 centos8 + distros_centos: MITOGEN_TEST_DISTRO_SPECS=centos6 centos7 centos8-py3 distros_centos5: MITOGEN_TEST_DISTRO_SPECS=centos5 distros_centos6: MITOGEN_TEST_DISTRO_SPECS=centos6 distros_centos7: MITOGEN_TEST_DISTRO_SPECS=centos7 - distros_centos8: MITOGEN_TEST_DISTRO_SPECS=centos8 + distros_centos8: MITOGEN_TEST_DISTRO_SPECS=centos8-py3 distros_debian: MITOGEN_TEST_DISTRO_SPECS=debian9 debian10 debian11 distros_debian9: MITOGEN_TEST_DISTRO_SPECS=debian9 distros_debian10: MITOGEN_TEST_DISTRO_SPECS=debian10 From 006d497c25a890c5c6aa9bfb389d7d5215bfc731 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 25 Nov 2025 09:52:27 +0000 Subject: [PATCH 24/35] CI: Show details of failed ci_lib.run_batches() commands --- .ci/ci_lib.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index 06867c33..eace0b71 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -156,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): From 15b2619fb279d26f8a0a49f98b3014b01c5dd13c Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Sat, 22 Nov 2025 09:12:37 +0000 Subject: [PATCH 25/35] CI: Bump deprecated macOS 13 runner to macOS 15 --- .github/workflows/tests.yml | 4 ++-- docs/changelog.rst | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ebfad8fd..7d91954a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -153,8 +153,8 @@ 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: diff --git a/docs/changelog.rst b/docs/changelog.rst index b8f59f59..edfc1ceb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,7 @@ In progress (unreleased) ------------------------ * :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 v0.3.33 (2025-11-22) From 0bafbd501cf55d568c4ae54b3801fa11ac8df023 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 25 Nov 2025 14:57:08 +0000 Subject: [PATCH 26/35] tests: Remove unused distros_* Tox factors --- tox.ini | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tox.ini b/tox.ini index b2974332..3270f10a 100644 --- a/tox.ini +++ b/tox.ini @@ -135,19 +135,6 @@ setenv = ans12: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 ubuntu2004-py3 # Ansible 13 (ansible-core 2.20) requires Python >= 3.9 on targets ans13: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 - distros_centos: MITOGEN_TEST_DISTRO_SPECS=centos6 centos7 centos8-py3 - distros_centos5: MITOGEN_TEST_DISTRO_SPECS=centos5 - distros_centos6: MITOGEN_TEST_DISTRO_SPECS=centos6 - distros_centos7: MITOGEN_TEST_DISTRO_SPECS=centos7 - distros_centos8: MITOGEN_TEST_DISTRO_SPECS=centos8-py3 - distros_debian: MITOGEN_TEST_DISTRO_SPECS=debian9 debian10 debian11 - distros_debian9: MITOGEN_TEST_DISTRO_SPECS=debian9 - distros_debian10: MITOGEN_TEST_DISTRO_SPECS=debian10 - distros_debian11: MITOGEN_TEST_DISTRO_SPECS=debian11 - distros_ubuntu: MITOGEN_TEST_DISTRO_SPECS=ubuntu1604 ubuntu1804 ubuntu2004 - distros_ubuntu1604: MITOGEN_TEST_DISTRO_SPECS=ubuntu1604 - distros_ubuntu1804: MITOGEN_TEST_DISTRO_SPECS=ubuntu1804 - distros_ubuntu2004: MITOGEN_TEST_DISTRO_SPECS=ubuntu2004 m_ans: MODE=ansible m_ans: ANSIBLE_SKIP_TAGS=resource_intensive m_ans: ANSIBLE_CALLBACK_WHITELIST=profile_tasks From 1cbd1777bc0bc5ef9636353d3974aae1de8b4e92 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 25 Nov 2025 16:07:01 +0000 Subject: [PATCH 27/35] 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. --- .../ansible_2_8_tests.yml | 76 ++++--------------- 1 file changed, 13 insertions(+), 63 deletions(-) diff --git a/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml b/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml index 403f7848..fd82d2ea 100644 --- a/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml +++ b/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml @@ -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/python3 - '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 - '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 From e044893a8864acb876b7d3f6438a8b28a80154fa Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 13:21:21 +0000 Subject: [PATCH 28/35] tests: Variabalize virtualenv creation in isssue 152 regression test Prep for AlamaLinux 9 introduction --- tests/ansible/hosts/group_vars/all.yml | 8 ++++++++ .../issue_152__virtualenv_python_fails.yml | 17 ++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/ansible/hosts/group_vars/all.yml b/tests/ansible/hosts/group_vars/all.yml index 42a087ce..ca96f25f 100644 --- a/tests/ansible/hosts/group_vars/all.yml +++ b/tests/ansible/hosts/group_vars/all.yml @@ -35,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 diff --git a/tests/ansible/regression/issue_152__virtualenv_python_fails.yml b/tests/ansible/regression/issue_152__virtualenv_python_fails.yml index 43b00de5..c90f63d2 100644 --- a/tests/ansible/regression/issue_152__virtualenv_python_fails.yml +++ b/tests/ansible/regression/issue_152__virtualenv_python_fails.yml @@ -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) From e0103eb66c3669ae5cc729655bf4adceb6c487e4 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 13:24:00 +0000 Subject: [PATCH 29/35] CI: Add OS release coverage: AlmaLinux 9 --- .ci/ci_lib.py | 2 +- docs/changelog.rst | 1 + tests/ansible/hosts/group_vars/alma9.yml | 8 ++++++++ tests/ansible/integration/ssh/password.yml | 4 ++-- tests/image_prep/host_vars/alma9.yml | 1 + tests/testlib.py | 2 +- tox.ini | 14 +++++++------- 7 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 tests/ansible/hosts/group_vars/alma9.yml diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index eace0b71..7289f173 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -34,7 +34,7 @@ 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-py3 debian9 debian11 ubuntu1604 ubuntu2004', + 'alma9-py3 centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', ) IMAGE_PREP_DIR = os.path.join(GIT_ROOT, 'tests/image_prep') IMAGE_TEMPLATE = os.environ.get( diff --git a/docs/changelog.rst b/docs/changelog.rst index edfc1ceb..2692cc07 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -23,6 +23,7 @@ In progress (unreleased) * :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 v0.3.33 (2025-11-22) diff --git a/tests/ansible/hosts/group_vars/alma9.yml b/tests/ansible/hosts/group_vars/alma9.yml new file mode 100644 index 00000000..25fd10bd --- /dev/null +++ b/tests/ansible/hosts/group_vars/alma9.yml @@ -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 }}" diff --git a/tests/ansible/integration/ssh/password.yml b/tests/ansible/integration/ssh/password.yml index ca08fa5b..5301c084 100644 --- a/tests/ansible/integration/ssh/password.yml +++ b/tests/ansible/integration/ssh/password.yml @@ -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 }} diff --git a/tests/image_prep/host_vars/alma9.yml b/tests/image_prep/host_vars/alma9.yml index 63279e0c..430d6bf2 100644 --- a/tests/image_prep/host_vars/alma9.yml +++ b/tests/image_prep/host_vars/alma9.yml @@ -3,3 +3,4 @@ docker_base: almalinux:9 packages: - perl-JSON + - procps-ng diff --git a/tests/testlib.py b/tests/testlib.py index 803159a3..6577f30b 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -53,7 +53,7 @@ LOG = logging.getLogger(__name__) DISTRO_SPECS = os.environ.get( 'MITOGEN_TEST_DISTRO_SPECS', - 'centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', + 'alma9-py3 centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', ) IMAGE_TEMPLATE = os.environ.get( 'MITOGEN_TEST_IMAGE_TEMPLATE', diff --git a/tox.ini b/tox.ini index 3270f10a..d660054d 100644 --- a/tox.ini +++ b/tox.ini @@ -123,18 +123,18 @@ setenv = ans{2.10,3,4,5}: ANSIBLE_STDOUT_CALLBACK=yaml # Print warning on the first occurence at each module:linenno in Mitogen. Available Python 2.7, 3.2+. PYTHONWARNINGS=default:::ansible_mitogen,default:::mitogen - ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004 + ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004 # Ansible 6 - 8 (ansible-core 2.13 - 2.15) require Python 2.7 or >= 3.5 on targets - ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=centos7 centos8-py3 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004 + ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004 # Ansible 9 (ansible-core 2.16) requires Python 2.7 or >= 3.6 on targets - ans9: MITOGEN_TEST_DISTRO_SPECS=centos7 centos8-py3 debian9 debian10 debian11 ubuntu1804 ubuntu2004 + ans9: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian11 ubuntu1804 ubuntu2004 # Ansible 10 (ansible-core 2.17) requires Python >= 3.7 on targets - ans10: MITOGEN_TEST_DISTRO_SPECS=debian10-py3 debian11-py3 ubuntu2004-py3 + ans10: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian10-py3 debian11-py3 ubuntu2004-py3 # Ansible 11 (ansible-core 2.18) requires Python >= 3.8 on targets - ans11: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 ubuntu2004-py3 - ans12: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 ubuntu2004-py3 + ans11: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian11-py3 ubuntu2004-py3 + ans12: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian11-py3 ubuntu2004-py3 # Ansible 13 (ansible-core 2.20) requires Python >= 3.9 on targets - ans13: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 + ans13: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian11-py3 m_ans: MODE=ansible m_ans: ANSIBLE_SKIP_TAGS=resource_intensive m_ans: ANSIBLE_CALLBACK_WHITELIST=profile_tasks From 1fe55f1c67fa0b283be0967970c88b2b08d87aa3 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 14:08:35 +0000 Subject: [PATCH 30/35] 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 --- .ci/ci_lib.py | 2 +- docs/changelog.rst | 1 + tests/ansible/ansible.cfg | 1 + .../connection_delegation/delegate_to_template.yml | 2 ++ .../connection_delegation/stack_construction.yml | 7 +++++++ tests/ansible/integration/process/unix_socket_cleanup.yml | 2 +- tests/ansible/integration/ssh/variables.yml | 4 ++-- tests/data/plain_old_module.py | 8 ++++++-- tests/testlib.py | 3 ++- 9 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index 7289f173..82a78b02 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -34,7 +34,7 @@ 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', - 'alma9-py3 centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', + 'alma9-py3 centos5 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', ) IMAGE_PREP_DIR = os.path.join(GIT_ROOT, 'tests/image_prep') IMAGE_TEMPLATE = os.environ.get( diff --git a/docs/changelog.rst b/docs/changelog.rst index 2692cc07..db6c1c38 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -24,6 +24,7 @@ In progress (unreleased) * :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 v0.3.33 (2025-11-22) diff --git a/tests/ansible/ansible.cfg b/tests/ansible/ansible.cfg index 4060d9ea..533c58a9 100644 --- a/tests/ansible/ansible.cfg +++ b/tests/ansible/ansible.cfg @@ -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 diff --git a/tests/ansible/integration/connection_delegation/delegate_to_template.yml b/tests/ansible/integration/connection_delegation/delegate_to_template.yml index 8cd50f98..d46103d6 100644 --- a/tests/ansible/integration/connection_delegation/delegate_to_template.yml +++ b/tests/ansible/integration/connection_delegation/delegate_to_template.yml @@ -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, ], diff --git a/tests/ansible/integration/connection_delegation/stack_construction.yml b/tests/ansible/integration/connection_delegation/stack_construction.yml index 58abac7b..b38d835b 100644 --- a/tests/ansible/integration/connection_delegation/stack_construction.yml +++ b/tests/ansible/integration/connection_delegation/stack_construction.yml @@ -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, ], diff --git a/tests/ansible/integration/process/unix_socket_cleanup.yml b/tests/ansible/integration/process/unix_socket_cleanup.yml index 4466aa2e..4dce1d41 100644 --- a/tests/ansible/integration/process/unix_socket_cleanup.yml +++ b/tests/ansible/integration/process/unix_socket_cleanup.yml @@ -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 %} diff --git a/tests/ansible/integration/ssh/variables.yml b/tests/ansible/integration/ssh/variables.yml index 5eb54dde..17d53350 100644 --- a/tests/ansible/integration/ssh/variables.yml +++ b/tests/ansible/integration/ssh/variables.yml @@ -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 %} diff --git a/tests/data/plain_old_module.py b/tests/data/plain_old_module.py index 7239f76a..2c14e0ff 100755 --- a/tests/data/plain_old_module.py +++ b/tests/data/plain_old_module.py @@ -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): diff --git a/tests/testlib.py b/tests/testlib.py index 6577f30b..da0c17f2 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -53,7 +53,7 @@ LOG = logging.getLogger(__name__) DISTRO_SPECS = os.environ.get( 'MITOGEN_TEST_DISTRO_SPECS', - 'alma9-py3 centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', + 'alma9-py3 centos5 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', ) IMAGE_TEMPLATE = os.environ.get( 'MITOGEN_TEST_IMAGE_TEMPLATE', @@ -725,6 +725,7 @@ class DockerMixin(RouterMixin): # - tests/testlib.py 'ssh_args': [ '-o', 'HostKeyAlgorithms +ssh-rsa', + '-o', 'KexAlgorithms +diffie-hellman-group1-sha1', '-o', 'PubkeyAcceptedKeyTypes +ssh-rsa', ], 'python_path': self.dockerized_ssh.python_path, From 14e833470501302afb3b0a8ca40a70d5e67da70f Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 16:03:45 +0000 Subject: [PATCH 31/35] CI: Add OS release coverage: Debian 12 --- .ci/ci_lib.py | 2 +- docs/changelog.rst | 1 + tests/ansible/hosts/group_vars/debian12.yml | 2 ++ tests/testlib.py | 2 +- tox.ini | 14 +++++++------- 5 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 tests/ansible/hosts/group_vars/debian12.yml diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index 82a78b02..504f3ae2 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -34,7 +34,7 @@ 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', - 'alma9-py3 centos5 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', + 'alma9-py3 centos5 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2004', ) IMAGE_PREP_DIR = os.path.join(GIT_ROOT, 'tests/image_prep') IMAGE_TEMPLATE = os.environ.get( diff --git a/docs/changelog.rst b/docs/changelog.rst index db6c1c38..b25cd08b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -25,6 +25,7 @@ In progress (unreleased) * :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 v0.3.33 (2025-11-22) diff --git a/tests/ansible/hosts/group_vars/debian12.yml b/tests/ansible/hosts/group_vars/debian12.yml new file mode 100644 index 00000000..f2f5fd56 --- /dev/null +++ b/tests/ansible/hosts/group_vars/debian12.yml @@ -0,0 +1,2 @@ +become_doas_available: true +pkg_mgr_python_interpreter: python3 diff --git a/tests/testlib.py b/tests/testlib.py index da0c17f2..f536f22c 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -53,7 +53,7 @@ LOG = logging.getLogger(__name__) DISTRO_SPECS = os.environ.get( 'MITOGEN_TEST_DISTRO_SPECS', - 'alma9-py3 centos5 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004', + 'alma9-py3 centos5 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2004', ) IMAGE_TEMPLATE = os.environ.get( 'MITOGEN_TEST_IMAGE_TEMPLATE', diff --git a/tox.ini b/tox.ini index d660054d..d72b6dd7 100644 --- a/tox.ini +++ b/tox.ini @@ -123,18 +123,18 @@ setenv = ans{2.10,3,4,5}: ANSIBLE_STDOUT_CALLBACK=yaml # Print warning on the first occurence at each module:linenno in Mitogen. Available Python 2.7, 3.2+. PYTHONWARNINGS=default:::ansible_mitogen,default:::mitogen - ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos6 centos8-py3 debian9 debian11 ubuntu1604 ubuntu2004 + ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos6 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2004 # Ansible 6 - 8 (ansible-core 2.13 - 2.15) require Python 2.7 or >= 3.5 on targets - ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004 + ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian12-py3 ubuntu1604 ubuntu1804 ubuntu2004 # Ansible 9 (ansible-core 2.16) requires Python 2.7 or >= 3.6 on targets - ans9: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian11 ubuntu1804 ubuntu2004 + ans9: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian12-py3 ubuntu1804 ubuntu2004 # Ansible 10 (ansible-core 2.17) requires Python >= 3.7 on targets - ans10: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian10-py3 debian11-py3 ubuntu2004-py3 + ans10: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian10-py3 debian12-py3 ubuntu2004-py3 # Ansible 11 (ansible-core 2.18) requires Python >= 3.8 on targets - ans11: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian11-py3 ubuntu2004-py3 - ans12: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian11-py3 ubuntu2004-py3 + ans11: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 ubuntu2004-py3 + ans12: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 ubuntu2004-py3 # Ansible 13 (ansible-core 2.20) requires Python >= 3.9 on targets - ans13: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian11-py3 + ans13: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 m_ans: MODE=ansible m_ans: ANSIBLE_SKIP_TAGS=resource_intensive m_ans: ANSIBLE_CALLBACK_WHITELIST=profile_tasks From a208daa461f4e007b2ca9449249763d1f93ee0fe Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 18:28:58 +0000 Subject: [PATCH 32/35] CI: Add OS release coverage: Ubuntu 22.04, Ubuntu 24.04 --- .ci/ci_lib.py | 2 +- docs/changelog.rst | 1 + tests/ansible/hosts/group_vars/ubuntu2204.yml | 2 ++ tests/ansible/hosts/group_vars/ubuntu2404.yml | 2 ++ tests/testlib.py | 2 +- tox.ini | 12 ++++++------ 6 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 tests/ansible/hosts/group_vars/ubuntu2204.yml create mode 100644 tests/ansible/hosts/group_vars/ubuntu2404.yml diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index 504f3ae2..9901c742 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -34,7 +34,7 @@ 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', - 'alma9-py3 centos5 centos8-py3 debian9 debian12-py3 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( diff --git a/docs/changelog.rst b/docs/changelog.rst index b25cd08b..ab048938 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -26,6 +26,7 @@ In progress (unreleased) * :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 v0.3.33 (2025-11-22) diff --git a/tests/ansible/hosts/group_vars/ubuntu2204.yml b/tests/ansible/hosts/group_vars/ubuntu2204.yml new file mode 100644 index 00000000..f2f5fd56 --- /dev/null +++ b/tests/ansible/hosts/group_vars/ubuntu2204.yml @@ -0,0 +1,2 @@ +become_doas_available: true +pkg_mgr_python_interpreter: python3 diff --git a/tests/ansible/hosts/group_vars/ubuntu2404.yml b/tests/ansible/hosts/group_vars/ubuntu2404.yml new file mode 100644 index 00000000..f2f5fd56 --- /dev/null +++ b/tests/ansible/hosts/group_vars/ubuntu2404.yml @@ -0,0 +1,2 @@ +become_doas_available: true +pkg_mgr_python_interpreter: python3 diff --git a/tests/testlib.py b/tests/testlib.py index f536f22c..15016964 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -53,7 +53,7 @@ LOG = logging.getLogger(__name__) DISTRO_SPECS = os.environ.get( 'MITOGEN_TEST_DISTRO_SPECS', - 'alma9-py3 centos5 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2004', + 'alma9-py3 centos5 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2404-py3', ) IMAGE_TEMPLATE = os.environ.get( 'MITOGEN_TEST_IMAGE_TEMPLATE', diff --git a/tox.ini b/tox.ini index d72b6dd7..28c97496 100644 --- a/tox.ini +++ b/tox.ini @@ -123,18 +123,18 @@ setenv = ans{2.10,3,4,5}: ANSIBLE_STDOUT_CALLBACK=yaml # Print warning on the first occurence at each module:linenno in Mitogen. Available Python 2.7, 3.2+. PYTHONWARNINGS=default:::ansible_mitogen,default:::mitogen - ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos6 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2004 + ans{2.10,3,4,5}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos6 centos8-py3 debian9 debian12-py3 ubuntu1604 ubuntu2204-py3 # Ansible 6 - 8 (ansible-core 2.13 - 2.15) require Python 2.7 or >= 3.5 on targets - ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian12-py3 ubuntu1604 ubuntu1804 ubuntu2004 + ans{6,7,8}: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian12-py3 ubuntu1604 ubuntu1804 ubuntu2404-py3 # Ansible 9 (ansible-core 2.16) requires Python 2.7 or >= 3.6 on targets - ans9: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian12-py3 ubuntu1804 ubuntu2004 + ans9: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 centos7 centos8-py3 debian9 debian10 debian12-py3 ubuntu1804 ubuntu2404-py3 # Ansible 10 (ansible-core 2.17) requires Python >= 3.7 on targets - ans10: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian10-py3 debian12-py3 ubuntu2004-py3 + ans10: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian10-py3 debian12-py3 ubuntu2404-py3 # Ansible 11 (ansible-core 2.18) requires Python >= 3.8 on targets ans11: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 ubuntu2004-py3 - ans12: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 ubuntu2004-py3 + ans12: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 ubuntu2404-py3 # Ansible 13 (ansible-core 2.20) requires Python >= 3.9 on targets - ans13: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 + ans13: MITOGEN_TEST_DISTRO_SPECS=alma9-py3 debian12-py3 ubuntu2404-py3 m_ans: MODE=ansible m_ans: ANSIBLE_SKIP_TAGS=resource_intensive m_ans: ANSIBLE_CALLBACK_WHITELIST=profile_tasks From f191f050bf0e0910e845bdf54ad23185246ef388 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 21:37:42 +0000 Subject: [PATCH 33/35] mitogen: Log why a module is sent or not sent by ModuleResponder This should not change the logic --- docs/changelog.rst | 2 ++ mitogen/master.py | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ab048938..a0d27c1c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,6 +27,8 @@ In progress (unreleased) * :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` v0.3.33 (2025-11-22) diff --git a/mitogen/master.py b/mitogen/master.py index 3b896504..4de32586 100644 --- a/mitogen/master.py +++ b/mitogen/master.py @@ -931,6 +931,31 @@ 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') + + return False + def find_related_imports(self, fullname): """ Return a list of non-stdlib modules that are directly imported by @@ -973,9 +998,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) ) )) @@ -1138,7 +1161,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( @@ -1146,8 +1169,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: @@ -1178,8 +1203,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) From 83b6cdb616ad838f431a7cac94717f6adb4c4698 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Nov 2025 22:16:31 +0000 Subject: [PATCH 34/35] 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`). --- docs/changelog.rst | 2 ++ mitogen/master.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index a0d27c1c..1824f6f9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -29,6 +29,8 @@ In progress (unreleased) * :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) diff --git a/mitogen/master.py b/mitogen/master.py index 4de32586..e495197d 100644 --- a/mitogen/master.py +++ b/mitogen/master.py @@ -843,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. @@ -954,6 +960,9 @@ class ModuleFinder(object): 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): From 8e6a93dd0fd17352d61c2720eea6c1ae5b623ad9 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 27 Nov 2025 00:12:44 +0000 Subject: [PATCH 35/35] Prepare v0.3.34 --- docs/changelog.rst | 4 ++++ mitogen/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1824f6f9..3fd0ab54 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,10 @@ To avail of fixes in an unreleased version, please download a ZIP file In progress (unreleased) ------------------------ + +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 diff --git a/mitogen/__init__.py b/mitogen/__init__.py index 8e1d2ec3..740515a7 100644 --- a/mitogen/__init__.py +++ b/mitogen/__init__.py @@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup. #: Library version as a tuple. -__version__ = (0, 3, 34, 'dev') +__version__ = (0, 3, 34) #: This is :data:`False` in slave contexts. Previously it was used to prevent