2023-12-01 18:53:31 +01:00
#!/usr/bin/env bash
# set -o errexit
## Colors
readonly COLOR_RED='\033[0;31m'
readonly COLOR_GREEN='\033[0;32m'
readonly COLOR_YELLOW='\033[0;33m'
readonly COLOR_BLUE='\033[0;34m'
readonly COLOR_PURPLE='\033[1;35m'
readonly COLOR_RESET='\033[0m'
RETURNCODE_SUCCESS=0
RETURNCODE_ERROR=1
2023-12-06 10:25:01 +01:00
DEFAULT_PYTHON_PATH='/usr/bin/python3'
PYTHON_PATH=''
readonly LOG_LEVEL_ERROR=0
readonly LOG_LEVEL_WARNING=1
readonly LOG_LEVEL_INFO=2
readonly LOG_LEVEL_DEBUG=3
2023-12-11 14:44:10 +01:00
LOG_LEVEL=$LOG_LEVEL_DEBUG
2023-12-01 18:53:31 +01:00
log()
{
local log_type="$1" # 'debug', 'info', 'warning' or 'error'
local message="$2"
local message_color=''
2023-12-06 10:25:01 +01:00
local message_level=''
2023-12-01 18:53:31 +01:00
case "$log_type" in
'error')
message_color="$COLOR_RED"
2023-12-06 10:25:01 +01:00
message_level="$LOG_LEVEL_ERROR"
2023-12-01 18:53:31 +01:00
;;
'warning')
message_color="$COLOR_YELLOW"
2023-12-06 10:25:01 +01:00
message_level="$LOG_LEVEL_WARNING"
2023-12-01 18:53:31 +01:00
;;
'info')
message_color="$COLOR_BLUE"
2023-12-06 10:25:01 +01:00
message_level="$LOG_LEVEL_INFO"
2023-12-01 18:53:31 +01:00
;;
'debug')
message_color="$COLOR_PURPLE"
2023-12-06 10:25:01 +01:00
message_level="$LOG_LEVEL_DEBUG"
2023-12-01 18:53:31 +01:00
;;
*)
echo "unexpected log type $log_type"
exit "$RETURNCODE_ERROR"
esac
2023-12-06 10:25:01 +01:00
if [ "$message_level" -le "$LOG_LEVEL" ]
then
printf "%b : %s\n" "${message_color}${log_type}${COLOR_RESET}" "$message"
fi
2023-12-01 18:53:31 +01:00
}
replace_in_file()
{
local file_path="$1"
local searched_string="$2"
local replacement_string="$3"
log 'debug' "replacing ${searched_string} with ${replacement_string} in $file_path"
local safe_searched_string=$(echo "$searched_string" | tr '"' '\"' | tr '$' '\$')
local safe_replacement_string=$(echo "$replacement_string" | tr '"' '\"' | tr '$' '\$')
# log 'debug' "safe_searched_string : $safe_searched_string"
printf 'safe_searched_string: %b\n' "$safe_searched_string"
grep -q "$safe_searched_string" "$file_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
log 'error' "failed to find $safe_searched_string in $file_path"
return $RETURNCODE_ERROR
fi
# log 'debug' "searching a separator"
local sep=''
for sep in '/' '|' '@'
do
if $(echo "$safe_searched_string" | grep -q "$sep") || $(echo "$safe_replacement_string" | grep -q "$sep")
then
sep=''
else
# found a suitable separator for use in sed
log 'debug' "found separator: $sep"
break
fi
done
if [ "$sep" = '' ]
then
log 'error' "failed to find a sed separator suitable for $safe_searched_string and $safe_replacement_string"
fi
sed -i "s${sep}${safe_searched_string}${sep}${safe_replacement_string}${sep}" "$file_path"
grep -q "$safe_replacement_string" "$file_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
log 'error' "failed to find $safe_replacement_string in $file_path"
return $RETURNCODE_ERROR
fi
}
2023-12-06 10:24:17 +01:00
is_version_greater_than()
{
first_value="${1}"
value_to_compare="${2}"
## Return False by default
return_is_version_greater_than="1"
log 'debug' "is_version_greater_than - Is first value (${first_value}) greater than the second value (${value_to_compare})."
if printf '%s\n' "${first_value}" "${value_to_compare}" | sort --check=quiet --version-sort; then
log 'debug' "${debug_prefix}is_version_greater_than - ${first_value} <= ${value_to_compare} ."
return_is_version_greater_than="1"
else
log 'debug' "${debug_prefix}is_version_greater_than - ${first_value} > ${value_to_compare} ."
return_is_version_greater_than="0"
fi
unset first_value
unset value_to_compare
return "${return_is_version_greater_than}"
}
2023-12-01 18:53:31 +01:00
controller__get_local_repos_path()
{
local debops_controller_path="$1"
echo "${debops_controller_path}/ansible.debops.git"
}
controller__get_reports_path()
{
local debops_controller_path="$1"
echo "${debops_controller_path}/reports"
}
controller__get_virtualenv_path()
{
local debops_controller_path="$1"
echo "${debops_controller_path}/debops.venv"
}
controller__get_debops_env_path()
{
local debops_controller_path="$1"
2023-12-13 11:19:33 +01:00
local local_repos_path=$(controller__get_local_repos_path "${debops_controller_path}")
echo "${local_repos_path}/.env" # see the allowed locations in man debops-config
2023-12-01 18:53:31 +01:00
}
2023-12-13 11:24:57 +01:00
path__as_regular_path()
{
local path="$1"
echo $path | sed "s|~|$home_dir|g"
}
# checks the validity of the given debops controller
controller__check_integrity()
{
local debops_controller_path="$1"
local local_repos_path=$(controller__get_local_repos_path "$debops_controller_path")
local virtual_env_path=$(controller__get_virtualenv_path "$debops_controller_path")
local debops_exit_code=''
# (debops.venv) debopsupdater@mazinger:~/debops/controllers/alambix/ansible.debops.git$ debops env
# DEBOPS_ANSIBLE_COLLECTIONS_PATH=~/debops/controllers/alambix/debops.venv/lib/python3.11/site-packages/debops/_data/ansible/collections
# DEBOPS_VENV=/home/debopsupdater/debops/controllers/alambix/debops.venv
# DEBOPS_VENV_PLAYBOOKS=/home/debopsupdater/debops/controllers/alambix/debops.venv/lib/python3.11/site-packages/debops/_data/ansible/collections/ansible_collections/debops/debops/playbooks
# DEBOPS_VENV_ROLES=/home/debopsupdater/debops/controllers/alambix/debops.venv/lib/python3.11/site-packages/debops/_data/ansible/collections/ansible_collections/debops/debops/roles
# DEBOPS_PROJECT_PATH=~/debops/controllers/alambix/ansible.debops.git
# DEBOPS_ANSIBLE_INVENTORY=~/debops/controllers/alambix/ansible.debops.git/ansible/inventory
# ANSIBLE_CONFIG=~/debops/controllers/alambix/ansible.debops.git/ansible.cfg
pushd "$local_repos_path"
source "$virtual_env_path/bin/activate"
eval "$(debops env)"
local home_dir=$(echo ~)
local var_name=''
for var_name in DEBOPS_ANSIBLE_COLLECTIONS_PATH DEBOPS_VENV DEBOPS_VENV_PLAYBOOKS DEBOPS_VENV_ROLES DEBOPS_PROJECT_PATH DEBOPS_ANSIBLE_INVENTORY ANSIBLE_CONFIG
do
local var_value=''
var_value=${!var_name}
var_value=$(path__as_regular_path $var_value)
if [ $? != 0 ]
then
log 'error' "failed to get the regular path from $var_value"
return "$RETURNCODE_ERROR"
fi
echo $var_value | grep -q "^$debops_controller_path"
if [ $? != 0 ]
then
log 'error' "the path for $var_name ($var_value) is expected to start with the path of the debops controller $debops_controller_path"
return "$RETURNCODE_ERROR"
fi
done
deactivate
popd
}
2023-12-01 18:53:31 +01:00
deboco__init()
{
local debops_controller_path="$1"
log 'info' "creating debobs controller in $debops_controller_path"
if [ "$debops_controller_path" = '' ]
then
log 'error' "wrong debops controller path: $debops_controller_path"
return "$RETURNCODE_ERROR"
fi
if [ -d "$debops_controller_path" ]
then
log 'error' "$debops_controller_path already exists"
return "$RETURNCODE_ERROR"
fi
2023-12-13 11:26:49 +01:00
local exit_code=''
2023-12-01 18:53:31 +01:00
local local_repos_path=$(controller__get_local_repos_path "$debops_controller_path")
local ipr_debops_url='https://git.ipr.univ-rennes.fr/cellinfo/ansible.debops'
log 'info' "cloning $ipr_debops_url into $local_repos_path"
git clone https://git.ipr.univ-rennes.fr/cellinfo/ansible.debops "$local_repos_path"
2023-12-13 11:26:49 +01:00
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
2023-12-01 18:53:31 +01:00
local debops_env_path=$(controller__get_debops_env_path "$debops_controller_path")
local debops_update_script_path="$local_repos_path/bin/update-debops.sh"
local from_line='readonly DEBOPS_ENVIRONMENT_FILE="${HOME}/.config/debops/environment"'
# local from_line='readonly DEBOPS_ENVIRONMENT_FILE'
local to_line="readonly DEBOPS_ENVIRONMENT_FILE=${debops_env_path}"
log 'info' "replacing $from_line with $to_line in $debops_update_script_path so that the script uses a debops environment specific to this controller"
replace_in_file "$debops_update_script_path" "$from_line" "$to_line"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
local bug_3678_is_fixed='false'
if [ "$bug_3678_is_fixed" = 'false' ]
then
log 'warning' "disabling pip update in $debops_update_script_path because of https://bugzilla.ipr.univ-rennes.fr/show_bug.cgi?id=3678"
replace_in_file "$debops_update_script_path" \
'python -m pip install --quiet --requirement "${PIP_PKG_LIST}" --upgrade' \
'# python -m pip install --quiet --requirement "${PIP_PKG_LIST}" --upgrade'
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
replace_in_file "$debops_update_script_path" \
'|| error_message "Error with pip packages upgrade ' \
'# || error_message "Error with pip packages upgrade '
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
fi
2023-12-05 13:35:25 +01:00
local bug_3715_is_fixed='false'
if [ "$bug_3715_is_fixed" = 'false' ]
then
# make sure that the python version is at least 3.10 so that ansible>=2.15 is installed (as debops 3.1 requires ansible>=2.15)
2023-12-06 10:24:17 +01:00
local python_version=$(${PYTHON_PATH} --version | cut --delimiter=" " --fields=2 --)
log 'debug' "python_version=$python_version"
if is_version_greater_than "3.10" "${python_version}"
then
log 'error' "debops only works with python version 3.10 or more, and your version of python is $python_version"
return $RETURNCODE_ERROR
fi
2023-12-05 13:35:25 +01:00
replace_in_file "$debops_update_script_path" \
'virtualenv --quiet -- ' \
2023-12-06 10:24:17 +01:00
"${PYTHON_PATH} -m venv "
2023-12-05 13:35:25 +01:00
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
fi
2023-12-01 18:53:31 +01:00
local virtual_env_path=$(controller__get_virtualenv_path "$debops_controller_path")
# no need to call update-debops.sh since update-dev.sh does it
DEBOPS_VENV="$virtual_env_path" "$local_repos_path/bin/update-dev.sh"
2023-12-13 11:26:49 +01:00
exit_code=$?
if [ "$exit_code" != "$RETURNCODE_SUCCESS" ]
then
log 'error' "$local_repos_path/bin/update-dev.sh with DEBOPS_VENV=$virtual_env_path failed with exit code $exit_code"
return $RETURNCODE_ERROR
fi
2023-12-13 11:24:57 +01:00
controller__check_integrity "$debops_controller_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
2023-12-01 18:53:31 +01:00
}
deboco__update()
{
local debops_controller_path="$1"
log 'info' "updating debobs controller in $debops_controller_path"
2023-12-13 11:24:57 +01:00
controller__check_integrity "$debops_controller_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
2023-12-13 11:26:49 +01:00
local exit_code=''
2023-12-13 11:24:57 +01:00
2023-12-01 18:53:31 +01:00
reports_path="$(controller__get_reports_path "$debops_controller_path")"
mkdir -p "${reports_path}"
report_file_path=${reports_path}/$(date --iso=seconds)-init-${target_host_fqdn}
local local_repos_path=$(controller__get_local_repos_path "$debops_controller_path")
local virtual_env_path=$(controller__get_virtualenv_path "$debops_controller_path")
log info "updating repository $local_repos_path"
pushd "$local_repos_path"
git pull | tee --append ${report_file_path}
popd
log info "updating debops itself in $virtual_env_path"
DEBOPS_VENV="$virtual_env_path" "$local_repos_path/bin/update-dev.sh" | tee --append ${report_file_path}
2023-12-13 11:26:49 +01:00
exit_code=$?
if [ "$exit_code" != "$RETURNCODE_SUCCESS" ]
then
log 'error' "$local_repos_path/bin/update-dev.sh with DEBOPS_VENV=$virtual_env_path failed with exit code $exit_code"
return $RETURNCODE_ERROR
fi
2023-12-13 11:24:57 +01:00
controller__check_integrity "$debops_controller_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
2023-12-01 18:53:31 +01:00
}
deboco__configure_machine()
{
local debops_controller_path="$1"
local target_host_fqdn="$2" # the machine on which we want to install debops bootstrap, eg alambix-108.ipr.univ-rennes.fr
2023-12-05 13:38:09 +01:00
log 'info' "configuring $target_host_fqdn using debobs controller $debops_controller_path"
local error_code=$RETURNCODE_SUCCESS
deboco__init_machine "$debops_controller_path" "$target_host_fqdn"
if [ $? = 0 ]
then
deboco__update_machine "$debops_controller_path" "$target_host_fqdn"
if [ $? != 0 ]
then
log 'error' "update_machine failed"
error_code=$RETURNCODE_ERROR
fi
else
log 'error' "init_machine failed"
error_code=$RETURNCODE_ERROR
fi
return "$error_code"
}
deboco__init_machine()
{
local debops_controller_path="$1"
local target_host_fqdn="$2" # the machine on which we want to install debops bootstrap, eg alambix-108.ipr.univ-rennes.fr
log 'info' "installing debops bootstrap on $target_host_fqdn using debobs controller $debops_controller_path"
2023-12-01 18:53:31 +01:00
2023-12-13 11:24:57 +01:00
controller__check_integrity "$debops_controller_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
2023-12-01 18:53:31 +01:00
local error_code=$RETURNCODE_SUCCESS
reports_path="$(controller__get_reports_path "$debops_controller_path")"
mkdir -p "${reports_path}"
report_file_path=${reports_path}/$(date --iso=seconds)-init-${target_host_fqdn}
echo "installing debops bootstrap on ${target_host_fqdn} (report stored in ${report_file_path})"
local local_repos_path=$(controller__get_local_repos_path "$debops_controller_path")
local virtual_env_path=$(controller__get_virtualenv_path "$debops_controller_path")
2023-12-05 13:38:09 +01:00
local debops_exit_code=''
2023-12-01 18:53:31 +01:00
pushd "$local_repos_path"
source "$virtual_env_path/bin/activate"
ANS_HOST=$(echo ${target_host_fqdn} | sed -E 's/\.univ-rennes[1]?\.fr$//')
log 'debug' "ANS_HOST=${ANS_HOST}"
debops run bootstrap-ldap -l "${ANS_HOST:-/dev/null}" | tee --append ${report_file_path}
2023-12-05 13:38:09 +01:00
debops_exit_code=$?
echo "return code for debops run bootstrap-ldap -l ${ANS_HOST:-/dev/null} : $debops_exit_code" >> "${report_file_path}"
log 'debug' "debops_exit_code=$debops_exit_code"
if [ "$debops_exit_code" != "$RETURNCODE_SUCCESS" ]
2023-12-01 18:53:31 +01:00
then
log 'error' 'debops run bootstrap-ldap failed'
error_code="$RETURNCODE_ERROR"
fi
deactivate
popd
return "$error_code"
}
deboco__update_machine()
{
local debops_controller_path="$1"
2023-12-05 13:38:09 +01:00
local target_host_fqdn="$2" # eg alambix-108.ipr.univ-rennes.fr
log 'info' "updating $target_host_fqdn using debobs controller $debops_controller_path"
2023-12-01 18:53:31 +01:00
2023-12-13 11:24:57 +01:00
controller__check_integrity "$debops_controller_path"
if [ $? != "$RETURNCODE_SUCCESS" ]
then
return $RETURNCODE_ERROR
fi
2023-12-01 18:53:31 +01:00
local error_code=$RETURNCODE_SUCCESS
reports_path="$(controller__get_reports_path "$debops_controller_path")"
mkdir -p "${reports_path}"
2023-12-11 14:44:10 +01:00
report_file_path=${reports_path}/$(date --iso=seconds)-update-${target_host_fqdn}
echo "applying debops configuration on ${target_host_fqdn} (report stored in ${report_file_path})"
2023-12-01 18:53:31 +01:00
local local_repos_path=$(controller__get_local_repos_path "$debops_controller_path")
local virtual_env_path=$(controller__get_virtualenv_path "$debops_controller_path")
2023-12-05 13:38:09 +01:00
local debops_exit_code=''
2023-12-11 14:44:10 +01:00
log 'debug' "local_repos_path=$local_repos_path"
2023-12-01 18:53:31 +01:00
pushd "$local_repos_path"
source "$virtual_env_path/bin/activate"
ANS_HOST=$(echo ${target_host_fqdn} | sed -E 's/\.univ-rennes[1]?\.fr$//')
log 'debug' "ANS_HOST=${ANS_HOST}"
debops run site --limit "${ANS_HOST:-/dev/null}" | tee --append ${report_file_path}
2023-12-05 13:38:09 +01:00
debops_exit_code=$?
echo "return code for debops run site --limit ${ANS_HOST:-/dev/null} : $debops_exit_code" >> "${report_file_path}"
if [ "$debops_exit_code" != "$RETURNCODE_SUCCESS" ]
2023-12-01 18:53:31 +01:00
then
log 'error' 'debops run site failed'
error_code="$RETURNCODE_ERROR"
fi
deactivate
popd
return "$error_code"
}
deboco_print_usage()
{
echo "$0 performs a debops controller operation"
echo
echo "usage:"
2023-12-06 10:24:17 +01:00
echo " $0 [options] <command>"
2023-12-01 18:53:31 +01:00
echo
echo "where:"
2023-12-06 10:24:17 +01:00
echo " [options] is a set of:"
echo " --python-path <python-path>"
echo " specifies the python executable to use (must be python 3.10 or above). Default is $DEFAULT_PYTHON_PATH"
echo
2023-12-01 18:53:31 +01:00
echo " <command> is one of:"
echo " init <debobs-controller-path>"
echo " creates a debops controller in <debobs-controller-path>"
echo
echo " update <debobs-controller-path>"
echo " updates the debops controller in <debobs-controller-path>"
echo
2023-12-05 13:38:09 +01:00
echo " init_machine <debobs-controller-path> <machine-fqdn>"
echo " installs debops bootstrap on the machine <machine-fqdn> using the debops controller in <debobs-controller-path>"
2023-12-01 18:53:31 +01:00
echo
2023-12-05 13:38:09 +01:00
echo " update_machine <debobs-controller-path> <machine-fqdn>"
2023-12-01 18:53:31 +01:00
echo " updates the machine <machine-fqdn> using the debops controller in <debobs-controller-path>"
2023-12-05 13:38:09 +01:00
echo
echo " configure_machine <debobs-controller-path> <machine-fqdn>"
echo " configures the machine <machine-fqdn> using the debops controller in <debobs-controller-path>". Equivivalent to a init_machine followed by update_machine
echo
2023-12-06 10:24:17 +01:00
echo "example:"
echo " deboco --python-path /usr/bin/python3.10 init ~/work/debops/constrollers/alambix"
2023-12-01 18:53:31 +01:00
}
deboco()
{
2023-12-06 10:24:17 +01:00
PYTHON_PATH="$DEFAULT_PYTHON_PATH"
while true
do
local arg="$1"
shift
if [ "$arg" = '--python-path' ]
then
PYTHON_PATH="$1"
shift
elif type "deboco__${arg}" >/dev/null 2>&1
then
"deboco__${arg}" "$@"
return $?
else
log 'error' "unhandled argument: ${arg}"
deboco_print_usage
return "$RETURNCODE_ERROR"
fi
done
2023-12-01 18:53:31 +01:00
}
deboco $@