From 09b972e96e5f16f48ae5a0b4b79ee31630287f10 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 26 Feb 2025 16:48:41 +0000 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 zcmV(zK<2+6iwFQeojF|s1ME8scvRJycM?WMh|GY1k<}7jk_eQ9knjwS3j+sD)~Lkd zDk3ixDi3uAw1pL%Oy%zNw)w1Cqr22VWn~2|TSX(9I6P$H`e3ApX=`EXqTcMpG9b#x zYx@8HIro_ax7(Fnzt4OhbME=?|9+iw@41BTyLY7Y4gVFw-+XL`|3VP|yO++JbN9&q zPw_7*8ddyV{LgRv=Pq0_^8YgaB}JvhMJ1z4M`8R+$Bg+d{^vjb^Ow%K?>xu9c+}|9 zA~XKQV~QdEqe>tR@(RC;|2O{kV4!?zmdj<#v!zKA&bGMu+%Mpsr%fEcG*)uMZ(peo z;kwVk|HWFDZIq-u`o-A=@Gt)t9G?G+0k$z$FlCoWHo7c9ce|jwUEu3ur4XZ!9zlKm z$HDm*`~`mTiSY)Y>v9e~TZ9~a)UC`C*SejO1YA6R@-*IiMLpVM^YLVl&g3JWFWW6u zI-J`T{`Ji6CLQ>xOXte}W-nZ_FeuHQedppJ*)R0d?f0+e^O;Wg20o8JWryo>6Arj8 zS98F1f22BrOZ9fRF?Yc8CUX9H4*0Wv;eQAG*8*PXfWIu@V;%6<1>Em|?-p?8fWIl= zl@9p30zT6L|BZmJcEIbVaCnUazD~gF9q^|Ge5(WgjDSZS@XZ1qcfhv_c*5x)0Z%#o zBjD0{J3J=f`RhB$D+m107Cx?Zz?+`qV~qp8Nxxm@^v zLnm?_a7~x{OFKO05Ff`n;HzH{{YQHT%3D)QUDC{5Fwd0S@ZPEP_ zqb4)t7BSvOBMZ0X7DeTigLAZ3L%`r=_$i8NeMh+@1%b6$E{PR=#^g_#TJlWY0EvYU zr!>mgHElIuo6?oZUuB}Kj)hn5@JLMcyF$mAI%9{YD9U0}cCRH|KnSPvLVsdP7K=oK zePGsq#f41yk%!4EcdupPoxbFMvRE}ow@@URoMMWPMfL@AS@>lC6*)|~ABbMtkC-eR z^(7MwG>x)ICUhlUZDq=g!JC*eWpF*ff_+(LZ+L80=t4GZnUBdQl5XaTCqHI#nq^lH z_JIXV-ovtA9Gr`n<5I!?OsVv&`6!dgJO~0kKq#MN**@kPPtraVlu^l`Y?e6XU2kU}+wTMY%jGUeo}2uDsqP#ieV47-jSkEtAO;?$-sCA)-N#0QyOmO=T(lW{9VmK} zsbzl;_}cI80UEW;{Vxy!)r~_HD@Zf>WAIXs)iZ7kg8R$-0MIs{ z$z(v2yZ;G=T)r5Gf?d<)-E7CHY?gV8DegHaf84I+K8&U8z5%9JXL|2W9@dJ%MZkFP zg=s%4WNc1hYC;CRc`x6az*{vM#Zb^*paj|=K&gfo7T)ejYIK)x#M;QdxQhWUF2GA?(*5x${B(@v)7dH&$Jx9$*^WPEGgmWGb3NX8m-Oh$Gok|BWl%u6&fCX! ze3Wg-^G23IAUfAa=i_dm@>N2ZB%}2tDP7!gk}$cVWr`fwFS4YC#rT#YaGIFSh1T%? zGXo^0>a&Vsa?f*J}1dQ59wW~&M=VVYlQ`b?D^K@%oQ5WT@TdmQPAzN0(fJ;>_jG01CGXo>7pl$`-j$q5nYwZ zRV{Kw0^74#K+v}v>#xFr{2yeLx zEr-H9mZ=pfrRr_SsW|o$$f>I?AsMn;!LnoRB|)th)xSJB3yY9lWT6Rm?EE zm}kQzCf_b*+hXoNE2lT6Lc?*@=&>l|&dvmfDil`S)?A6DCnm4HkCT~v@;pr1mC(*R zjS^bZ=J!!vH6Nk8iXQ1GqY_8-jEajZunbX~Mv5A0jq&ZUO%w}-BWzLd^* zs|SLaVdCIw?Tfh)CaF@$r>(mSnA1Cs)-#+t*C(dUXr@r227sRs_=`>acQ`&F$dMKp zTA!eVrfbmS4_9(MdcH}VG|mYJDR?haR8m)|B{+Egh$Ro72GSu(uw<%mO`xH$n_&!cJRm+;WgYSbxwSLJfDB?nD8q6G4QR^;SCV4s^w9D6kQKGZ1uc6K z@1ff%>+rTZ*~PK7kN44jXx=XQG2$rXd8-v3z@@-KTQ`?-*28la2Yggj2tXwp8gJyP zKOf!r%r2tHBM={8A2*P88WxSF-$_|cj0LvJ2bsJUBY)9uS@+p=r4i#jMNv`za_$F3 z#p;6IMXugPPF5%zY1fMy?f;T*P55@=t`Xl*`@%1ElR$#)|-;o%UmsKR&)he1{B!%2np!UMp8PTvc1pQMLOip|_C>sX@_(?)nOO z$JKHgjs9HU&xIyKp(S{Lc+tMc7xM)}Y>W9HevG>hT{BCl3nr0*Bvl_UBosN@q|Uh7Xxh~>gL*( zWo4U232Cj2>GDyrZ6bT_K^<>07&P8*CLpNy;w2Q_T;f;D2bCAG$RbMeQiV3Y+Jm?8 zje8blB}YIZ!|jfEfNN>qA85b7Zw`MS3Dxd{=f*UXLn(Y*V@hQnZjtFl3(uTgjm8dH zyijZXD4Rbv(n}wFg+`PTn-I{vkx%#=o)o>Mf?YEUuS!e7@yHeGEu40RzK_>2f3A2v zqM4T|xO9n2ppCcQZ%Xn2`?xoECv~IFBLL)9im9V`uJK9NaE<&ts5%))6<`JHWwCj8!D;Q_;JInYcm zk$EWMtrxp39T4a*w$Xbd2OZTzrP;lGIl8`WRc7^TT&c{(+cFJti)HQHYRRT=O$xQ} zpftBm&o=AFQ7vzMO|?j-rNTsAj1g5$MD;vcs(ag7Ez&nvET34`7CI!haJH5T<%Kv4 z>@M1MhqWRQsZVyg>qvFH>#%h4|3M92ktu>ZYQT|{d zF!APn)|+x5niM;@K-9$IX%_m#%GEeS2H9EW`?wN~=W2K22)_SWS(i7GwJ@Mc{j{GdS;D611m)YFc!{RTD56ccRmAnvVt=05?41KX#)Q7od#{3?$s%@pL_chue3^3%S#gas+ zl-BFcC`e9S73aspr17DnnG8Kw+CTBY<7>+XBRN?Znh<0^K8l7O53f3I2gpV; z!WK(HpW2li?iA+Gva~ZTdpfKO9_Um_I=wdX#f;!kJxX{oZ7+!&Ym|6&_E~sZ67hBX z8eV(yX35+Xhk85TM!#CA_nDaX$rkZ4kM_8EJa1##UK=>ycv%9biX8UmqyO6Rp3Qli zJ;G;6o;+mzXcfoMU!gyw&*6GGT-O)}m6dcH+SqWa-3jIE_~Eto!c2Op)O?Cw8RF1^ zvF~2TPZtr1e^DZ4M#qSt0DAYDg>NwDxabt@6hH849Ysr>d;IbNl2VIM%cn4MB+d_{ zacC+Nuc-GvOKP=nwQzvm&d-lHa020btNDXqZHvi+73^ek91oaqvZfJ-4D5})5C4+m z5?oOUmmeC7-uwXuOsy3Mvm?=fZ4hg5=q8pO^s{G}i#AonP>ntA`yll;gMGMgg- ztl(@WgUcSFLpmZ;KEc*WFYUwRIRLvop${3mmJ;h z;cu?_xz&{;&za;0Ds0P9QzpZ0SbiEuD5^Wf%Z7YWTzN`G8&gNWqkq`O)QLTex0_FS z_4B<)R`Hjn?F23Wf#E=#i%w%bK>;JmuGt!?&$& zpZT%KV|r!|$->v?k5P1ugO}B(#K8-u;3gj2P1fuz+CQD_a&N591-fJ-+;L5 z)u2SkD{5`jOwwSQh#R;uI7q>(bWEkT!Nfd$oLDpnD`%S=DUbCDV|vjnIn`v%__+B*B7w`Ai32UHvT3ZlfcXjN5o(r}UY)Zg}Bp%M-ou+~rfGWQ6Xt;$=<&B%5&z--!&@D3r!-p=BAe#HE;mg8OCnX*p`iX^WL7?MoarJpu0 zz|Tgy2aB~Ii0dw)rO6=*S-|R;i)eHXZu+Wd0>PGFi))DGCRH^5imCw59TSER_`!eh zMcp%80m&P=7e88|^fH_^{@)FSn+imqS+bFT#*^gV0U)hz-*#GIkzJPfl!r7bk-PD; z<)50Qt%vcOR>Fn+mTO6qbk|_FaJp}BYU@TyfZCQMU-d|B`iS{K7TbgW-vZf{4MQ8E z9EVUL9~W^|M!N9(uPOL#5l<#HZt~jR&HN0-vw?q0#P1T)I6?~c)!v)Tqc9Mjn!=45 z#p$9yGxslmO;p+AIG$;eHZ3g^pg_?gL4&pe(kN&Htu&C(38sP;6a^m`iXczhqy^>C zI<0hwQFqmq-F01eU3b}CU%TsKK`luOeSjhqR30i76()p70c}fx&i|ZqCus`0pZ)#+ zzt8{sb(dyl?)%(x&pqef^I-LViqbc!a$(O016S%F_s2v|RlRuQPglT*ujuoUm2w(p8Hf+jNF{OD^P_i0DbrLlq-&w`zVe!w6q=-vFJ_ftP4yDy{FiOXtAeh<@(#AJ=_v! zG%_6;Hnx9G!m(x1*s^GBPs+cvMJ_v+p^9U)?B;Sn&MlLmf=Fr46{UMRvFWWyC%8Cv zD~c7pgxMrxG}rT(3wf5%A+!dHd|vrh3}mySkWGC&3fU}{aeB0s-*W#EvgH*FvTC`s zXgN327oC2ZY4xC`qI~vGsKT|fCwu74NcOSZ6@!7&+3ZF;4}QXP#gE1kEG)8+7a@%< zIVUed8soyZM=O3lTJa$wELbwCzm&&jn<^Irc|bGqOQ{PGAvTF|LBUQf<41{Z=EXn` z>1NJ5I8$S3rtmMU>zNT{zn`v50J2t=KywqG1evC@<>>6SMS3u zDsX#b9x~Pv7@`zlL`nEbYILKHsL?D2)LB3@sh+~3(q`12 z-J(w0?SI2j&7e`upivEwlX4?;NHuvF6-iU+e|nU(VSQT|GlP?2ed|hun>OJm56T)H zZA_(`iyWxEe0{9ocQxjy6;!UnHzY#es(KfW@SKN! zOUm1~_9``VNY4>OpI2f|KA0&`q|WfzUZsY`l=|$qmzUy(sDfei2u+S2^&|jyuJ*FnAY*n<{^2n3^E)O$jZPc%Yr*6a%gCJ79aXunP!4|G&1}d)M$akw8mlU>JRiv4BUV-H-`ymyO{>-Zl)&sNHSA_ zCy*s$H+RRv@(V%Q0pwGj{#(kn`|DV6M@CKct2%k?XUiuav;TySBSP@FzE`(!OJa?@ z6u7+0T2#A>_JPgW9zi-w+IXMJ&A*B5#>Sr+w^r>@Ym6G{f7c@wp{xW_ybENoMMW=L z51g);Xcgstayk}N<&1`@s+=am**MUFk;-Vt3zTS0 z6R0p>^~cMyB>nW*G;8~G{0<*NuTTTDRy_&gTll~=>c3dd)+wt>V!#p!*co`fK|mkq zUyedNEZBiQdO`|1u$n#M*DCgy29Hm%M^i`#0(>v%XLLsg(W{4i<)7kdqIw4NQEXPE zT10L~(j(@$4D;(EJ!J-b(o3W*i*#Oksgph8SEjrOy_8-uFqLhSsxQpi7Ug)cvmd`M zpu5ZjHQZjG6W+^joL=Yuh>II_rvk^oo7nfjtNstev{DIuks8xYKm5cT>E|jjt4X7=m zE6>K_%hO3!KQMCfZbadkY4L}`aghRXw0Hf`W71+!ZC^r-aX**xR$S}^X858L1WhLH z?%gQCKiaU5(!^H1*r~^H=iYcRGURCqXmhOVsX1aJJRz>fNB~$AER`NDH3UnIxU7^q z$hhbLxXT7`f{Uc+E=y^Nro>&AQp5EEN9^z-#u1?xAy2I#x*p!SO%pMcf&BD?=ZZ_1 zMNa93UQ@p9)+=jml=rtIIeL-ECH@ryafDj2`C>6_)Zm|Jl3`fy=4P_y+Q<&zMItDw z^>37nU=QN1$RpNqk&6p2 zjIt7mVU(5V%1cZQ8;D5^Wv}nNY!ir(UL#AtE|OlmTV)ja_ca^1-E3!KTf1h(UF>1V zS|N6WxglD2Y*`>**T&}hrXyyf#LuDof4rg_Z(fmU+>f}O>HofvSOm{#pZ>LlF}wZ? z3nRNe*WR=1C)2JEn?G)`Z1Y#QV6VxizX9tCaFYI-h2$B-+Zn;p9BpK;*78*^Xvi*l zA#owRPoF_g-nF6KT!a-7`~#0K7_K23jvS~(ZaN3EsP9^!GU0{)KjT3UJQ+Lyw;1EE zWg=`8yw|q>G>_%ESEN@?-St=GtK81o+hg&=trpMWyw}HmAKCsh?9Kd$@y`Te2Kma( zmvzX}Gaq<+h5IUk=?0i#`44Att-ZxiTzre^B@}q-;%7DkjXFAIH%!23n%$cw8nGd|nXBGwc%q6?|Om8cnxw%5^m$U1O)em+rYr31QX$jMvv-nd zXBNHrS=VWUTlF+f`E^Ca5<$jh$~f-Q4>31L_n-ll{&b;ASB}C18WRiTfuiRyY+Gim z^|$UP&J3C^M1=c~qfe+~z#ItdyAVA`P;YDZ$J|x2`=!Vwt=>A1T>$D9(Crr9ewuyy zhvun$yk{P%E1v$ zuSKeRxu?26;Z>m8JriHK6z$wQO>0L3+cp=dc3)}m3Wcf z=6D{2KWhhzo!9b}dGzTzzS4x{O6cuLzOp|(tmZ2d@$(!KT>GhA!u4f27aFdn5v~ zC6A~en+Fe}ZcVf!9-vk;9#Kh9B);=HPK5@pR(L__o@_jeEQ@HOJF)lY6s z-L9}xL^V%2)D?bUE6Sa6WeU@XWBKt%vl5SJ>4`w5_K6KIJgBGT!_Sjc5^Wh-MQ;^VF0QHy$K7A+cJ!|u6C*61|W-gN-P*sc3x=;TA<{*z! z1#Rkfd~yx#8I$wb-X9mU_s=_pYGDuKwHVrxr&#^~7{Hy53Pgs?=sPvfsUHBG5%|JxmG2=5>#7v zS6=Y}f_5Ug87OC7i+Ub|B4j4?Ry=|u?Yv44NBHPD#*3U^|N7M7nnoSYHqbXr1$hR4 z^47Ef{$`zzut=u!5wCPVsEdpw};u=M)tOVy}ircmQ%|$G_V!)%*KP>5O`me z3&+~}zC;hu(Y$C!2~v1zD$@CgpL(|?CvpLe2y)F+2wke8$q=c62GcuVdj-kMk$lgv zd;zkM2^cVkX*xnr=!&qn`hPH1Ho$ttoKCSfsbOnhI=z}kkbUh!uOvDJ`zuS*^iIO@ z$j`E*hm~GQDVS8klJYM0N+Q=fvshAV?<5|Ra#>ROrCv#?m~<^mGIjS#8i+{=EGf`C zX%KChclJivO!NlP$w+zU8kM5?@e9LOcJVv=Ikszc(q=>_ah;J1oNR^=Y<*T`z+33A z4Daw6h&X28#g}5%3i?ws@|&u7LXWl8^bmP(G65?m%5%leA!S0Ogbq~;)f~v_HOgPY zGGPTHc??(N*%i$b=?&xeGAPGOm;el)-_J+UQgxbvW1j0L9LXX)`UrnG?^SB`IY}Nq z@|eO`8I@=Y4TvB^@q%3|| z>1Rzr7S?>8X_v$<`p8<^y;k+}Am1=FqKuK`(+`_MuJ?BOsIa8xr{H!)&ehbreHG_- zyNHQFoR20D#=p-JG_eVU@2|21MCxb)p>!2Xz=aS^pk=U_CA7wtAWSc035e;@1j6(2 zECKO6nm|}SoFxQe69~r*t|*E0q%aTxbXHgQ+64-KVi)+Go!DFe)=lzIA373ZnV4I} zsffV%D$1#1XIxnl+XT2P_l!S@AJO*U(T6ID+S9h-G0M+fO)Jbts3FCvc%^s})dlo3 zN|xzM_XB+$fI03hOt2zLSsOQ&Qb>y znBb60`pe&6gE zFhS_L4 zr;Uz{%o^|&RoCZT_FAK&ru|#W8fbJh4Psx$voE_F9s93kUv6e!S{ohmwL~D&hbgEhIcy}@lQqfE8Y%uTUw{R#L1J~R2Jf^|s&FHGe+@9bno#032ChW4aQGpi zCgQ3c@@(`b%RdZOm3uu&kbfgAgYwmwolg3*gc`;HDr?p3+oRc2Sa#OC1Sp|p)&$GR z!|PC=;}uns=H#AR-Q@&Sqr7G?BM7}Sp`W8TarG-2k0AnMYUDBxyZZ)S&WfuHG?nrq33%b~75Q!Sezjc8x+%P7Wtp@Q{6>QL7Ou zwjo$&*8@O*F>$yiL$Kp*YKX3M48}k27qsh71+oybu z*6bY+s)0=$-( z%$8=GPuIuhOHY#Or0qdDDY!i)SjStAOJ7;)Ajx3U>Jo$7`*L;C{$N-eY)Y`mmi^pr zNe5&mr~`WrvmGM?!1JG^vo`766rU$`qb<0z zPjE+kzIN!DgLoH+N6^(zIl|G|ecLrQ4-^ zGhhqO-RuW!b8HXT9<)7FFEl0)fy_5NdNlE|#{A@}w;%nWG2d8xZfQ!y5C=Lq2JpU2nU`Hq$A!17Zl$r9uINJnR*?I+1%mi(K=7~5aQeqyBOaL;8B<7r%4{>7 z(mv{ubWXfrDji5}JzdgKs6|HCr()O?J}Mn#U$AHhLk++voYD`4IEV{^e^CaE!oPxR zuxm5QMJ2oRnk_gOOM7OQz@3)+dvMJk7Nk@1SHL=2lr7mYsXvq;UD66Ts>2c8AxT?3 z`B5QwJiEH^VSEoB&#Eq*%bqf-3m;)mrs_gBdrGS=oX4Jw)rCdu$xvN5UtWw22KY@g zw0&&VsXG4s8QLUU^$Z>7o1ry89(<0&XM8|iI~~Qy*o7ml$HnZFx^XpI0y7IY)8-(Z ztHsbiu(-*AynmH>lpv*=1)xi24G}t*)FB^#T%*ZFWWdiWt40LeVrM z4fGY(7wNRizZeL!bXcyvh=*fcE~y>g+TaanMjBz&VVZEe*(e|Am1!rvAy&wKvV%P= zujrNGZzyxaK#Z&!`8Sk$oE5naid3EST$|Uvo%1BASbZuSA1%J;q`VLcgb%81WU6hX z62?q`X)C!Zni;y6E{|uuPFDiqR+ryno&^aG3pCj-pSZ{he?gXpuo;N~qs`h_GBBop zHl$tM@Wmx{%l}kM{tj&dWwDjRrab+C&?dW3RrIiYANJB183BIz2jm69 zEfYd^oT+L-Zxz%|Hzxm*k~bp~Nirq#G1VpEO~Wd4y2jH#w$2BzXR-N?T!?^Q*)AtT zA?sqZfu|!#*eB8iUk>8Va&+77pJ6r^NUeZFP1ERbTB|d={Q8k!64K9uECo`~<)03_ zbCBNz``{W5R%B{6ziCxYcJPQXc+8O41z+RnLt>Xju}OP_$K!3{w-fW%r>EJg)2)Ki zD2R&A`#tSuVuRLY-Q)cy|Gviz`>mkV!LF(E43Ij4r{cw96K(5rta;U|j5mb8Yp`h< z-&3&-7ijHbO0Ha{6ivX<4a$sG`Rp!LS9h2`}M0+(mrqP!3=^}Z-}xllRK+LvQ zvHFFNqA!N(7i#IcpA0VJICPh7a`*_;Ik;>b6WPYv=zM>pVA)g;|4M_ zeb3w(<$|u8k6h53^u<=bT4VOMIF;>}aY0*=9U~?uS>=L~BV5pL_YoHq?^L;<of5Sw9s5Bozq_&r#w$81pdZ(LzORShvr5L+MZ$ONgC024G!WrC=s z!zwpIECez^2G3Q|%)|ttvOo_LG>`ZgVuFs!e^d+4{237;AmWAm<9@#{$_L4SMS&?1J_yoY--no>f8{gGvSJ2tKzBwsAXhSRKp_3^fUj}% zA#n#t`_dlbfW83^DA{1IPB!4Vh7L7&AOEzi zQkT>z?l6hxhwxMC>O;o?T>fdlr>{pIiv3!MBBXf2=$k0NyqwSh*Qjhvf#nGg=<$7a zy)U7@kbFrbYGfFf#?n$1SaCzG$)KvFNcHlk26C)%udjy@by)(NT`Z#Gbd^w=&i!5R z>t7yC3aWVJom0m`JN_v6bwC01qjFK9Aq^ouP!KPSz+jGOE;Hjx6NO3qR`6At$;z|} zuu=a>4rZ@3n<$=-{!S`wH&2J>BzX2_P^0p34DsRn5bvurvY8CEE6o(d$7(mP@J-;f)=xn-hAlZ`yl2BFu@D_CTPw{t&E7tO_@Nn&%%0^cY|J9s@{+k(&3@Eo>w`~G;7r;i}DK|)nv zCHI4%)B_l_-tQXqdKfERyj5(QPG@+9@`KMH)(#yr0=hh5XxsI}H6=-wfHDAg#PG@4 z^|)&Z?uMC#K&0vn$mB|Uozf)=4C%WWh3lE!iX#6D6(&y8Qvcefe1@JVu;MB}OaIXO zCk)qMFvo7ysNt|Y&`p-13+tEM*?IXmTBiE# zH*(&L@JAr!;s~bAr6m|oW8T3s>e42a=Vau1!{LVe*>b0STopuc^i_)2D5KdfJ3kKL zIam2a4RqNfJEaclleAZU>mk;geSnWy*xIii#=a_eZsCJRyMI)MQ);mJSH}r%Mc$dL zF28#3fbew8|3f#kk*S9PPZiuJi4TLBKVINIDSu30Sv8V#x<3bU-JlzpE7!ocNe%Xq zTqKgjVfp^`G{TSYxf>=yY*OxzA!0q?7X$Wsps}+KB5kmNkY$wiR3dd!>a3mKQ-Eqi z_d-?8o|JM7bG4T9b|}r^(}J%wBUBdAwq+CfE}~lK9YBnFM1oJQB@BDoJX}+nB#2=H zVi{nVB@lW!pwR&H2~eG$SAH}PmZ~|+Yn1j|nxQJ2CKvyBO#-Zkhhdw@!lM=94sf@} zuO@s-qe*e^by%7o1Ca?J*v;-|-H+hd1$XED_pKPrZ~1mWNk6)Mru9-I5ZRK68jIVe z&xW;fVpe-y9>yV+T;^==0BH}@R`CsXqT)PtWw-$R$isMAn~i-*qrP~sFCOfRC(@T4 zg1h4}3Y99=ZAJD?{$eh>64bNi7SszBil&Vb@6r^YmBldGKxxID3hn^)XfHT?II-wI z0NVM-EjfS{TtfQh*Z(mCX%*1X!?0D}igroBqzq*zfUp$h5%--Io?PaY@bOkM8TON$TltXmKu1;srtK5TwuL$w&_l z12YX{i+)_sK3-Ri2Fxdr>Qx31Tp2Xu1a241WS$G21kY`JCXiu1(`9~`&zxai&S$m<>Y@j^Iog7zs_zQn4pZ{w*%9if!1J z`u|({nC5+xvmV>Z}iSL~dpo z?J1||e7@xwA&i>@^dB#Yn}CZII@$$mXK|vyztbH`?}MulHkXTCio!1XErI*%RSIv! z7|laqVcI@Ka^bXoSu)5eUgXP+1>ASxwh!FWl<|Fs3 z(q}@)SHke^f^{dbDI*1b%6`G851BDG@oo%7?4aFWkmi^T^6Sq>S_LV=5~#P;>_FL-D0&64 z0Rg}cvGbr8BoDyX&v`xV+%EsPKVApqj<||TG{b2Hz9Qh5wsCybKJ}*H7$Un{_6czSN zAR+AmzQeqki}WnQjej|tapU373MLe3ytg{V@47)wml|A|$K=;A%9s|0k73WXZDO)n zjZLHOz=d+?E!z)vKU*`PwT%VPK&6 zL(0R5KN_O*MvZb^532 zuz-=^Y+E-Nfd4U`0ULXu4~!=rKNQ|a&`%HV^$qM8Gh9>E;_dJBr9uY_4dq8T&xBAa zrxsZ^u=sk+;5iID!lLVmx;zX&J(bd_3&(0ShIjb%w(CjT?mCzn{Wy4L)1$wMea5ir z!{AZ2!EkN91o(87C18^hD4WffPxB2ul}&VZbBt_eF#Ifm(3fl`cxP^N`VekNSI}}E zC|yODraVqlnu^En*4>0D{A2(>v4?;X%0(8q>v8hRI@u7Z>!-_aVdZB)dH4s!G-Rc6 z1MeROK;U1mEAC$%WH`O|NSly2aMCce?6)KNmlorUj*I{yYeWaoc<5Z!zfZ+N$VD} zd9OHz%%o6plzuY1<$v}CL?8%fTmj)hh|2)w7<7J@LdwQsI1p(h*GB+EliCDpTj^-B z5YgS{1gy_I;{@y##AePVX~@UClswplXX*YG#?Y?-*u7<@07F2$zidHTg`k1wir?>E@{fOno>ci*v=dA;-lFHq+h7`9>-%*HluQsdhU5jKAl3rG8NB< zf_Ty>H0BwNOo9Oxd_iX@3u_IYge~q9LA+!tO@|$A`$vj0Ohen6K?^e!JUOu9B<^cY z@e?JlI^^=_t{DomxA%LeZIPHBjtWLkPNxKnr+sl;Vfba zr2XskmZ3DqU*lcaP+BmOIz$T0 zxnHLbCq~F1_aRkYcnTGe6NS+qV3Jh^30quj&Hy=SfeE3(yhfldkN<%-V6pV5;so0- z%wyxEPEOTe^XV(8=7HYyo;CY;v>W=z(xd1)8y;nUCmm+-m6N*}nSHI3t*O@{;iIGj 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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 18/20] 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 19/20] 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 20/20] 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: