Ansible.sh
#!/usr/bin/env bash
# coding: utf-8
# bash: 3.x
# Copyright © 2025 Florent Claerhout.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -euo pipefail
function _abort {
printf "\033[91m%s -- aborting\033[0m\n" "$*" >&2
exit 1
}
function create_ansible_venv {
local -r dir_path=${1:?missing dir_path}
local -r python_version_major="3.13"
# install platform dependencies:
case "${OSTYPE}" in
darwin*)
if pkgutil --packages | grep com.apple.pkg.CLTools_Executables
then
echo "CLT already installed, skipping"
else
xcode-select --install
echo "Waiting for CLT to be installed"
while ! xcode-select -p > /dev/null 2>&1
do
sleep 1
done
fi
case $(sw_vers --productVersion) in
# python 3.9
26.*)
local -r python_version="3.13.9"
curl https://www.python.org/ftp/python/${python_version}/python-${python_version}-macos11.pkg --output ~/Downloads/python-${python_version}.pkg
open -W ~/Downloads/python-${python_version}.pkg
;;
*)
_abort "Yet unsupported macOS version -- open an issue to request support"
esac
;;
*)
_abort "Yet unsupported platform -- open an issue to request support"
esac
if which python${python_version_major} >/dev/null 2>&1
then
echo "Using $(python${python_version_major} --version)"
else
_abort "Python${python_version_major} is missing -- please investigate"
fi
if [[ -f "${dir_path}/bin/ansible" ]]
then
echo "ansible venv already created, skipping"
else
rm -rf "${dir_path}"
python${python_version_major} -m venv "${dir_path}"
"${dir_path}/bin/pip" --no-cache-dir install ansible certifi
fi
}
function print_ansible_dir_path {
local -r ansible_file_path=$(which ansible)
if [[ -z ${ansible_file_path} ]]
then
local -r ansible_venv_path="/tmp/ansible.venv"
create_ansible_venv "${ansible_venv_path}" 1>&2
echo "${ansible_venv_path}/bin"
else
dirname "${ansible_file_path}"
fi
}
function print_usage {
cat <<-EOF
Ansible wrapper: install Ansible if needed and run it.
Usage:
$(basename "$0") [options] VARIANT ARGV…
Options:
-C PATH reset working directory
-e edit source file and exit
-h print this help and exit
-x trace execution
Variants:
adhoc -> ansible
playbook -> ansible-playbook
Examples:
ansible.sh adhoc -m ping localhost
ansible.sh playbook -i inventory.yml playbook.yml
EOF
}
while getopts "C:ehx" OPT
do
case "${OPT}" in
C)
cd "${OPTARG}"
;;
e)
edit -W "${0}"
exit $?
;;
h)
print_usage
exit 0
;;
x)
set -x
;;
?)
_abort "syntax error -- use -h for help"
esac
done
shift $((OPTIND-1))
if [[ $# -eq 0 ]]
then
print_usage
else
ANSIBLE_DIR_PATH=$(print_ansible_dir_path)
export SSL_CERT_FILE=$("${ANSIBLE_DIR_PATH}/python3" -m certifi)
case "${1}" in
adhoc)
"${ANSIBLE_DIR_PATH}/ansible" "${@:2}"
;;
playbook)
"${ANSIBLE_DIR_PATH}/ansible-playbook" "${@:2}"
;;
*)
_abort "${1}: unsupported command -- use -h for help"
esac
fi