Merge pull request #1254 from moreati/issue1118-update-containers

CI: Build newer test images
pull/1370/head
Alex Willmer 1 week ago committed by GitHub
commit 017de4c8e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
.gitignore vendored

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

@ -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

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

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

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

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

@ -181,6 +181,6 @@
{% endfor %}
validate: '/usr/sbin/visudo -cf %s'
when:
- ansible_virtualization_type != "docker"
- ansible_connection == "local"
roles:
- role: user_policies

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,9 @@
bootstrap_packages: [python3, python3-apt]
docker_base: ubuntu:24.04
packages:
- libjson-perl
- locales
- opendoas
- python3-virtualenv
- virtualenv

@ -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,8 +20,36 @@ centos8
debian9
debian10
debian11
debian12
[ubuntu]
ubuntu1604
ubuntu1804
ubuntu2004
ubuntu2204
ubuntu2404
[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
alma9
debian11
debian12
ubuntu2004
ubuntu2204
ubuntu2404

@ -0,0 +1,2 @@
bootstrap_packages: []
package_manager_repos: []

@ -0,0 +1,3 @@
- name: Bootstrap
raw: "{{ lookup('template', 'bootstrap.sh.j2') }}"
changed_when: true

@ -0,0 +1,21 @@
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
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
exit 42
fi
{% endif %}

@ -0,0 +1,6 @@
- name: Update GRUB
command: update-grub
changed_when: true
- name: Reboot
reboot:

@ -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

@ -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: []

@ -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

@ -1,2 +1,4 @@
- name: Restart sshd
meta: noop
command: "true"
changed_when: false
check_mode: false

@ -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

@ -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

@ -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

@ -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
# ================== ======================

Loading…
Cancel
Save