# shellcheck shell=sh set -eu remove_externally_managed_marker() { "${python_interpreter}" -c ' import pathlib import sysconfig path = pathlib.Path(sysconfig.get_path("stdlib")) / "EXTERNALLY-MANAGED" path.unlink(missing_ok=True) ' } install_ssh_keys() { if [ ! -f "${ssh_private_key_path}" ]; then # write public/private ssh key pair public_key_path="${ssh_private_key_path}.pub" # shellcheck disable=SC2174 mkdir -m 0700 -p "${ssh_path}" touch "${public_key_path}" "${ssh_private_key_path}" chmod 0600 "${public_key_path}" "${ssh_private_key_path}" echo "${ssh_public_key}" > "${public_key_path}" echo "${ssh_private_key}" > "${ssh_private_key_path}" # add public key to authorized_keys authoried_keys_path="${HOME}/.ssh/authorized_keys" # the existing file is overwritten to avoid conflicts (ex: RHEL on EC2 blocks root login) cat "${public_key_path}" > "${authoried_keys_path}" chmod 0600 "${authoried_keys_path}" # add localhost's server keys to known_hosts known_hosts_path="${HOME}/.ssh/known_hosts" for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat "${key}")" >> "${known_hosts_path}" done fi } customize_bashrc() { true > ~/.bashrc # Show color `ls` results when available. if ls --color > /dev/null 2>&1; then echo "alias ls='ls --color'" >> ~/.bashrc elif ls -G > /dev/null 2>&1; then echo "alias ls='ls -G'" >> ~/.bashrc fi # Improve shell prompts for interactive use. echo "export PS1='\[\e]0;\u@\h: \w\a\]\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> ~/.bashrc } install_pip() { if ! "${python_interpreter}" -m pip.__main__ --version --disable-pip-version-check 2>/dev/null; then case "${python_version}" in *) pip_bootstrap_url="https://ci-files.testing.ansible.com/ansible-test/get-pip-23.1.2.py" ;; esac while true; do curl --silent --show-error "${pip_bootstrap_url}" -o /tmp/get-pip.py && \ "${python_interpreter}" /tmp/get-pip.py --disable-pip-version-check --quiet && \ rm /tmp/get-pip.py \ && break echo "Failed to install packages. Sleeping before trying again..." sleep 10 done fi } bootstrap_remote_alpine() { py_pkg_prefix="py3" packages=" acl bash gcc python3-dev ${py_pkg_prefix}-pip sudo " if [ "${controller}" ]; then packages=" ${packages} ${py_pkg_prefix}-cryptography ${py_pkg_prefix}-packaging ${py_pkg_prefix}-yaml ${py_pkg_prefix}-jinja2 ${py_pkg_prefix}-resolvelib " fi while true; do # shellcheck disable=SC2086 apk add -q ${packages} \ && break echo "Failed to install packages. Sleeping before trying again..." sleep 10 done # Upgrade the `libexpat` package to ensure that an upgraded Python (`pyexpat`) continues to work. while true; do # shellcheck disable=SC2086 apk upgrade -q libexpat \ && break echo "Failed to upgrade libexpat. Sleeping before trying again..." sleep 10 done } bootstrap_remote_fedora() { py_pkg_prefix="python3" packages=" acl gcc ${py_pkg_prefix}-devel " if [ "${controller}" ]; then packages=" ${packages} ${py_pkg_prefix}-cryptography ${py_pkg_prefix}-jinja2 ${py_pkg_prefix}-packaging ${py_pkg_prefix}-pyyaml ${py_pkg_prefix}-resolvelib " fi while true; do # shellcheck disable=SC2086 dnf install -q -y ${packages} \ && break echo "Failed to install packages. Sleeping before trying again..." sleep 10 done } bootstrap_remote_freebsd() { packages=" python${python_package_version} py${python_package_version}-sqlite3 py${python_package_version}-setuptools bash curl gtar sudo " if [ "${controller}" ]; then jinja2_pkg="py${python_package_version}-jinja2" cryptography_pkg="py${python_package_version}-cryptography" pyyaml_pkg="py${python_package_version}-yaml" packaging_pkg="py${python_package_version}-packaging" # Declare platform/python version combinations which do not have supporting OS packages available. # For these combinations ansible-test will use pip to install the requirements instead. case "${platform_version}/${python_version}" in 13.3/3.9) # defaults above 'just work'TM ;; 13.3/3.11) jinja2_pkg="" # not available cryptography_pkg="" # not available pyyaml_pkg="" # not available ;; 14.0/3.9) # defaults above 'just work'TM ;; 14.0/3.11) cryptography_pkg="" # not available jinja2_pkg="" # not available pyyaml_pkg="" # not available ;; *) # just assume nothing is available jinja2_pkg="" # not available cryptography_pkg="" # not available pyyaml_pkg="" # not available packaging_pkg="" # not available ;; esac packages=" ${packages} ${packaging_pkg} libyaml ${pyyaml_pkg} ${jinja2_pkg} ${cryptography_pkg} " fi while true; do # shellcheck disable=SC2086 env ASSUME_ALWAYS_YES=YES pkg bootstrap && \ pkg install -q -y ${packages} \ && break echo "Failed to install packages. Sleeping before trying again..." sleep 10 done install_pip if ! grep '^PermitRootLogin yes$' /etc/ssh/sshd_config > /dev/null; then sed -i '' 's/^# *PermitRootLogin.*$/PermitRootLogin yes/;' /etc/ssh/sshd_config service sshd restart fi # make additional wheels available for packages which lack them for this platform echo "# generated by ansible-test [global] extra-index-url = https://spare-tire.testing.ansible.com/simple/ prefer-binary = yes " > /etc/pip.conf # enable ACL support on the root filesystem (required for become between unprivileged users) fs_path="/" fs_device="$(mount -v "${fs_path}" | cut -w -f 1)" # shellcheck disable=SC2001 fs_device_escaped=$(echo "${fs_device}" | sed 's|/|\\/|g') mount -o acls "${fs_device}" "${fs_path}" awk 'BEGIN{FS=" "}; /'"${fs_device_escaped}"'/ {gsub(/^rw$/,"rw,acls", $4); print; next} // {print}' /etc/fstab > /etc/fstab.new mv /etc/fstab.new /etc/fstab # enable sudo without a password for the wheel group, allowing ansible to use the sudo become plugin echo '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > /usr/local/etc/sudoers.d/ansible-test } bootstrap_remote_macos() { # Silence macOS deprecation warning for bash. echo "export BASH_SILENCE_DEPRECATION_WARNING=1" >> ~/.bashrc # Make sure ~/ansible/ is the starting directory for interactive shells on the control node. # The root home directory is under a symlink. Without this the real path will be displayed instead. if [ "${controller}" ]; then echo "cd ~/ansible/" >> ~/.bashrc fi # Make sure commands like 'brew' can be found. # This affects users with the 'zsh' shell, as well as 'root' accessed using 'sudo' from a user with 'zsh' for a shell. # shellcheck disable=SC2016 echo 'PATH="/usr/local/bin:$PATH"' > /etc/zshenv } bootstrap_remote_rhel_9() { if [ "${python_version}" = "3.9" ]; then py_pkg_prefix="python3" else py_pkg_prefix="python${python_version}" fi packages=" gcc ${py_pkg_prefix}-devel " # pip is not included in the Python devel package under Python 3.11 if [ "${python_version}" != "3.9" ]; then packages=" ${packages} ${py_pkg_prefix}-pip " fi # Jinja2 is not installed with an OS package since the provided version is too old. # Instead, ansible-test will install it using pip. # packaging and resolvelib are missing for Python 3.11 (and possible later) so we just # skip them and let ansible-test install them from PyPI. if [ "${controller}" ]; then packages=" ${packages} ${py_pkg_prefix}-cryptography ${py_pkg_prefix}-pyyaml " fi while true; do # shellcheck disable=SC2086 dnf install -q -y ${packages} \ && break echo "Failed to install packages. Sleeping before trying again..." sleep 10 done } bootstrap_remote_rhel() { case "${platform_version}" in 9.*) bootstrap_remote_rhel_9 ;; esac } bootstrap_remote_ubuntu() { py_pkg_prefix="python3" packages=" acl gcc python${python_version}-dev python3-pip python${python_version}-venv " if [ "${controller}" ]; then cryptography_pkg="${py_pkg_prefix}-cryptography" jinja2_pkg="${py_pkg_prefix}-jinja2" packaging_pkg="${py_pkg_prefix}-packaging" pyyaml_pkg="${py_pkg_prefix}-yaml" resolvelib_pkg="${py_pkg_prefix}-resolvelib" # Declare platforms which do not have supporting OS packages available. # For these ansible-test will use pip to install the requirements instead. # Only the platform is checked since Ubuntu shares Python packages across Python versions. case "${platform_version}" in "20.04") jinja2_pkg="" # too old resolvelib_pkg="" # not available ;; esac packages=" ${packages} ${cryptography_pkg} ${jinja2_pkg} ${packaging_pkg} ${pyyaml_pkg} ${resolvelib_pkg} " fi while true; do # shellcheck disable=SC2086 apt-get update -qq -y && \ DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --no-install-recommends ${packages} \ && break echo "Failed to install packages. Sleeping before trying again..." sleep 10 done } bootstrap_docker() { # Required for newer mysql-server packages to install/upgrade on Ubuntu 16.04. rm -f /usr/sbin/policy-rc.d for key_value in ${python_interpreters}; do IFS=':' read -r python_version python_interpreter << EOF ${key_value} EOF echo "Bootstrapping Python ${python_version} at: ${python_interpreter}" remove_externally_managed_marker done } bootstrap_remote() { for key_value in ${python_interpreters}; do IFS=':' read -r python_version python_interpreter << EOF ${key_value} EOF echo "Bootstrapping Python ${python_version} at: ${python_interpreter}" python_package_version="$(echo "${python_version}" | tr -d '.')" case "${platform}" in "alpine") bootstrap_remote_alpine ;; "fedora") bootstrap_remote_fedora ;; "freebsd") bootstrap_remote_freebsd ;; "macos") bootstrap_remote_macos ;; "rhel") bootstrap_remote_rhel ;; "ubuntu") bootstrap_remote_ubuntu ;; esac remove_externally_managed_marker done } bootstrap() { ssh_path="${HOME}/.ssh" ssh_private_key_path="${ssh_path}/id_${ssh_key_type}" install_ssh_keys customize_bashrc # allow tests to detect ansible-test bootstrapped instances, as well as the bootstrap type echo "${bootstrap_type}" > /etc/ansible-test.bootstrap case "${bootstrap_type}" in "docker") bootstrap_docker ;; "remote") bootstrap_remote ;; esac } # These variables will be templated before sending the script to the host. # They are at the end of the script to maintain line numbers for debugging purposes. bootstrap_type=#{bootstrap_type} controller=#{controller} platform=#{platform} platform_version=#{platform_version} python_interpreters=#{python_interpreters} ssh_key_type=#{ssh_key_type} ssh_private_key=#{ssh_private_key} ssh_public_key=#{ssh_public_key} bootstrap